WinCE中断结构分析

来源:互联网 发布:java compare 返回值 编辑:程序博客网 时间:2024/06/03 16:00

以前写的原创博文,这里放一份

前一段时间研究了一下WinCE下的中断结构,整理了一下,希望与大家讨论。

最下面有PDF版本下载,便于保存

Windows Embedded CE 

 中断结构分析
关键字:WinCE,中断,体系,结构
摘要:本文主要以WinCE  .NET 5.0为操作系统平台,ARM为硬件平台,分析了WinCE下
中断的结构与实现方式

前言
  在嵌入式系统当中,对于中断的处理是非常重要的一部分内容。许多外围设备都需要通
过中断来实现自己的功能或者与系统内核交互,系统时钟本身也是由时钟中断产生的。所以
本文旨在分析WinCE下的中断的结构,以及常用的几种实现方式,来帮助读者了解WinCE
中断体系及实现自己的中断处理结构。
  下面的介绍如非特殊说明,均以 ARM架构为硬件基础,操作系统代码使用 Windows
embedded CE 5.0。

一 WinCE中断体系结构
  先看图 1:

图 1 WinCE中断体系结构

 

 这张是经典的说明中断体系的图,我们可以通过分析这张图来了解WinCE的中断体系。  

  从结构上看,WinCE中断涉及4层,即:硬件层、内核层、OAL层、IST处理层。了
解这 4层之间的交互传递将对我们了解WinCE中断处理很有帮助。

1 硬件层:
  硬件层就是实际触发中断的硬件,这里主要有两方面作用,一个是触发中断,第二个是
enable/disable硬件中断。

2 内核层:
  这一层由内核来处理,包括中断异常产生后跳转到相应的ISR,以及根据SYSINTR来
触发相应的Event。关于SYSINTR 和 IRQ 的概念后面会说明。

3 OAL层
  这一层主要就是我们需要实现的代码了,来识别硬件IRQ,对应到SYSINTR。

4 IST处理层
  一般使用 IST来做实际的中断处理,这样不会占用很多的锁定系统时间来处理中断,
但是对中断的实时性大打折扣

二  IRQ,ISR,IST和 SYSINTR

说到这里先解释下IRQ,ISR,IST及 SYSINTR 的概念、意义及相互关系。

IRQ:
IRQ (Interrupt request),中断请求。
这里就是外设或其它请求服务的设备发出来的中断。属于硬件中断,可能是一个电平触发的
GPIO 中断,也可能是内部DMA的一个中断。

ISR:
ISR (Interrupt serviceroutine),  中断处理程序。
WinCE实际上使用 ISR来处理中断,即默认的中断入口函数,在 ARM体系中,系统默认的
ISR就是 OEMInterruptHandler

IST:
IST (Interrupt servicethread),  中断服务线程。
在 ARM 的结构中,ISR 一般不会用来进行任何实际的操作,而只是返回一个 SYSINTR,
实际的操作全部在IST中完成,IST一般是在Device Manager 的一个线程中运行的一段
高优先级的应用程序代码,用来服务实际的中断请求。

SYSINTR:
在 WinCE中,SYSINTR 就是 system interrupt,就是一个操作系统的逻辑中断。
一般对于中断的处理方式都是将一个IRQ映射为一个或者多个(中断共享)SYSINTR,而后,
在实际的ISR中根据IRQ返回一个对应的SYSINTR用来告诉操作系统需要服务的逻辑对
象。

使用逻辑中断的好处当然就是可以实现虚拟的中断(一个 SYSINTR 就被 OS 认为是一个独
立中断)和中断共享(单 IRQ对应多 SYSINTR)。
逻辑中断是WinCE需要处理的实际对象,至于这个对象是一个共享的IRQ,还是一个虚拟
的中断,还是独立的物理中断,系统并不过问,从而隔离了硬件与逻辑,我们的 ISR 需要
做的也正是这种物理中断到逻辑中断的映射。

三 WinCE中断处理原理
  下面基于 ARM 体系,来介绍 WinCE中断处理的流程与原理。
  对于一个硬件IRQ中断,系统内核在捕获之后,会交给OEMInterruptHandler 处理,
