当IRQL下降时,Windows中是如何触发软件中断的?

问题描述 投票:1回答:1

我知道对于硬件中断,当KeAcquireInterruptSpinLock调用KeLowerIrql时,HAL调整LAPIC中的中断屏蔽,这将允许自动服务排队中断(可能在IRR中)。但是,对于软件中断,例如,ntdll.dll sysenter调用SSDT NtXxx系统服务,当IRQL进入被动级别时,它们如何“推迟”并触发?同样适用于DPC调度程序软件中断(如果DPC用于当前CPU和高优先级),IRQL <Dispatch IRQL时是如何触发的?软件中断是否在SSDT中的所有循环中调用函数(NtXxx),即

while (irql != passive)

懒惰IRQL的问题完全相同:

由于访问PIC的操作相对较慢,因此需要访问I / O总线以更改IRQL的HAL(例如PIC和32位高级配置和电源接口(ACPI)系统)实现了性能优化,称为惰性IRQL,这可以避免PIC访问。当IRQL被引发时,HAL会在内部注意到新的IRQL,而不是更改中断屏蔽。如果随后发生优先级较低的中断,则HAL将中断屏蔽设置为适合第一个中断的设置,并且在IRQL降低之前不会停止低优先级中断(从而保持中断挂起)。因此,如果在IRQL引发时没有发生低优先级中断,则HAL不需要修改PIC。

它如何保持这个中断未决?是否只是循环一个条件,直到更高优先级的ISR降低IRQL,并且当线程被安排进去时,最终会满足条件?它就这么简单吗?

编辑:我必须错过这里的东西,因为假设设备IRQL的ISR使用IoRequestDpc请求DPC,如果它是高优先级DPC并且目标是当前处理器则它调度DPC / Dispatch级别的中断以消耗处理器的DPC队列。这一切都发生在设备IRQL(DIRQL)的ISR中,这意味着具有Dispatch / DPC IRQL级别的软件中断将在KeAcquireInterruptSpinLock中旋转我认为因为当前的IRQL太高,但它不会永远在那里旋转因为在ISR返回之后调用降低IRQL的实际例程意味着它将停留在设备IRQL的ISR中等待该软件中断,这需要IRQL <Dispatch / DPC IRQL(2),不仅如此,调度员将无法调度下一个线程,因为调度DPC在Dispatch / DPC IRQL级别运行,该级别要低得多。我能想到一个解决方案。

1)ISR将KDPC对象返回到KiInterruptDispatch,以便它知道DPC的优先级,然后在使用KeReleaseInterruptSpinLock降低IRQL后自行调度,但KSERVICE_ROUTINE仅返回不相关的布尔值,因此排除了这一点。

有谁知道如何避免这种情况?

编辑2:也许它会生成一个新线程阻塞等待IRQL <Dispatch IRQL,然后从ISR返回并丢弃IRQL。

windows kernel interrupt
1个回答
3
投票

这是在任何来源都没有明确解释的事情,有趣的是,second comment也提出了同样的问题。

在研究了ReactOS内核和WRK后,我现在确切知道会发生什么

当驱动程序从PnP管理器接收IRP_MN_START_DEVICE时,使用IoConnectInterrupt使用它在IRP中接收的CM_RESOURCE_LIST中的数据初始化中断对象。特别感兴趣的是由PnP管理器分配给设备的向量和亲和性(如果设备在其PCIe配置空间中公开MSI功能,则很容易做到,因为它不必担心底层IRQ路由)。它传递向量,指向ISR的指针,ISR的上下文,IRQL到IoConnectInterrupt,它调用KeInitializeInterrupt使用参数初始化中断对象,然后调用KeConnectInterrupt,它将当前线程的亲和力切换到目标处理器,锁定调度程序数据库并检查该IDT条目是否指向BugCheck包装器KxUnexpectedInterrupt0[IdtIndex]。如果是,那么它将IRQL提升到31,因此以下是原子操作,并使用HAL API启用由LAPIC上的PnP管理器映射的向量,并为其分配与IRQL对应的TPR优先级。然后,它将向量映射到ID矢量中的IDT条目中的处理程序地址。为此,它将地址&Interrupt->DispatchCode[0]传递给IDT映射例程KeSetIdtHandlerAddress。看来这是一个template,对于所有中断对象都是一样的,根据WRK是KiInterruptTemplate。果然,检查ReactOS内核,我们在KeInitializeInterrupt中看到 - 由IoConnectInterrupt调用 - 代码:

 RtlCopyMemory(Interrupt->DispatchCode,
               KiInterruptDispatchTemplate,
               sizeof(Interrupt->DispatchCode));

