windows中实际的中断处理是通过IoConnectInterrupt注册的。。。。
NTSTATUS
NTAPI
IoConnectInterrupt(OUT PKINTERRUPT *InterruptObject,
IN PKSERVICE_ROUTINE ServiceRoutine,
IN PVOID ServiceContext,
IN PKSPIN_LOCK SpinLock,
IN ULONG Vector,
IN KIRQL Irql,
IN KIRQL SynchronizeIrql,
IN KINTERRUPT_MODE InterruptMode,
IN BOOLEAN ShareVector,
IN KAFFINITY ProcessorEnableMask,
IN BOOLEAN FloatingSave)
{
PKINTERRUPT Interrupt;
PKINTERRUPT InterruptUsed;
PIO_INTERRUPT IoInterrupt;
PKSPIN_LOCK SpinLockUsed;
BOOLEAN FirstRun;
CCHAR Count = 0;
KAFFINITY Affinity;
PAGED_CODE();
/* Assume failure */
*InterruptObject = NULL;
/* Get the affinity */
Affinity = ProcessorEnableMask & KeActiveProcessors;/*获取CPU Affinity....*/
while (Affinity)/*在多处理平台上的ISR需要连接到Affinity对应的每个CPU上。。*/
{
/* Increase count */
if (Affinity & 1) Count++;/*计算需要处理的count数。。。。*/
Affinity >>= 1;
}
/* Make sure we have a valid CPU count */
if (!Count) return STATUS_INVALID_PARAMETER;
/* Allocate the array of I/O Interrupts */
IoInterrupt = ExAllocatePoolWithTag(NonPagedPool,
(Count - 1) * sizeof(KINTERRUPT) +
sizeof(IO_INTERRUPT),
TAG_KINTERRUPT);/*在非分页内存上为KINTERRUPT分配内存。。。*/
if (!IoInterrupt) return STATUS_INSUFFICIENT_RESOURCES;
/* Select which Spinlock to use */
SpinLockUsed = SpinLock ? SpinLock : &IoInterrupt->SpinLock; /*如果指定了SpinLock。则使用参数里的SpinLock,否则使用刚刚分配好的KINTERRUPT的SpinLock.....*/
/* We first start with a built-in Interrupt inside the I/O Structure */
*InterruptObject = &IoInterrupt->FirstInterrupt;
Interrupt = (PKINTERRUPT)(IoInterrupt + 1);
FirstRun = TRUE;
/* Start with a fresh structure */
RtlZeroMemory(IoInterrupt, sizeof(IO_INTERRUPT));
/* Now create all the interrupts */
Affinity = ProcessorEnableMask & KeActiveProcessors;
for (Count = 0; Affinity; Count++, Affinity >>= 1)/*循环处理该中断需要连接的处理器,然后连接中断。。。。*/
{
/* Check if it's enabled for this CPU */
if (Affinity & 1)
{
/* Check which one we will use */
InterruptUsed = FirstRun ? &IoInterrupt->FirstInterrupt : Interrupt;
/* Initialize it */
KeInitializeInterrupt(InterruptUsed,
ServiceRoutine,
ServiceContext,
SpinLockUsed,
Vector,
Irql,
SynchronizeIrql,
InterruptMode,
ShareVector,
Count,
FloatingSave); /*初始化KINTERRUPT...*/
/* Connect it */
if (!KeConnectInterrupt(InterruptUsed))/*初始化完后,这里建立实际的连接。。。*/
{
/* Check how far we got */
if (FirstRun)
{
/* We failed early so just free this */
ExFreePool(IoInterrupt);
}
else
{
/* Far enough, so disconnect everything */
IoDisconnectInterrupt(&IoInterrupt->FirstInterrupt);
}
/* And fail */
return STATUS_INVALID_PARAMETER;
}
/* Now we've used up our First Run */
if (FirstRun)
{
FirstRun = FALSE;
}
else
{
/* Move on to the next one */
IoInterrupt->Interrupt[(UCHAR)Count] = Interrupt++;
}
}
}
/* Return Success */
return STATUS_SUCCESS;
}
由此看出比较重要的实际上是KeInitializeInterrupt和KeConnectInterrupt这两个函数。。。。。
VOID
NTAPI
KeInitializeInterrupt(IN PKINTERRUPT Interrupt,
IN PKSERVICE_ROUTINE ServiceRoutine,
IN PVOID ServiceContext,
IN PKSPIN_LOCK SpinLock,
IN ULONG Vector,
IN KIRQL Irql,
IN KIRQL SynchronizeIrql,
IN KINTERRUPT_MODE InterruptMode,
IN BOOLEAN ShareVector,
IN CHAR ProcessorNumber,
IN BOOLEAN FloatingSave)
{
ULONG i;
PULONG DispatchCode = &Interrupt->DispatchCode[0], Patch = DispatchCode;
/*每个KIINTERRUPT的实际入口就是这个DispatchCode数组。.这个数组是中断处理的入口代码汇编后形成的机器码。。。。*/
/* Set the Interrupt Header */
Interrupt->Type = InterruptObject;
Interrupt->Size = sizeof(KINTERRUPT);
/* Check if we got a spinlock */
if (SpinLock)/*设置此中断对象的自旋锁。。。*/
{
Interrupt->ActualLock = SpinLock;
}
else
{
/* This means we'll be usin the built-in one */
KeInitializeSpinLock(&Interrupt->SpinLock);
Interrupt->ActualLock = &Interrupt->SpinLock;
}
/* Set the other settings */
Interrupt->ServiceRoutine = ServiceRoutine;/*这里初始化各个域*/
Interrupt->ServiceContext = ServiceContext;
Interrupt->Vector = Vector;
Interrupt->Irql = Irql;
Interrupt->SynchronizeIrql = SynchronizeIrql;
Interrupt->Mode = InterruptMode;
Interrupt->ShareVector = ShareVector;
Interrupt->Number = ProcessorNumber;
Interrupt->FloatingSave = FloatingSave;
Interrupt->TickCount = (ULONG)-1;
Interrupt->DispatchCount = (ULONG)-1;
/* Loop the template in memory */
for (i = 0; i < KINTERRUPT_DISPATCH_CODES; i++)/*这里将汇编代码KiInterruptTemplate的机器指令复制到DispatchCode....注意。。这里很重要。。。*/
{
/* Copy the dispatch code */
*DispatchCode++ = KiInterruptTemplate[i];
}
/* Sanity check */
ASSERT((ULONG_PTR)&KiChainedDispatch2ndLvl -
(ULONG_PTR)KiInterruptTemplate <= (KINTERRUPT_DISPATCH_CODES * 4));
/* Jump to the last 4 bytes */
Patch = (PULONG)((ULONG_PTR)Patch +
((ULONG_PTR)&KiInterruptTemplateObject -
(ULONG_PTR)KiInterruptTemplate) - 4); /*注意..KiInterruptTemplate只是一个模板而已。。。就是一个框架。所以这里需要移动到最后的4个字节。。。来将实际的中断服务函数的地址写在这里。。。。。这样。。。就实现了对具体中断服务的跳转。。。*/
/* Apply the patch */
*Patch = PtrToUlong(Interrupt); /*这里就是将KIINTERRUPT的地址写入中断处理模板的最后4字节。。。。*/
/* Disconnect it at first */
Interrupt->Connected = FALSE;
}
现在来看看KiInterruptTemplate部分的代码。。就清楚了。。。KiInterruptTemplate的代码在ntoskrnl/ke/i386/Traps.s里。。。。
.func KiInterruptTemplate
_KiInterruptTemplate:
/* Enter interrupt trap */
INT_PROLOG kit_a, kit_t, DoPushFakeErrorCode
_KiInterruptTemplate2ndDispatch:
/* Dummy code, will be replaced by the address of the KINTERRUPT */
mov edi, 0
_KiInterruptTemplateObject:/*这条跳转指令的地址在KeConnectInterrupt函数里会被替换成实际的转入中断处理的函数的地址。。。*/
/* Dummy jump, will be replaced by the actual jump */
jmp _KeSynchronizeExecution@12
_KiInterruptTemplateDispatch:
/* Marks the end of the template so that the jump above can be edited */
TRAP_FIXUPS kit_a, kit_t, DoFixupV86, DoFixupAbios
.endfunc
/*因为
Patch = (PULONG)((ULONG_PTR)Patch +所以这里之后Patch对应的就是mov edi,0这条指令的"0"这个立即数了。。。。。所以执行替换后。。这条指令就变成了mov edi,PKiInterrupt了。。*/
接下来分析另外一个重要的KeConnectInterrupt函数。。
BOOLEAN
NTAPI
KeConnectInterrupt(IN PKINTERRUPT Interrupt)
{
BOOLEAN Connected, Error, Status;
KIRQL Irql, OldIrql;
UCHAR Number;
ULONG Vector;
DISPATCH_INFO Dispatch;
/* Get data from interrupt */
Number = Interrupt->Number;
Vector = Interrupt->Vector;
Irql = Interrupt->Irql;
/* Validate the settings */
if ((Irql > HIGH_LEVEL) ||
(Number >= KeNumberProcessors) ||
(Interrupt->SynchronizeIrql < Irql) ||
(Interrupt->FloatingSave))
{
return FALSE;
}
/* Set defaults */
Connected = FALSE;
Error = FALSE;
/* Set the system affinity and acquire the dispatcher lock */
KeSetSystemAffinityThread(1 << Number);
OldIrql = KiAcquireDispatcherLock();
/* Check if it's already been connected */
if (!Interrupt->Connected)
{
/* Get vector dispatching information */
KiGetVectorDispatch(Vector, &Dispatch); /*这里获取Vector这个中断号对应的中断分发信息。。。。比如此中断上是否已经连接了中断服务。。。还有此中断是共享的还是单独的。。。。*/
/* Check if the vector is already connected */
if (Dispatch.Type == NoConnect)/*如果还没有任何ISR连接到这个中断号上。。。。*/
{
/* Do the connection */
Interrupt->Connected = Connected = TRUE;
/* Initialize the list */
InitializeListHead(&Interrupt->InterruptListEntry);
/* Connect and enable the interrupt */
KiConnectVectorToInterrupt(Interrupt, NormalConnect);/*这里连接中断服务。。。*/
Status = HalEnableSystemInterrupt(Vector, Irql, Interrupt->Mode);/*调用HAL打开这个中断号对应的中断,因为未使用的中断都是被屏蔽的。。。*/
if (!Status) Error = TRUE;
}
else if ((Dispatch.Type != UnknownConnect) &&
(Interrupt->ShareVector) &&
(Dispatch.Interrupt->ShareVector) &&
(Dispatch.Interrupt->Mode == Interrupt->Mode))
{
/* The vector is shared and the interrupts are compatible */
ASSERT(FALSE); // FIXME: NOT YET SUPPORTED/TESTED
Interrupt->Connected = Connected = TRUE;
ASSERT(Irql <= SYNCH_LEVEL);
/* Check if this is the first chain */
if (Dispatch.Type != ChainConnect)/*如果此中断是共享的。。并且还没有建立共享中断需要的入口点。。。。则建立。。。*/
{
/* Setup the chainned handler */
KiConnectVectorToInterrupt(Dispatch.Interrupt, ChainConnect);
}
/* Insert into the interrupt list */
InsertTailList(&Dispatch.Interrupt->InterruptListEntry,
&Interrupt->InterruptListEntry);/*将刚才建立的中断连接入此中断号对应的队列。。。*/
}
}
/* Unlock the dispatcher and revert affinity */
KiReleaseDispatcherLock(OldIrql);
KeRevertToUserAffinityThread();
/* Check if we failed while trying to connect */
if ((Connected) && (Error))/*如果出错了。。。*/
{
DPRINT1("HalEnableSystemInterrupt failed\n");
KeDisconnectInterrupt(Interrupt);
Connected = FALSE;
}
/* Return to caller */
return Connected;
}
实际的连接函数是KiConnectVectorToInterrupt..
VOID
NTAPI
KiConnectVectorToInterrupt(IN PKINTERRUPT Interrupt,
IN CONNECT_TYPE Type)
{
DISPATCH_INFO Dispatch;
PKINTERRUPT_ROUTINE Handler;
PULONG Patch = &Interrupt->DispatchCode[0];
/* Get vector data */
KiGetVectorDispatch(Interrupt->Vector, &Dispatch); /*获取中断号对应的分发信息。。。*/
/* Check if we're only disconnecting */
if (Type == NoConnect)/*如果是要取消此中断服务。。。。则设置Handler为Dispatch.NoDispath...也就是最开始的默认中断处理。。。。。就是简单的打印一些调试信息而已。。。*/
{
/* Set the handler to NoDispatch */
Handler = Dispatch.NoDispatch;
}
else
{
/* Get the right handler */
Handler = (Type == NormalConnect) ?
Dispatch.InterruptDispatch:
Dispatch.ChainedDispatch;/*有两种类型的中断。。。。共享的和独立的。。共享的中断处理需要循环注册了的中断服务。。。如果是独立的中断。。。则直接调用注册了的中断服务就行了。。。。所以这里的Handler是不一样的。。*/
ASSERT(Interrupt->FloatingSave == FALSE);
/* Set the handler */
Interrupt->DispatchAddress = Handler; /*将Handler的地址写入DispatchAddress....*/
/* Jump to the last 4 bytes */
Patch = (PULONG)((ULONG_PTR)Patch +
((ULONG_PTR)&KiInterruptTemplateDispatch -
(ULONG_PTR)KiInterruptTemplate) - 4);
/* Apply the patch */
*Patch = (ULONG)((ULONG_PTR)Handler - ((ULONG_PTR)Patch + 4)); /*这里将KiInterruptTemplateDispatch那里的jmp指令的跳转地址改写为Handler....至于这里为什么要用Handler的地址减去Patch再加4...那是因为这里是相对地址的跳转。所以是从当前指令来偏移的。。。。*/
/* Now set the final handler address */
ASSERT(Dispatch.FlatDispatch == NULL);
Handler = (PVOID)&Interrupt->DispatchCode;
}
/* Set the pointer in the IDT */
((PKIPCR)KeGetPcr())->IDT[Interrupt->Vector].ExtendedOffset =
(USHORT)(((ULONG_PTR)Handler >> 16) & 0xFFFF);
((PKIPCR)KeGetPcr())->IDT[Interrupt->Vector].Offset =
(USHORT)PtrToUlong(Handler);/*好了。。这里改写相应的IDT......这里一改写。。。那真正的中断处理也就连接好了。。。。。。。。。大功告成了。。*/
}
现在再来看看共享中断和非共享中断的处理吧。。。。。。先看非共享中断的处理。。。。
func KiInterruptDispatch@0
_KiInterruptDispatch@0:
/* Increase interrupt count */
inc dword ptr PCR[KPCR_PRCB_INTERRUPT_COUNT] /*递增PCR的中断计数器。。。。*/
/* Save trap frame */
mov ebp, esp /*保存TRAP FRAME的指针。。。。。*/
/* Save vector and IRQL */
mov eax, [edi+KINTERRUPT_VECTOR]/*在跳转到这里之前。。。edi已经是指向了KIINTERRUPT了。。。这里将中断向量号存入eax........*/
mov ecx, [edi+KINTERRUPT_SYNCHRONIZE_IRQL]/*这里将IRQL写入ecx.....*/
/* Save old irql */
push eax
sub esp, 4
/* Begin interrupt */
push esp
push eax
push ecx
call _HalBeginSystemInterrupt@12 /*这个函数前面已经分析过了。。。*/
/* Check if it was handled */
or al, al
jz SpuriousInt
/* Acquire the lock */
GetIntLock:
mov esi, [edi+KINTERRUPT_ACTUAL_LOCK]
ACQUIRE_SPINLOCK(esi, IntSpin) /*获取自旋锁。。。。。*/
/* Make sure that this interrupt isn't storming */
VERIFY_INT kid
/* Save the tick count */
mov ebx, _KeTickCount
/* Call the ISR */
mov eax, [edi+KINTERRUPT_SERVICE_CONTEXT]
push eax
push edi
call [edi+KINTERRUPT_SERVICE_ROUTINE] /*这里调用具体的中断服务。。。。*/
/* Check if the ISR timed out */
add ebx, _KiISRTimeout/*检测ISR的处理是否超时。。。ReactOS设置了最大的ISR处理时间为55个tick......如果超过了55个tick..则说明ISR有问题了。。。。*/
cmp _KeTickCount, ebx
jnc IsrTimeout
ReleaseLock:
/* Release the lock */
RELEASE_SPINLOCK(esi)
/* Exit the interrupt */
INT_EPILOG 0
SpuriousInt:
/* Exit the interrupt */
add esp, 8
INT_EPILOG 1
#ifdef CONFIG_SMP
IntSpin:
SPIN_ON_LOCK(esi, GetIntLock)
#endif
IsrTimeout:
/* Print warning message */
push [edi+KINTERRUPT_SERVICE_ROUTINE]
push offset _IsrTimeoutMsg
call _DbgPrint
add esp,8
/* Break into debugger, then continue */
int 3
jmp ReleaseLock
/* Cleanup verification */
VERIFY_INT_END kid, 0
.endfunc
好了。。到这里具体的中断处理的注册过程就分析完了。。。现在来总结下。。。。
Windows为驱动开发提供的接口是IoConnectInterrupt 函数。。。。。。ReactOS的实现是先调用KeInitializeInterrupt初始化中断对象KIINTERUPT....。。此过程将KiInterruptTemplate....的那条指令mov edi,0这条指令的寻址地址改写为中断对象的地址。。。然后调用KeConnectInterrupt来建立实际的连接,这里根据中断的类型是为共享的还是非共享的。。。。改写KiInterruptTemplate后的跳转指令的跳转地址。。。。。。也就是跳转到实际的处理函数。。然后如果是第一次初始化。。。则还要改写IDT.....