这个函数就是我们实现中断处理的中心函数,首先我们从CPU 的寄存器里获得中断的信息,
这些信息告诉我们到底是哪个 IRQ 源触发了中断。
一般实现中断服务的方式有以下几种:

1. 简单中断处理——ISR模型
  最简单的中断处理办法就是在ISR中直接处理,这里就是指在OEMInterruptHandler
中直接对中断源进行判断,然后调用服务程序。

  这种方式的优点和缺点一样明显。
  优点:快速的响应了中断处理,使系统的实时性能增加不少
  缺点:由于进入OEMInterruptHandler的时候关闭了系统中断(当然你可以在ISR中
自己打开中断,不过处理起来较麻烦),所以如果处理时间过长,其他中断很可能被忽略,
造成中断丢失。并且系统被锁定,使得系统任务的响应变慢。

2. 中断处理的一般流程——IST模型
  前面看到了 ISR模型的优缺点。作为WinCE,主要定位还是民用的消费类电子产品,
所以,对于中断响应的实时性不是特别高,所以系统的运行响应速度就显得更加重要。而且
目前的嵌入式设备的处理速度越来越高,已经几乎达到了当时奔 3 的水平。所以 ISR 的模
型并不适用于WinCE。

如果把中断服务程序当作一个系统进程或者线程来处理,这样就不会造成系统被锁定,
中断被屏蔽等问题,使得中断服务程序和其它进程、线程一样被系统管理调度。于是就有了
IST的概念

IST 模型的想法是,在 ISR 中判断到具体的中断源 IRQ,就返回一个标志,用来标记
需要哪个程序来服务中断,然后重新打开中断,恢复系统调度,系统根据这个标志来激活相
应的程序来服务中断。

这个就是最常用的中断处理模型。使得中断被关闭,系统被锁定的时间最短。
在 WinCE中,经常使用的就是建立中断服务线程(IST),然后以IRQ 来申请一个系统
逻辑中断号(SYSINTR),创建一个事件(Event),将 Event 与 SYSINTR 绑定,随后 IST
阻塞在等待Event上面。

ISR 中只给系统返回一个 SYSINTR,系统根据绑定关系激活相应的Event,使得随后
的 IST得以运行。

这就是 IST的一般服务流程
IST模型的缺点就是中断服务的延迟较大,从 ISR 返回,到 IST开始运行,中间需要
花费一定的时间,相比 ISR 模型的中断实时性较差,但是这种代价对于我们的应用是值得
的。

四  IST模型的实现
  下面我们来看IST模型具体在我们的驱动中是如何实现的。
  上面已经介绍了 IST模型的一般服务流程,下面我们针对驱动程序实例,来分析具体
的实现步骤。
1 驱动程序中 IST的构建与中断初始化
上面介绍的 IST流程中,很多步骤都是WinCE的内置支持,也就是说你只要调用相应
的 API就可以实现功能了,不需要自己编写太多的代码。只需要实现一些流程代码。
首先是驱动程序端的中断初始化。假设现在有一个驱动程序,需要服务中断源,IRQ
为 0x12。

a)  以 IRQ 为参数,申请SYSINTR,方法为调用
KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR,&(dwIrq),  
                       sizeof(UINT32),&dwSysIntr,  
                       sizeof(UINT32), NULL)
其中 dwIrq为IRQ号,即0x12
dwSysIntr 为系统中断号,也就是调用返回的结果存放的位置



b)  创建与 SYSINTR 绑定的Event
由于我们的IST是需要Event激活的,所以这里申请一个 Event。
申请 Event的步骤比较简单和标准
hISTEvent = CreateEvent(0,FALSE,FALSE,NULL);

c)  将SYSINTR 与Event绑定
调用 InterruptInitialize(dwSysIntr,hISTEvent,0,0)将 SYSINTR 与 Event绑
定,用来在OEMInterruptHandler 中返回SYSINTR 时激活相对应的 Event