KiInterruptDispatchTemplate现在似乎是空白,因为ReactOS的amd64端口正处于早期开发阶段。在Windows上,它将被实现,并作为KiInterruptTemplate

然后它将IRQL降低到旧的IRQL。如果IDT条目没有指向BugCheck ISR,那么它会初始化一个链接中断 - 因为IDT条目已经有一个地址。它使用CONTAINING_RECORD通过其成员获取中断对象,处理程序的地址(DispatchCode[0])并将新的中断对象连接到已存在的对象,初始化已经引用的中断对象的LIST_ENTRY作为列表的头部并将其标记为通过将DispatchAddress成员设置为KiChainedDispatch的地址来链接中断。然后它丢弃调度程序数据库spinlock并切换关联并返回中断对象。

然后,驱动程序使用DeferredRoutine为设备对象设置一个DPC - 使用IoInitializeDpcRequest作为成员。

FORCEINLINE VOID IoInitializeDpcRequest ( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIO_DPC_ROUTINE DpcRoutine )
    KeInitializeDpc(&DeviceObject->Dpc,
               (PKDEFERRED_ROUTINE) DpcRoutine,
               DeviceObject);

KeInitializeDpc调用KiInitializeDpchard-coded将优先级设置为medium,这意味着KeInsertQueueDpc将把它放在DPC队列的中间。调用后可以使用KeSetImportanceDpcKeSetTargetProcessorDpc来分别设置生成优先级和目标处理器的返回DPC。它将DPC对象复制到设备对象的成员,如果已有DPC对象,则将其排队到已存在的DPC。

当中断发生时,中断对象的KiInterruptTemplate模板是被调用的IDT中的地址,然后是call the real interrupt dispatcher,这是DispatchAddress成员,对于正常中断将是KiInterruptDispatch,对于链接中断将是KiChainedDispatch。它将中断对象传递给KiInterruptDispatch(它可以这样做,因为正如我们之前看到的,RtlCopyMemoryKiInterruptTemplate复制到中断对象中,这意味着它可以使用带有相对RIP的asm块来获取它所属的中断对象的地址to(它也可以尝试用CONTAINING_RECORD函数做一些事情)但是intsup.asm包含以下代码来执行它:lea rbp, KiInterruptTemplate - InDispatchCode ; get interrupt object address jmp qword ptr InDispatchAddress[rbp]; finish in common code)。然后KiInterruptDispatch将获得中断的自旋锁,可能使用KeAcquireInterruptSpinLock。 ISR(ServiceContext)使用为设备和ISR创建的设备对象地址调用IoRequestDpc,作为参数,以及特定于中断的上下文和可选的IRP(我猜它是从DeviceObject->Irp的头部获取的,如果例程是为了处理IRP)。我希望它是KeInsertQueue的单行包装器,但是传递设备对象的Dpc成员,而这正是它的原样:KeInsertQueueDpc(&DeviceObject->Dpc, Irp, Context);。首先,KeInsertQueue将IRQL从ISR的设备IRQL提升到31,从而阻止所有抢占。 WRKdpcobj.c的第263行包含以下内容:

#if !defined(NT_UP)

    if (Dpc->Number >= MAXIMUM_PROCESSORS) {
        Number = Dpc->Number - MAXIMUM_PROCESSORS;
        TargetPrcb = KiProcessorBlock[Number];

    } else {
        Number = CurrentPrcb->Number;
        TargetPrcb = CurrentPrcb;
    }

这表明DPC->Number成员必须由KeSetTargetProcessorDpc设置为目标核心数+最大处理器。这是奇怪的,当然我去看了ReactOS的KeSetTargetProcessorDpc,它确实! KiProcessorBlock似乎是一个内核结构,用于快速访问每个内核的KPRCB结构。

然后使用DpcData = KiSelectDpcData(TargetPrcb, Dpc)获取核心的正常DPC队列自旋锁,返回&Prcb->DpcData[DPC_NORMAL]作为它传递给它的DPC的类型是正常的,而不是线程。然后它获取队列的自旋锁,这似乎是ReactOS上的一个空函数体,我认为这是因为:

