不抽烟,少喝酒,多运动,多思考,多努力

仅仅是为了记录自己想记下的一些东西,方便自己以后查看
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 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 +
                     ((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.....

posted on 2009-04-28 09:58  adward  阅读(527)  评论(0编辑  收藏  举报