d)  创建一个 IST,并且等待hISTEvent
到了这一步,中断关于系统方面的初始化基本结束,剩下的就是创建一个 IST,然
后等待 Event来运行中断服务代码,例如:
   
while(TRUE) {
WaitForSingleObject(hISTEvent,INFINITE) ==
WAIT_OBJECT_0)


}

这里需要注意的是IST什么时候创建都可以,但是在InterruptInitialize之前不要
运行 IST 以等待这个 Event,也就是说在 InterruptInitialize 之前不要使用这个
Event,否则会导致InterruptInitialize失败。
还有就是不要使用WaitForMultipleObjects来等待Event。
在中断处理完成之后需要调用 InterruptDone,参数为该中断的SYSINTR。来通
知系统,中断处理完成,系统重新使能该中断


    到了这里,驱动的中断初始化工作就全部完成了。


 OEM层需要做的工作
OEM 层 主 要 是 控 制 IRQ 的enable  (BSPIntrEnableIrq) 与disable
(BSPIntrDisableIrq), 当然要初始化 IRQ 的配置,使其在正确的触发状态,比如上升延
触发

    至此一个中断处理的IST模型就实现了,系统在IRQ触发时调用映射函数,获得相应
IRQ 的 SYSINTR,然后返回合法的SYSINTR给系统,系统查表激活相应的Event,对应
的 IST进行中断服务,然后再次等待 Event。

   中断资源的释放
    当不需要当前中断继续服务的时候可以通过调用KernelIoControl 来释放申请到的
SYSINTR,具体格式为:
  KernelIoControl(IOCTL_HAL_RELEASE_SYSINTR,  dwSysIntr,  sizeof(DWORD),
NULL, 0, NULL);

  其中 dwSysIntr 就是需要释放的 SYSINTR号码。



五 可安装的 ISR
    为什么要使用可安装 ISR(以下简称 IISR)
    需要 IISR 的目的有两种:
I. 动态的安装中断程序
在系统运行的过程中注册中断,这种中断一般是不可预知设备的中断,常用在总线设备
中,比如PCI设备

II. 中断共享
当多个中断源使用同一个中断号(IRQ)时,就需要使用 IISR 来实现了

当然如果是需要动态安装的共享中断就最适合了。

因为我们的 IST模型中,中断服务程序就是驱动中的IST,IRQ与 IST是一对一的关
系。所以在需要动态添加一个中断服务程序的时候就没有办法处理了。
同样由于 IRQ 与 IST 的一一对应关系对于一个 IRQ 对应多个需要服务的 IST 就同样
没有办法处理。
基于上面的情况才会有IISR 的出现,IISR 从本质上是在ISR 中提供了一个接口,当
ISR 调用 NKCallIntChain时,以此IRQ为参数,在链表中依次查找是哪一个服务程序来
服务这次 IRQ,然后返回相应的 SYSINTR,此后的动作与 IST 模型就基本一样,通过
SYSINTR 来激活Event,从而启动相应的 IST。
  所以 IISR 的实现就是动态的向某一个IRQ服务程序链表添加结点的过程。

2 IISR的实现
下面我们来看看IISR 的具体实现步骤:
  首先我们需要了解IISR服务中断的实现原理,如上面描述的,根据IRQ,来顺序调用
链表中的中断处理程序。所以我们可以有两个选择,一个就是类似 ISR 模型,直接在链表
中的中断处理程序中判断是不是自己的中断,并且做处理。还有一种方式就是类似 IST 模
型,如果判断是自己的中断,则返回一个SYSINTR,以此SYSINTR 来激活IST。
  无论哪种方法,关于注册中断和查询中断的方式是一样的,下面我们来看下如何将中断
程序添加到链表,又如何在中断来的时候去搜索链表。
  Microsoft提供了一个通用的IISR的处理模型,叫做GIISR,这是一个以 IST模型处
理 IISR 的模块,源程序可以在WINCE500\PUBLIC\COMMON\OAK\DRIVERS\GIISR
找到。熟悉了 GIISR,想实现自己的 IISR 处理程序或者基于 ISR 模型的处理,都比较简
单了。
  下面我们就分析这种比较通用的处理 IISR的模型。
