看见论坛上有个朋友对WinCE上的Interrupt Handling不太了解,何牛又回帖说太忙。
Interrupt Handing,先要从Interrupt Installation谈起。
(以x86平台为例,arm我不会,sorry~)
在Kernel startup的过程中,会首先调用OEMInit(), 做OAL上的初始化,而这其中关于interrupt的部分又包括
OALIntrInit() // 将PIC上的物理IRQ与系统定义的SYSINTR_XXX相关联
x86InitPICs() // 调用HookInterrupt()
然后,interrupt就enable了。目前为止,还只是完成了Windows CE 3.0上的Interrupt
Installation,在目前的CE上,还支持动态的加载中断(Installable ISR, IISR,
我不知道怎么翻译好)以来支持不同的ISR共享同一根中断信号线。包含IISR的DLL通过调用kernel当中的
LoadIntChainHandler()来实现动态的加载中断, 而LoadIntChainHandler()最终会调用到
SC_LoadIntChainHandler()(PRIVATE\WINCEOS\COREOS\NK\KERNEL\Loader.c).
在SC_LoadIntChainHandler(
LPCWSTR lpszFileName, // 模块名
LPCWSTR lpszFunctionName, // ISR
BYTE bIRQ
),
每个IISR都需要提供CreateInstance()这样一个函数,因为在SC_LoadIntChainHandler()中,会调用
CreateInstance()以来得到ISR所需要的参数。然后通过HookIntChain()将ISR挂到对应的Irq的链表上
而HookIntChain() (PRIVATE\WINCEOS\COREOS\NK\KERNEL\kdriver.c)
其实也是个很简单的函数,Kernel维护着一张INTCHAIN
(PRIVATE\WINCEOS\COREOS\NK\KERNEL\kdriver.c)的表,以IRQ为索引,相同的IRQ又用链表的形式组织起
来,这样就把整个Kernel当中的IRQ和IISR组织了起来。
OK, 上面就是中断注册的全部过程
当一个中断发生时,OAL
ISR会根据IRQ得到对应的SYSINTR,具体的做法也很简单,就是调用NkCallIntChain()遍历上面提到的链表,如果有设备注册了该
IRQ号(或者说,该设备为此中断的中断源)那么就到用对应的IISR(Installable
ISR)。注意,因为是顺序遍历,所以早注册的IISR会首先得到处理。如果在所有注册的IISR上都不处理(NkCallIntChain()返回为
SYSINTR_CHAIN),那么OAL上的ISR会通过OALIntrTranslateIrq()将IRQ通过在OALIntrIrq()建立的静
态对应关系,来得到对应的SYSINTR。
OAL ISR在最后会关闭所有较低或者相等的IRQ号,并且向PIC中写入EOI表示ISR的处理结束
在HookInterrupt()(PRIVATE\WINCEOS\COREOS\NK\KERNEL\x86\sysinit.c)中,有这样的一段代码
if (pStub->pfnHandler == NULL) {
pStub->PushEax = 0x50;
pStub->Pushad = 0x60;
pStub->MovEsi = 0xBE;
pStub->pfnHandler = pfnHandler;
pStub->JmpNear = 0xE9;
pStub->JmpOffset = ((DWORD)&CommonIntDispatch) - ((DWORD)(&pStub->JmpOffset) + 4);
InitIDTEntry(hwInterruptNumber, KGDT_R0_CODE, pStub, INTERRUPT_GATE);
return(TRUE);
}
无疑的,这是一段初始化IDT条目的代码,上面的一堆赋值语句,初看觉得很奇怪,但是继续发掘CommonIntDispatch()下的代码,可以发现
call esi
mov ecx, dword ptr [pfnOEMIntrOccurs] // (ecx) = pfnOEMIntrOccurs
push eax // push argument == SYSINTR returned
call ecx // call pfnOEMintrOccurs
pop ecx // dummy pop
如果对汇编足够熟(熟到知道Pushad的机器码是60这样的程度)或者加上一点想象的话,那就不难发现,其实那段代码就是一段x86汇编的机器码
PUSH EAX;
PUSHAD
MOV ESI pfnHandler
JMP near offset &CommonIntDispatch //这里还有一个偏移没有计算。
但最后IDT中保存的entry就是这段machine code,也就是,换言之,如果执行中断的话,那就会执行这段代码从而调用commonIntDispatch()然后再从CommonIntDispatch中调用对应的OAL ISR得到相应的SYSINTR.
注意Kernel中的KDataStruct(private\winceos\coreos\nk\inc\nkx86.h)这个数据结构
其中的,LPEVENT alpeIntrEvents[SYSINTR_MAX_DEVICES];
就是将SYSINTR与EVENT关联起来的数组。
继续回到CommonIntDispatch()中来,就会发现kernel是靠一次RunThread()来唤醒IST的。挨下来的工作就是与线程的调度相关的了……
阅读(2627) | 评论(1) | 转发(0) |