/ *在UP版本中,IRQL> = DISPATCH * /中不存在自旋锁

这是有道理的,因为ReactOS只支持1个核心意味着另一个核心上没有可以访问DPC队列的线程(核心可能有一个目标DPC用于该核心的队列)。只有一个DPC队列。如果它是一个多核系统,它将不得不获得自旋锁,因此这些看起来是占位符,用于实现多核功能。如果它无法为DPC队列获取自旋锁,那么它将在IRQL 31处自旋等待或者下降到中断本身和spinwait的IRQL,允许其他中断发生在核心但没有其他线程在核心上运行。

请注意,windows将使用KeAcquireSpinLockAtDpcLevel来获取此自旋锁,而ReactOS则不会。 KeAcquireSpinLockAtDpcLevel does not touch the IRQL。虽然,在WRK中,它直接使用KiAcquireSpinLock,这可以在dpcobj.c的第275行看到,它只获得自旋锁并且对IRQL没有任何作用(KiAcquireSpinLock(&DpcData->DpcLock);)。

在获得自旋锁之后,它首先确保DPC对象尚未在队列中(当DpcData成员使用从cmpxchg返回的DpcData进行初始化时,KiSelectDpcData(TargetPrcb, Dpc)成员将为null)并且如果是,则丢弃自旋锁并返回;否则,它会将DPC成员设置为指向已传递的中断特定上下文,然后根据其优先级(InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);)将其插入到头部(InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);)或尾部(if (Dpc->Importance == HighImportance))的队列中。然后确保DPC没有执行if (!(Prcb->DpcRoutineActive) && !(Prcb->DpcInterruptRequested))。然后它检查KiSelectDpcData是否返回第二个KDPC_DATA结构,即DPC是螺纹类型(if (DpcData == &TargetPrcb->DpcData[DPC_THREADED])),如果它是和if ((TargetPrcb->DpcThreadActive == FALSE) && (TargetPrcb->DpcThreadRequested == FALSE))然后它做一个锁定的xchg分别将TargetPrcb->DpcSetEventRequest设置为true然后它将TargetPrcb->DpcThreadRequestedTargetPrcb->QuantumEnd设置为true并且它设置如果目标PRCB是当前PRCB,则RequestInterrupt为true,否则仅在目标核心不空闲时才将其设置为true。

现在是原始问题的症结所在。 WRK现在包含以下代码:

#if !defined(NT_UP)

            if (CurrentPrcb != TargetPrcb) {
                if (((Dpc->Importance == HighImportance) ||
                     (DpcData->DpcQueueDepth >= TargetPrcb->MaximumDpcQueueDepth))) {

                    if (((KiIdleSummary & AFFINITY_MASK(Number)) == 0) ||
                        (KeIsIdleHaltSet(TargetPrcb, Number) != FALSE)) {

                        TargetPrcb->DpcInterruptRequested = TRUE;
                        RequestInterrupt = TRUE;
                    }
                }

            } else {
                if ((Dpc->Importance != LowImportance) ||
                    (DpcData->DpcQueueDepth >= TargetPrcb->MaximumDpcQueueDepth) ||
                    (TargetPrcb->DpcRequestRate < TargetPrcb->MinimumDpcRate)) {

                    TargetPrcb->DpcInterruptRequested = TRUE;
                    RequestInterrupt = TRUE;
                }
            }

#endif

本质上,在多处理器系统上,如果从DPC对象获取的目标核心不是线程的当前核心,那么:如果DPC具有高重要性或者它超过最大队列深度和目标亲和力的逻辑and并且空闲核心为0(即目标核心不空闲)和(好吧,KeIsIdleHaltSet似乎完全相同(它检查目标PRCB中的休眠标志))然后它在PRCB中设置一个DpcInterruptRequested标志目标核心。如果DPC的目标是当前核心,那么如果DPC不是低重要性(注意:这将允许中等!)或者如果DPC队列深度超过最大队列深度并且如果核心上的DPC的请求率没有' t超过最小值,它在当前核心的PRCB中设置一个标志,表示存在DPC。