a)  首先我们需要以 IRQ 来申请 SYSINTR,并且将SYSINTR 与 Event 绑定,这些
步骤与IST模型中介绍的一样,这里就不重复叙述了,IISR 在这里与 IST模型并
没有任何的不同。其与 IST 模型的唯一不同点就是如何根据 IRQ 来判断相应的

SYSINTR。
在 IST 模型中是 OEM写死的一个判断程序,而 IISR 可以动态来注册一个判断程
序给系统调用,这是唯一的实现区别。
b)  下面我们需要注册可安装中断程序的 dll,和dll中的中断处理函数,并且将他们与
某一个特定的IRQ相关联
这个过程是通过调用LoadIntChainHandler函数来实现的。
这里我们的中断服务dll叫做”giisr.dll”,处理函数名叫做”ISRHandler”,对应IRQ
为0x20,则函数调用形式如下:
HANDLE  hIsrHandler   LoadIntChainHandler(TEXT(“giisr.dll”),
TEXT(“ISRHandler”), 0x20);



c)  上一步在GIISR中通过CreateInstance把这个新的中断处理程序加入GIISR自
己的管理。GIISR 的主要作用就是判断当中断来的时候,是不是其内部数组中的某
个成员需要服务中断。所以需要更多的信息用来判断中断是否匹配当前的中断服务
程序,所以我们需要把信息传递进去,这里就是调用KernelLibIoControl。
具体的方法为:
KernelLibIoControl(hIsrHandler,IOCTL_GIISR_INFO,&giisr_info,
sizeof(GIISR_INFO), NULL, 0, NULL);
这里就是把 giisr_info 的内容传递给刚才注册的中断,giisr_info 是一个
GIISR_INFO的结构体,其内容如下:
typedef struct _GIISR_INFO {
      DWORD SysIntr;                    // SYSINTRfor ISR handler to return
(if associated device is asserting IRQ)
      BOOL CheckPort;                  // If true,check port to see if device is
asserting IRQ
      BOOL PortIsIO;                    // Port isIO port (possibly true only for
x86)
      BOOL UseMaskReg;                //  If  true,  read  fromMaskAddr  to
obtain mask
      DWORD PortAddr;                  // PortAddress
      DWORD PortSize;                  // Port datawidth in bytes
      DWORD Mask;                        //Mask  to  use  on  data  port  to
determine if device is asserting IRQ
      DWORD MaskAddr;                  //  Address  of  register  to  use  as
mask
} GIISR_INFO, *PGIISR_INFO;

这些成员都是需要设置的,具体含义如下
SysIntr:这个中断所对应的系统中断号,即第一步申请到的 SYSINTR,系统在
确定是当前的设备出发的 IRQ 之后会返回这个 SysIntr
CheckPort:一般为 TRUE,如果为 FALSE 则直接返回 SysIntr,而不是判断是
不是当前设备触发的中断
PortIsIO:是不是IO端口,这个可能只是在 x86 下使用,我们置为 FALSE
UseMaskReg:是否使用地址来获得 Mask,如果为TRUE,则Mask 字段无意义
PortAddr:实际上是可以判断中断是哪个设备出发的那个寄存器的地址
PortSize:PortAddr的位宽,标志PortAddr是1字节(BYTE),2 字节(WORD),
还是 4字节(DWORD)的寄存器,其他不支持
Mask:一个掩码位,在UseMaskReg为FALSE 的情况下与PortAddr的值进行
位或运算,如果不为0,则确定为当前设备触发的中断
MaskAddr:当 UseMaskReg为 TRUE 的时候,使用这个地址来获得掩码的值,
给动态的判断中断提供了接口
仔细看了上面各个成员的介绍,大家就应该可以了解 GIISR 是如何判断中断是不
是当前设备产生的。
所有的判断依据就是这个结构体。一般我们会将 CheckPort 置为 TRUE,然后让
系统去读取PortAddr地址处的值,这个值可以标志是否为当前设备触发的中断。
获得这个值以后,与一个 mask 值进行或运算(&),如果值不为 0,则认为是当前
设备触发的中断。这个 mask 值在 UseMaskReg 为 FALSE 时是成员 Mask,反
之是从 MaskAddr 地址处获得。



