ReactOS 中断处理之连接实际的中断处理转

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 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,DoFixupV86,DoFixupAbios
.endfunc

/*因为

Patch = (PULONG)((ULONG_PTR)Patch +
((ULONG_PTR)&KiInterruptTemplateObject -
(ULONG_PTR)KiInterruptTemplate) - 4);

所以这里之后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.....

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...