它现在释放DPC队列spinlock:KiReleaseSpinLock(&DpcData->DpcLock);(当然是#if !defined(NT_UP))(它不会改变IRQL)。然后它检查过程是否请求了一个中断(if (RequestInterrupt == TRUE)),然后如果它是一个单处理器系统(#if defined(NT_UP))它只是调用KiRequestSoftwareInterrupt(DISPATCH_LEVEL);但是如果它是一个多核系统它需要检查目标PRCB以查看它是否需要发送IPI。

if (TargetPrcb != CurrentPrcb) {
    KiSendSoftwareInterrupt(AFFINITY_MASK(Number), DISPATCH_LEVEL);

    } else {
        KiRequestSoftwareInterrupt(DISPATCH_LEVEL);
    }     

它说明了它的作用;如果当前的PRCB不是DPC的目标PRCB,那么它使用DISPATCH_LEVEL向处理器号码发送KiSendSoftwareInterrupt优先级的IPI;否则,它使用KiRequestSoftwareInterrupt。根本没有文档,但我的猜测是这是一个自我IPI,它将包装一个HAL函数,编程ICR以发送级别优先级向自己发送IPI(我的理由是在这个阶段的ReactOS调用HalRequestSoftwareInterrupt,它显示了一个未实现的PIC写入)。所以它不是INT意义上的软件中断,但实际上,简单来说就是硬件中断。然后它将IRQL从31降低到之前的IRQL(即ISR IRQL)。然后它返回到ISR,然后它将返回到KiInterruptDispatch; KiInterruptDispatch然后将使用KeReleaseInterruptSpinLock释放ISR自旋锁,这会将IRQL降低到中断之前的状态,然后弹出陷阱帧但我认为它会首先弹出陷阱帧然后编程LAPIC TPR以便寄存器恢复过程是原子的,但我认为这并不重要。

ReactOS有以下内容(WRK没有记录KeReleaseSpinlock或IRQL降低程序,所以这是我们最好的):

VOID NTAPI KeReleaseSpinLock ( KIRQL NewIrql )
    {
    /* Release the lock and lower IRQL back */
    KxReleaseSpinLock(SpinLock);
    KeLowerIrql(OldIrql);
    }

VOID FASTCALL KfReleaseSpinLock ( PKSPIN_LOCK SpinLock, KIRQL OldIrql )
    {
    /* Simply lower IRQL back */
    KeLowerIrql(OldIrql);
    }

KeLowerIrql是HAL函数KfLowerIrql的包装器,该函数包含KfLowerIrql(OldIrql);,就是这样。

VOID FASTCALL KfLowerIrql ( KIRQL NewIrql )
    {
     DPRINT("KfLowerIrql(NewIrql %d)\n", NewIrql);

     if (NewIrql > KeGetPcr()->Irql)
     {
         DbgPrint ("(%s:%d) NewIrql %x CurrentIrql %x\n",
         __FILE__, __LINE__, NewIrql, KeGetPcr()->Irql);
         KeBugCheck(IRQL_NOT_LESS_OR_EQUAL);
         for(;;);
     }
     HalpLowerIrql(NewIrql);
 }

此函数基本上可以防止新的IRQL高于当前的IRQL,因为该函数应该降低IRQL。如果一切正常,该函数调用HalpLowerIrql(NewIrql);这是多处理器AMD64实现的框架 - 它实际上并不实现APIC寄存器写入(或x2APIC的MSR),它们是ReactOS的多处理器AMD64实现上的空函数,因为它正在开发中;但是在Windows上,它们不会,并且它们实际上将编程LAPIC TPR,以便现在可以发生排队的软件中断。

HalpLowerIrql(KIRQL NewIrql, BOOLEAN FromHalEndSystemInterrupt)
 {
   ULONG Flags;
   UCHAR DpcRequested;
   if (NewIrql >= DISPATCH_LEVEL)
     {
       KeSetCurrentIrql (NewIrql);
       APICWrite(APIC_TPR, IRQL2TPR (NewIrql) & APIC_TPR_PRI);
       return;
     }
   Flags = __readeflags();
   if (KeGetCurrentIrql() > APC_LEVEL)
     {
       KeSetCurrentIrql (DISPATCH_LEVEL);
       APICWrite(APIC_TPR, IRQL2TPR (DISPATCH_LEVEL) & APIC_TPR_PRI);
       DpcRequested = __readfsbyte(FIELD_OFFSET(KIPCR, HalReserved[HAL_DPC_REQUEST]));
       if (FromHalEndSystemInterrupt || DpcRequested)
         {
           __writefsbyte(FIELD_OFFSET(KIPCR, HalReserved[HAL_DPC_REQUEST]), 0);
           _enable();
           KiDispatchInterrupt();
           if (!(Flags & EFLAGS_INTERRUPT_MASK))
             {
               _disable();
             }
     }
       KeSetCurrentIrql (APC_LEVEL);
     }
   if (NewIrql == APC_LEVEL)
     {
       return;
     }
   if (KeGetCurrentThread () != NULL &&
       KeGetCurrentThread ()->ApcState.KernelApcPending)
     {
       _enable();
       KiDeliverApc(KernelMode, NULL, NULL);
       if (!(Flags & EFLAGS_INTERRUPT_MASK))
         {
           _disable();
         }
     }
   KeSetCurrentIrql (PASSIVE_LEVEL);
 }

首先,它检查新的IRQL是否高于调度级别,如果是,它将其设置为正常并写入LAPIC TPR寄存器并返回。如果没有,它会检查当前的IRQL是否为调度级别(>APC_LEVEL)。这意味着根据定义,新的IRQL将低于调度级别。我们可以看到,在这种情况下,它使它等于DISPATCH_LEVEL而不是让它下降并将其写入LAPIC TPR寄存器。然后检查是HalReserved[HAL_DPC_REQUEST],这似乎是ReactOS使用的,而不是我们之前看到的DpcInterruptRequested,所以只需用它替换它。然后将其设置为0(注意,PCR在内核模式中由FS段指向的段描述符的开始处开始)。然后启用中断并调用KiDispatchInterrupt,之后如果eflags寄存器在KiDispatchInterrupt期间更改了IF标志,则禁用中断。然后它还会在最终将IRQL设置为被动级别之前检查内核APC是否处于挂起状态(这超出了本解释的范围)

VOID NTAPI KiDispatchInterrupt ( VOID )
 {
     PKIPCR Pcr = (PKIPCR)KeGetPcr();
     PKPRCB Prcb = &Pcr->Prcb;
     PKTHREAD NewThread, OldThread;

     /* Disable interrupts */
     _disable();

     /* Check for pending timers, pending DPCs, or pending ready threads */
     if ((Prcb->DpcData[0].DpcQueueDepth) ||
         (Prcb->TimerRequest) ||
         (Prcb->DeferredReadyListHead.Next))
     {
         /* Retire DPCs while under the DPC stack */
         //KiRetireDpcListInDpcStack(Prcb, Prcb->DpcStack);
         // FIXME!!! //
         KiRetireDpcList(Prcb);
     }

     /* Re-enable interrupts */
     _enable();

     /* Check for quantum end */
     if (Prcb->QuantumEnd)
     {
         /* Handle quantum end */
         Prcb->QuantumEnd = FALSE;
         KiQuantumEnd();
     }
     else if (Prcb->NextThread)
     {
         /* Capture current thread data */
         OldThread = Prcb->CurrentThread;
         NewThread = Prcb->NextThread;

         /* Set new thread data */
         Prcb->NextThread = NULL;
         Prcb->CurrentThread = NewThread;

         /* The thread is now running */
         NewThread->State = Running;
         OldThread->WaitReason = WrDispatchInt;

         /* Make the old thread ready */
         KxQueueReadyThread(OldThread, Prcb);

         /* Swap to the new thread */
         KiSwapContext(APC_LEVEL, OldThread);
     }
 }

首先,它禁用中断_disable只是一个asm块的包装器,它清除IF标志并在clobber列表中有内存和cc(以防止编译器重新排序)。这看起来像是arm语法。

 {
     __asm__ __volatile__
     (
      "cpsid i    @ __cli" : : : "memory", "cc"
     );
 }

这确保它可以作为不间断的过程耗尽DPC队列;与禁用中断一样,它不能被时钟中断中断并重新安排。这可以防止2个调度程序同时运行的情况,例如,如果使用Sleep()产生的线程最终调用KeRaiseIrqlToSynchLevel,这类似于禁用中断。这将防止定时器中断中断它并在当前正在执行的线程切换过程的顶部调度另一个线程切换 - 它确保调度是原子的。

它会检查当前内核的正常队列中是否有DPC,或者是否有定时器到期或延迟就绪线程,然后调用KiRetireDpcList,它基本上包含一个while队列深度!= 0循环,首先检查它是否是计时器到期请求(我现在不会进入),如果没有,则获取DPC队列自旋锁,从队列中取出DPC并将成员解析为参数(中断仍然被禁用),减少队列深度,丢弃自旋锁,启用打断并调用DeferredRoutine。当DeferredRoutine返回时,它再次禁用中断,如果队列中有更多中断,它会重新获取自旋锁(自旋锁和中断禁用,确保从队列中删除DPC是原子的,这样另一个中断,因此DPC队列消耗不会对相同的DPC - 它已经从队列中删除了)。由于在ReactOS上尚未实现DPC队列自旋锁,我们可以假设在Windows上可能发生的事情:如果它无法获取自旋锁,那么假设它是自旋锁并且我们仍然在DISPATCH_LEVEL并且中断被禁用,它将旋转直到另一个核心上的线程调用KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);并不是那么多,因为每个线程都有大约100 uops的自旋锁,所以我们可以在DISPATCH_LEVEL上禁用中断。