d)  下面就是启动IST,等待Event,这里和IST模型没有任何区别。

到这里全部的初始化就完成了,可以看出,和IST模型相比就是多了两步 b)和 c),
这两步决定了中断判断的方式,这也是 IISR的根本所在。

   
 中断的判断
下面详细介绍下可安装中断在 ISR 中被判断的过程。
同 样 , 中 断 到 来 时 进 入 的 函 数 是 OEMInterruptHandler , 在
OEMInterruptHandler中会调用NKCallIntChain来遍历该IRQ对应注册的IISR。
这是一个链表结构,所以对中断判断程序是一个顺序调用的过程,即先注册的设备先
判断,如果判断到正确的结果,则返回合法的 SYSINTR,OEMInterruptHandler
也同样返回这个值。所以即使后面的设备也符合条件,也不会被执行。如果整个链表
中都没找到正确的设备,则返回 SYSINTR_CHAIN。OEMInterruptHandler 在判
断到返回结果为SYSINTR_CHAIN 时,即表示请求中断的设备不在链表中。



 自定义的 IISR
  我们可以不使用GIISR,而自己实现IISR功能,只要知道了IISR的原理。

当以某一个寄存器或者地址的值,不足以判断到底是系统中哪个设备触发的中断
的时候,GIISR 就不是这么好用了。比如多个不同的外设,使用同一个GPIO 来触发
中断。外设需要读取多个寄存器或者需要一个复杂些的计算(不只是简单的一个&操作)
才能判定中断是否是其产生的。这时候我们需要使用自己的一套 IISR的处理方式。
当然我们不希望去改动整个微软对于 IISR 的处理结构,所以我们就要区分开来上
面介绍的 GIISR 的模型里,哪些是微软的架构,哪些是GIISR 自己的实现。

  微软的 IISR 架构:
a)  首先需要使用 LoadIntChainHandler去注册这个 IISR 的处理判断程序的
dll,在这个 dll中除了需要一个判断处理程序(也就是通过
LoadIntChainHandler传递进去的那个参数),还需要一个CreateInstance
的函数,这个是必须的。在你不改动微软内核的情况下,名字也是固定的,详
细地函数定义,可以参考 GIISR 的 CreateInstance。至于 IOControl,最
好也参考 GIISR 的定义一个,如果不需要去调用KernelLibIoControl 的话,
应该是可以不实现的。

b)  在OEMInterruptHandler中调用NKCallIntChain去遍历链表,在调用处理
函数时将CreateInstance 的返回值作为参数传递进去,如果处理函数在判断
到不是自己触发的中断,应该返回 SYSINTR_CHAIN,否则返回一个有意义
的 SYSINTR 值

c)  在需要注销这个IISR 的时候,调用FreeIntChainHandler,需要在dll中实
现 DestroyInstance 这个函数,被系统调用,这个是可选的。

GIISR 自己的实现:
使用同一套代码管理这些中断处理程序,每个中断处理程序在内部的数组中占用
一项,这些项目记录着中断处理程序激活使用的SYSINTR 以及判定其触发中断的标
准。这个标准就是读取某个寄存器或地址来用掩码来判断。同时这个数组项目对应的
结构体数据就是通过KernelLibIoControl 传递进去的。

  所以对于需要使用自己的特殊方式判断中断触发的程序,可以使用自己的中断判别
程序。
  我们可以为每一个中断外设都实现自己的处理dll。在调用LoadIntChainHandler
时传递进自定义的一个 dll 与处理函数,CreateInstance 一定要实现,不过返回值
可以忽略。
  在处理函数中,我们直接根据自己的外设来判断中断条件,然后返回相应的
SYSINTR。

  其实使用 CreateInstance的含义就是想把同一判断类型的设备使用一套统一的
处理函数来判断。CreateInstance 的返回值就是区别不同设备的这个 Index。

