当 IRQL 下降时,如何在 windows 中触发软件中断?

How are software interrupts triggered in windows when the IRQL drops?

我知道对于硬件中断,当 KeAcquireInterruptSpinLock 调用 KeLowerIrql 时,HAL 会调整 LAPIC 中的中断掩码,这将允许自动处理排队的中断(可能在 IRR 中)。但是对于软件中断,例如,ntdll.dll sysenter 调用 SSDT NtXxx 系统服务,它们如何 'postponed' 并在 IRQL 进入被动级别时触发 DPC 调度程序软件中断也是如此(如果DPC 用于当前 CPU 并且具有高优先级),当 IRQL < Dispatch IRQL 时如何触发? SSDT all 循环中的软件中断调用函数 (NtXxx) 的条件是

while (irql != passive)

懒惰 IRQL 的问题完全相同:

Because accessing a PIC is a relatively slow operation, HALs that require accessing the I/O bus to change IRQLs, such as for PIC and 32-bit Advanced Configuration and Power Interface (ACPI) systems, implement a performance optimization, called lazy IRQL, that avoids PIC accesses. When the IRQL is raised, the HAL notes the new IRQL internally instead of changing the interrupt mask. If a lower-priority interrupt subsequently occurs, the HAL sets the interrupt mask to the settings appropriate for the first interrupt and does not quiesce the lower-priority interrupt until the IRQL is lowered (thus keeping the interrupt pending). Thus, if no lower-priority interrupts occur while the IRQL is raised, the HAL doesn’t need to modify the PIC.

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

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

1) ISR returns KiInterruptDispatch 的 KDPC 对象,以便它知道 DPC 的优先级,然后在 它降低 IRQL 后自行调度使用 KeReleaseInterruptSpinLock 但 KSERVICE_ROUTINE 只有 returns 一个不相关的布尔值,所以这被排除了。

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

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

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

首先,DPC 软件中断不像常规的 SSDT 软件中断那样,后者不会被延迟 运行 在被动 IRQL 并且可以随时中断。 DPC 软件中断不使用 intsyscall 或类似的东西,在调度级别被推迟和 运行。

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

一个驱动程序,当它从 PnP 管理器接收到 IRP_MN_START_DEVICE 时,使用 IoConnectInterrupt using the data in the CM_RESOURCE_LIST it receives in the IRP. Of particular interest is the vector and affinity that was assigned by the PnP manager to the device (which is simple to do if the device exposes an MSI capability in its PCIe configuration space as it doesn't have to worry about underlying IRQ routing). It passes the vector, a pointer to an ISR, context for the ISR, IRQL to IoConnectInterrupt which calls KeInitializeInterrupt to initialise the interrupt object using the parameters and then it calls KeConnectInterrupt which switches the affinity of the current thread to the target processor, locks the dispatcher database and checks that that IDT entry points to a BugCheck wrapper KxUnexpectedInterrupt0[IdtIndex]. If it is then it raises IRQL to 31 so the following is an atomic operation and uses the HAL API to enable the vector that was mapped by the PnP manager on the LAPIC and assign it a TPR priority level corresponding to the IRQL. It then maps the vector to the handler address in the IDT entry for ther vector. To do this it passes the address &Interrupt->DispatchCode[0] into the IDT mapping routine KeSetIdtHandlerAddress. It appears this is a 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 的地址来将其标记为链式中断。然后它删除调度程序数据库自旋锁并切换亲和力并 returns 中断对象。

驱动程序然后使用 IoInitializeDpcRequest.

为设备对象设置一个 DPC——以 DeferredRoutine 作为成员
FORCEINLINE VOID IoInitializeDpcRequest ( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIO_DPC_ROUTINE DpcRoutine )
    KeInitializeDpc(&DeviceObject->Dpc,
               (PKDEFERRED_ROUTINE) DpcRoutine,
               DeviceObject);

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

当中断发生时,中断对象的 KiInterruptTemplate 模板是被调用的 IDT 中的地址,然后 call the real interrupt dispatcher which is the DispatchAddress member which will be KiInterruptDispatch for a normal interrupt or KiChainedDispatch for a chained interrupt. It passes the interrupt object to KiInterruptDispatch (it can do this because, as we saw earlier, RtlCopyMemory copied KiInterruptTemplate into the interrupt object, this means that it can use an asm block with a relative RIP to acquire the address of the interrupt object it belongs to (it could also attempt to do something with the CONTAINING_RECORD function) but intsup.asm contains the following code to do it : lea rbp, KiInterruptTemplate - InDispatchCode ; get interrupt object address jmp qword ptr InDispatchAddress[rbp]; finish in common code). KiInterruptDispatch will then acquire the interrupt's spinlock, probably using KeAcquireInterruptSpinLock. The ISR (ServiceContext) calls IoRequestDpc with the device object address that was created for the device and ISR, as a parameter, along with interrupt specific context and an optional IRP (which I'm guessing it gets from the head at DeviceObject->Irp if the routine is meant to handle an IRP). I expected it to be a single line wrapper of KeInsertQueue but passing the Dpc member of the device object instead and that's exactly what it is: KeInsertQueueDpc(&DeviceObject->Dpc, Irp, Context);. Firstly KeInsertQueue raises the IRQL from the device IRQL of the ISR to 31 which prevents all preemption. 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 似乎是 fast-accessing 每个内核的 KPRCB 结构的内核结构。

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