请注意,排放过程只会耗尽当前核心的队列。当DPC队列为空时,它会重新启用中断并检查是否存在任何延迟就绪线程并使它们全部就绪。然后它将调用链返回到KiInterruptTemplate,然后ISR正式结束。

因此,作为概述,在KeInsertQueuedpc中,如果要排队的DPC是另一个核心并且它具有高优先级或队列深度超过PRCB中定义的最大值,则它在核心的PRCB中设置DpcRequested标志并发送IPI到核心很可能以某种方式运行KiDispatchInterrupt(ISR可能只是确实调用KiDispatchinterrupt的IRQL下级程序),这将耗尽该核心上的DPC队列;调用KiDispatchinterrupt的实际包装器可能会或可能不会像HalpLowerIrql那样禁用PRCB中的DpcRequested标志,但我不知道,它可能确实是我所建议的HalpLowerIrql。在KeInsertQueuedpc之后,当它降低IRQL时,没有任何反应,因为DpcRequested标志位于另一个核心而不是当前核心。如果要排队的DPC是针对当前核心,那么如果它具有高优先级或中优先级,或者队列深度超过最大队列深度且DPC速率小于PRCB中定义的最小速率,则它设置DpcRequested标志在PRCB中并请求一个自我IPI,它将调用调度程序使用的相同通用包装器,因此可能类似于HalpLowerIrql。在KeInsertQueuedpc之后,它使用HalpLowerIrql降低IRQL并且看到DpcRequested因此在降低IRQL之前排空当前核心的队列。

