前一段时间研究了一下WinCE下的中断结构,整理了一下,希望与大家讨论。
最下面有PDF版本下载,便于保存
版权申明:本文版权归ARMCE所有,转载请保留所有原文内容及 ARMCE标识并注明出
自 ARMCE,禁止任何未经作者同意的内容修改及再发布,ARMCE保留所有权利。
Windows Embedded CE 中断结构分析
关键字:WinCE,中断,体系,结构
摘要:本文主要以WinCE .NET 5.0 为操作系统平台,ARM为硬件平台,分析了WinCE下
中断的结构与实现方式
作者:ETDrawer@ARMCE
前言
在嵌入式系统当中,对于中断的处理是非常重要的一部分内容。许多外围设备都需要通
过中断来实现自己的功能或者与系统内核交互,系统时钟本身也是由时钟中断产生的。所以
本文旨在分析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 service routine), 中断处理程序。
WinCE实际上使用 ISR来处理中断,即默认的中断入口函数,在 ARM体系中,系统默认的
ISR就是 OEMInterruptHandler
IST:
IST (Interrupt service thread), 中断服务线程。
在 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 为系统中断号,也就是调用返回的结果存放的位置
/* ARMCE深入
实际这个 IOControl调用的是函数 OALIoCtlHalRequestSysIntr,该函数的位置
在
WINCE500PLATFORMCOMMONSRCCOMMONINTRCOMMONIoctl.c
最终调用 OALIntrRequestSysIntr,这个由 OEM 自己实现,一般就是维护了 2
张表,一张是从 SYSINTR 到 IRQ 号码的映射表,另一张是 IRQ 到 SYSINTR 的
映射表,两者是对应的关系。
这里注意一点,一个IRQ 映射为多个SYSINTR 是支持的,也就是共享中断,但是
系统默认的实现并没有支持一个 SYSINTR 映射为多个 IRQ,你可以自己实现。
这两张表对于后面在OEMInterruptHandler中由IRQ查找SYSINTR提供了方便
*/
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
/* ARMCE深入
InterruptInitialize 的 代 码 参 考 SC_InterruptInitialize , 在
E:WINCE500PRIVATEWINCEOSCOREOSNKKERNELIntrapi.c中
主要做两件事情:
#1 将 Event 的 Ptr 填入一个数组,这个数组是记录每个 SYSINTR 对应激活的
Event
#2 调用OEMInterruptEnable,使能SYSINTR所对应的IRQ,并且将标志 IRQ
被引用次数的变量加1(WinCE6 的代码中未见这一变量)
*/
d) 创建一个 IST,并且等待hISTEvent
到了这一步,中断关于系统方面的初始化基本结束,剩下的就是创建一个 IST,然
后等待 Event来运行中断服务代码,例如:
while(TRUE) {
WaitForSingleObject(hISTEvent,INFINITE) ==
WAIT_OBJECT_0)
…
}
这里需要注意的是IST什么时候创建都可以,但是在InterruptInitialize之前不要
运行 IST 以等待这个 Event,也就是说在 InterruptInitialize 之前不要使用这个
Event,否则会导致InterruptInitialize失败。
还有就是不要使用WaitForMultipleObjects来等待Event。
在中断处理完成之后需要调用 InterruptDone,参数为该中断的SYSINTR。来通
知系统,中断处理完成,系统重新使能该中断
到了这里,驱动的中断初始化工作就全部完成了。
2 OEM层需要做的工作
OEM 层 主 要 是 控 制 IRQ 的 enable (BSPIntrEnableIrq) 与 disable
(BSPIntrDisableIrq), 当然要初始化 IRQ 的配置,使其在正确的触发状态,比如上升延
触发
至此一个中断处理的IST模型就实现了,系统在IRQ 触发时调用映射函数,获得相应
IRQ 的 SYSINTR,然后返回合法的SYSINTR给系统,系统查表激活相应的Event,对应
的 IST进行中断服务,然后再次等待 Event。
3 中断资源的释放
当不需要当前中断继续服务的时候可以通过调用 KernelIoControl 来释放申请到的
SYSINTR,具体格式为:
KernelIoControl(IOCTL_HAL_RELEASE_SYSINTR, dwSysIntr, sizeof(DWORD),
NULL, 0, NULL);
其中 dwSysIntr 就是需要释放的 SYSINTR 号码。
/* ARMCE深入
这里实际调用的是OALIntrReleaseSysIntr这个函数,由OEM自己实现。所做的动作也
很简单,就是从SYSINTR 到 IRQ 和 IRQ 到 SYSINTR 的那两个映射关系数组中删除映射
关系。
*/
五 可安装的 ISR
1 为什么要使用可安装 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 的模块,源程序可以在WINCE500PUBLICCOMMONOAKDRIVERSGIISR
找到。熟悉了 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);
/* ARMCE深入
LoadIntChainHandler的源代码可以在WINCE500PRIVATEWINCEOS
COREOSNKKERNELLoader.c中找到,就是函数SC_LoadIntChainHandler
WinCE6 在 KDriver.c 中的 NKLoadIntChainHandler,两者功能大致相同,只
是在一些结构体定义上略有不同
其主要功能就是加载 dll 到 NK,并且获得三个函数的指针,一个是
CreateInstance,一个是你刚才传进来的处理函数,这里就是 ISRHandler,还
一个就是 IOControl,后面会用到。
首先调用CreateInstance 来获得一个实例的数据,这个数据就是一个 index,用
来标示其中的一个中断处理程序的索引。
这里可能需要解释下 GIISR 的处理原理。我们所有的可安装中断都通过 giisr.dll
里面的 ISRHandler来处理,在NKCallIntChain被调用的时候,会遍历所有注册
到这个 IRQ 的中断处理函数,这里全部都是同一个函数 ISRHandler。那么就需
要可以区分每一次调用,所以就在 giisr 模块里面维护一个数组,每一个中断服务
程序占用一个数组成员,这些数组成员的Index就是他们在giisr里面的唯一标识。
所以 CreateInstance 的任务就是查找数组,找到第一个空闲位置,将 Index 返
回。在ISRHandler 被调用到的时候,会将这个Index传递进去,根据这个 Index,
ISRHandler 就能够知道是数组中哪个成员正在被查询,进而确定是不是这个成员
需要处理中断请求,进而确定该返回的 SYSINTR。详细的步骤下面会一一说明,
大家先有一个概念
在从 CreateInstance 返回了可用的数组 Index 之后,调用 HookIntChain,此
函数在Kdriver.c中。这个函数的功能比较简单,我们先了解下共享中断在系统中
的处理。
前面有所提到,在调用 NKCallIntChain 时会遍历一个链表,每个链表头就是系统
维护的一个数组中的一个成员,每一个 IRQ 号码都对应数组中的一个成员,这个
数组就是 pIntChainTable。IRQ 按照其号码在数组中对应相应的数组成员,注意
pIntChainTable 是一个 256 个成员的数组,这也就意味着你的 IRQ 号码数字不
能超过 255,当然这是指你传递进来的 IRQ 号码,如果你的 IRQ 号码都是大于
255 的,可以对实际 IRQ 号码做处理,保证其数字值在 0-255,比如对 256 取模,
这样当然就可以传递进来了。
pIntChainTable中的每个元素都是一个链表的头,当你向一个IRQ 添加中断处理
的时候,实际是建立了一个新的PINTCHAIN元素,然后向pIntChainTable中的
IRQ 索引位置去添加,如果该位置不为空,则查找这个元素指向的下一个元素,在
这个单向链表的操作下,将新的中断处理程序加入。
这个加入工作就是HookIntChain做的
*/
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; // SYSINTR for 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 is IO port (possibly true only for
x86)
BOOL UseMaskReg; // If true, read from MaskAddr to
obtain mask
DWORD PortAddr; // Port Address
DWORD PortSize; // Port data width 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 地址处获得。
/* ARMCE深入
KernelLibIoControl 的 源 代 码 可 以 在 Kdriver.c 中 找 到 , 就 是
SC_KernelLibIoControl 。 SC_KernelLibIoControl 直 接 调 用 的
NKKernelLibIoControl,这个函数同样在 kdriver.c 中。分析源码可知,就是调
用了 giisr.dll 中的 IOControl,参数为 IOCTL_GIISR_INFO,就是传递进去了
一个 GIISR_INFO 结构体,用来给 Index 标示的数组元素赋值,这个 Index 就
是前面CreateInstance返回的那个数值,通过 hIsrHandler传递进去。
*/
d) 下面就是启动IST,等待Event,这里和IST模型没有任何区别。
到这里全部的初始化就完成了,可以看出,和IST模型相比就是多了两步 b)和 c),
这两步决定了中断判断的方式,这也是 IISR的根本所在。
3 中断的判断
下面详细介绍下可安装中断在 ISR 中被判断的过程。
同 样 , 中 断 到 来 时 进 入 的 函 数 是 OEMInterruptHandler , 在
OEMInterruptHandler中会调用NKCallIntChain来遍历该IRQ对应注册的IISR。
这是一个链表结构,所以对中断判断程序是一个顺序调用的过程,即先注册的设备先
判断,如果判断到正确的结果,则返回合法的 SYSINTR,OEMInterruptHandler
也同样返回这个值。所以即使后面的设备也符合条件,也不会被执行。如果整个链表
中都没找到正确的设备,则返回 SYSINTR_CHAIN。OEMInterruptHandler 在判
断到返回结果为SYSINTR_CHAIN 时,即表示请求中断的设备不在链表中。
/* ARMCE深入
NKCallIntChain 的代码在 KDriver.c 中,是个非常简单的函数,以传递进来的
IRQ 为 Index 找到链表的头,然后去调用各链表节点的 hIsrHandler,并且以
CreateInstance 的返回值为参数。当有返回的值不是 SYSINTR_CHAIN 时,就返
回 这个值, 反之继续 查找, 直到链表 尾部, 如果还没 有找到, 就返 回
SYSINTR_CHAIN。
*/
4 自定义的 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 的返回值。
/*ARMCE 深入
FreeIntChainHandler 函数实现同样在 loader.c 中的SC_FreeIntChainHandler。
所做的动作就是,如果有 DestroyInstance 函数,则调用 DestroyInstance。然后
调用 UnhookIntChain。UnhookIntChain的函数实现在 kdriver.c 中,是一个标准
的单向链表删除其中一个元素的操作,这里不再详细解释。
*/
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 for Microsoft Windows CE 5.0 Help, Microsoft
3. Microsoft Windows Embedded CE 5.0 source code, Microsoft