5 IISR资源的释放
当我们需要注销这个 IISR 的时候,需要调用 FreeIntChainHandler,来将该中
断服务从链表中删除。

FreeIntChainHandler(hIsrHandler);
hIsrHandler 就是LoadIntChainHandler 的返回值。
  

6 使用 IISR的注意事项
由于 IISR 是动态的被加载的,也就是说注册的 dll 会被加载到内核空间。所以不
能调用任何非内核的函数。
并且,如果将 IISR的 dll放在 bib文件的MODULES section里面,需要设置”K”
属性,如
giisr.dll           $(_FLATRELEASEDIR)\giisr.dll     NK   SHK

如果放在 FILES section 里面,需要保证没有 fixup类型的变量。
    
六  WinCE 中断的延迟
  1 造成中断延迟的原因

图 1 WinCE 中断体系结构

重新看图 1。这里画出了从硬件中断发生,到 IST 运行的全部过程,中间就是
我们要研究的延时部分。
图中的”IST 延迟”标志,实际上就是我们所说的中断延时。但是将 IST 延时细
分,可以分为如下几部分延时:
a)  ISR 延时
这部分延时是指从硬件触发中断到进入OEM的 ISR程序的时间,在 ARM 体系
下,就是进入OEMInterruptHandler 的时间。

b)  ISR 执行时间
在 OEMInterruptHandler 中判断 IRQ,并且返回相应的 SYSINTR 的时间,
也是OEM最主要把握的时间。
c)  从ISR 返回到 IST运行的时间
主要就是系统根据ISR 返回的SYSINTR激活相应的Event,系统调度运行,
到 IST执行的这段时间

2 如何降低中断延时
上面的中断延时原因中,a)是我们一般不去过问的部分,这段代码是微软实现
的,原则上是可以改的,但是收效不大,而且可能引入 bug,建议不要去动。c)是
我们可以部分控制的,关键就是IST的优先级,其他部分我们也使用微软默认的实
现。
b) 这 一 部 分 就 是 我 们 着 重 需 要 管 理 的 了 , 因 为 这 一 块 就 是
OEMInterruptHandler 函数的实现,是 OEM 自己实现的代码。也就是说,判断
中断源,返回SYSINTR的过程是我们唯一较可行的控制中断延时的地方。
关于这部分的优化,首先就是尽量用较简单的逻辑判断 IRQ,来返回相应的
SYSINTR,而且 OEMInterruptHandler 会调用NKCallIntChain,所以 IISR 的
链表长度与 IISR处理函数的效率也是影响中断延时的重要因素。
一般在判断 IRQ 的过程中,我们会把最可能出现、最频繁出现的IRQ 放在最前
面判断,比如系统时钟。这样在刚进入OEMInterruptHandler就可以判断到IRQ,
并返回,节省了时间,提高了效率。同样这种方法也适用于 IISR,将最可能出现,
最频繁出现的设备放在链表的前面,可以提高遍历的效率。


七  总结
WinCE 下提供了较灵活的中断处理方式,包括ISR,IST,IISR 三种主要方式。
对于 ARM 架构下的系统开发,我们常用的就是 IST 与 IISR 两种方式。两种方式
从本质上都是通过返回正确的SYSINTR来激活相应的 IST来服务中断。不同点只是对
IRQ 判断的方式,IST 是内置的 OEM 写死的判断程序,而 IISR 是通过一个预留的接
口,来动态注册判断程序,从而给了系统一个注册新中断的机会。同时 IISR 可以实现
中断共享,在一个 IRQ 上通过链表的方式不断添加判断程序,从而让多个设备共享同
一个 IRQ,同时又可以有自己独立的中断判断程序。
在最后,我们分析了中断延时的一些因素,这里并没有详细的进行分析,只是提出
了一些降低中断延时的方法。

参考文献:
1.  Microsoft Windows CE .NET中的中断体系结构    , Nat Frampton
2.  Platform Builder forMicrosoft Windows CE 5.0 Help, Microsoft
3.  Microsoft Windows Embedded CE5.0 source code, Microsoft

0 0