你看到了这个问题吗? WRK显示正在请求的“软件”中断(其ISR可能调用KiDispatchInterrupt,因为它是一个多功能函数,并且只有一个函数可用:KiRequestSoftwareInterrupt(DISPATCH_LEVEL) in all scenarios)但是当ReactOS显示在IRQL下降时调用KiDispatchInterrupt。您期望当KiInterruptDispatch丢弃ISR自旋锁时,执行此操作的功能只会检查延迟就绪线程或计时器到期请求然后只丢弃IRQL,因为LAPIC TPR将立即发生排空队列的软件中断编程,但ReactOS实际检查队列中的项目(使用PRCB上的标志)并启动程序中的队列耗尽以降低IRQL。没有用于自旋锁释放的WRK源代码,但让我们假设它不会执行ReactOS上发生的事情并让'软件'中断处理它 - 也许它会让整个DPC队列检查出它的等效HalpLowerIrql。但等一下,如果它不用于启动像ReactOS那样排队的队列,那么Prcb->DpcInterruptRequested是什么呢?也许它仅用作控制变量,因此它不会排队2个软件中断。我们还注意到ReactOS也是requests a 'software' interrupt at this stage(对于arm的向量中断控制器),这是非常奇怪的。所以也许不是。这显然表明它会被调用两次。似乎它排空了队列,然后在IRQL下降(很可能在某个阶段也调用KiRetireDpcList)后,立即进入“软件”中断,ReactOS和WRK都做同样的事情。我想知道是谁做的。我的意思是为什么自我IPI然后排队呢?其中一项行动是多余的。

至于懒惰的IRQL。我在WRK或ReactOS上看不到它的证据,但它将被实施的地方将是KiInterruptDispatch。可以使用KeGetCurrentIrql获取当前的IRQL,然后将其与中断对象的IRQL进行比较,然后对TPR进行编程以对应当前的IRQL。它要么停顿中断,要么使用自身IPI为该向量排队另一个,否则它只是简单地切换陷阱帧。

© www.soinside.com 2019 - 2024. All rights reserved.