/* On UP builds, spinlocks don't exist at IRQL >= DISPATCH */

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

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

获取自旋锁后,它首先确保 DPC 对象不在队列中(DpcData 成员在执行 cmpxchg 初始化时将为 null DpcData returned from KiSelectDpcData(TargetPrcb, Dpc)) 如果是,它会删除自旋锁和 returns;否则,它将 DPC 成员设置为指向传递的中断特定上下文,然后将其插入到队列的头部(InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);)或尾部(InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);)基于其优先级(if (Dpc->Importance == HighImportance))。然后确保 DPC 尚未执行 if (!(Prcb->DpcRoutineActive) && !(Prcb->DpcInterruptRequested))。然后它检查是否 KiSelectDpcData return 编辑了第二个 KDPC_DATA 结构,即 DPC 是线程类型(if (DpcData == &TargetPrcb->DpcData[DPC_THREADED])),如果是 if ((TargetPrcb->DpcThreadActive == FALSE) && (TargetPrcb->DpcThreadRequested == FALSE)) 那么它确实锁定的 xchg 分别将 TargetPrcb->DpcSetEventRequest 设置为真,然后将 TargetPrcb->DpcThreadRequestedTargetPrcb->QuantumEnd 设置为真,如果目标 PRCB 是,则将 RequestInterrupt 设置为真当前 PRCB 否则它只会在目标核心不空闲时将其设置为真。

原题的关键来了。 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

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

它现在释放 DPC 队列自旋锁: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,则它使用 KiSendSoftwareInterrupt 向处理器编号发送 DISPATCH_LEVEL 优先级的 IPI;否则,它使用 KiRequestSoftwareInterrupt。根本没有文档,但我猜这是一个 Self IPI,它将包装一个 HAL 函数,该函数对 ICR 进行编程以在调度级别优先级向自身发送 IPI(我的推理是 ReactOS 在这个阶段调用 HalRequestSoftwareInterrupt 显示未实现的 PIC 写入)。所以它不是 INT 意义上的软件中断,实际上,简单地说,是硬件中断。然后它将 IRQL 从 31 降低回以前的 IRQL(这是 ISR IRQL)。然后它 returns 到 ISR,然后它将 return 到 KiInterruptDispatchKiInterruptDispatch 然后将使用 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 寄存器和 returns。如果不是,它会检查当前 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 循环,它首先检查它是否是一个定时器到期请求(我不会' t 进入现在),如果不是,则获取 DPC 队列自旋锁,从队列中取出一个 DPC 并将成员解析为参数(仍然禁用中断),减少队列深度,丢弃自旋锁,启用中断并调用 DeferredRoutine .当 DeferredRoutine returns 时,它再次禁用中断,如果队列中有更多中断,它会重新获取自旋锁(自旋锁和中断禁用确保从队列中删除 DPC 是原子的,以便另一个中断和因此 DPC 队列排空在同一个 DPC 上不起作用——它已经从队列中删除了)。由于 DPC 队列自旋锁在 ReactOS 上还没有实现,我们可以假设在 windows 上可能会发生什么:如果它未能获得自旋锁,那么假设它是一个自旋锁并且我们仍在 DISPATCH_LEVEL 并中断被禁用时,它会自旋直到其他核心上的线程调用 KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);,这并没有那么多阻碍,因为我想说每个线程都有大约 100 微秒的自旋锁,所以我们可以负担得起在 DISPATCH_LEVEL.

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

因此,总的来说,在KeInsertQueuedpc中,如果要排队的 DPC 是另一个核心并且它具有高优先级或队列深度超过 PRCB 中定义的最大值,那么它会设置 DpcRequested 标志在核心的 PRCB 中,并向核心发送一个 IPI,该核心最有可能以某种方式 运行s 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 显示正在请求 'software' 中断(其 ISR 可能调用 KiDispatchInterrupt,因为它是一个 multi-purpose 函数,并且只有一个函数被使用过: KiRequestSoftwareInterrupt(DISPATCH_LEVEL) in all scenarios) 但随后 ReactOS 显示 KiDispatchInterrupt 在 IRQL 下降时被调用 以及 。你会期望当 KiInterruptDispatch 删除 ISR 自旋锁时,这样做的函数将只检查延迟就绪线程或定时器到期请求,然后只删除 IRQL,因为排空队列的软件中断会尽快发生因为 LAPIC TPR 已被编程,但 ReactOS 实际上检查队列中的项目(使用 PRCB 上的标志)并在过程中启动队列排空以降低 IRQL。没有自旋锁释放的 WRK 源代码,但让我们假设它只是不做 ReactOS 上发生的事情并让 'software' 中断处理它——也许它离开了整个 DPC 队列检查它的等价物HalpLowerIrql。但是等一下,如果 Prcb->DpcInterruptRequested 不像在 ReactOS 上那样用于启动队列清空,那它是干什么用的?也许它只是用作控制变量,因此它不会排队 2 个软件中断。我们还注意到 ReactOS 也 requests a 'software' interrupt at this stage(对于 arm 的向量中断控制器)这是非常奇怪的。所以也许不是那样。这明显表明它被调用了两次。看起来它耗尽了队列,然后 'software' 中断在 ReactOS 和 WRK 上的 IRQL 下降(很可能在某个阶段也调用 KiRetireDpcList 之后)立即出现,并且做同样的事情。我想知道有人对此有何看法。我的意思是为什么既要进行 Self IPI,又要清空队列?其中一项操作是多余的。

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