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

2024-05-09

我知道对于硬件中断,当 KeAcquireInterruptSpinLock 调用 KeLowerIrql 时,HAL 会调整 LAPIC 中的中断掩码,这将允许自动服务排队的中断(可能在 IRR 中)。但是对于软件中断,例如,ntdll.dll sysenter 调用 SSDT NtXxx 系统服务,当 IRQL 进入被动级别时,它们如何“推迟”并触发。同样的情况也适用于 DPC 调度程序软件中断(如果 DPC 用于当前CPU且具有高优先级),当IRQL

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 I 处旋转think因为当前的 IRQL 太高了,但是它不会永远在那里旋转吗?因为降低 IRQL 的实际例程是在 ISR 返回后调用的,这意味着它将停留在设备 IRQL 处的 ISR 中,等待该软件中断。要求 IRQL

1)ISR将KDPC对象返回给KiInterruptDispatch,以便其知道DPC是什么优先级,然后自行调度after它使用 KeReleaseInterruptSpinLock 降低了 IRQL,但 KSERVICE_ROUTINE 仅返回不相关的布尔值,因此排除了这种情况。

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

编辑2:也许它会生成一个新线程,该线程会阻塞等待 IRQL


这是在任何来源上都没有真正明确解释的事情,有趣的是第二条评论 http://www.osronline.com/article.cfm?article=529也问同样的问题。

首先,DPC软件中断与常规SSDT软件中断不同,后者不会被推迟,并且以被动IRQL运行,并且可以随时中断。 DPC软件中断不使用int or syscall或类似的事情,被推迟并在调度级别运行。

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

驱动程序,当它接收到IRP_MN_START_DEVICE从 PnP 管理器,使用初始化一个中断对象IoConnectInterrupt https://github.com/Zer0Mem0ry/ntoskrnl/blob/master/Ke/amd64/intobj.c使用中的数据CM_RESOURCE_LIST它在 IRP 中接收。特别令人感兴趣的是 PnP 管理器分配给设备的向量和亲和力(如果设备在其 PCIe 配置空间中公开 MSI 功能,则这很容易做到,因为它不必担心底层 IRQ 路由)。它将向量、指向 ISR 的指针、ISR 的上下文、IRQL 传递给IoConnectInterrupt哪个调用KeInitializeInterrupt使用参数初始化中断对象,然后调用KeConnectInterrupt它将当前线程的关联性切换到目标处理器,锁定调度程序数据库并检查 IDT 条目是否指向 BugCheck 包装器KxUnexpectedInterrupt0[IdtIndex]。如果是,则它将 IRQL 提高到 31,因此以下是原子操作,并使用 HAL API 启用由 LAPIC 上的 PnP 管理器映射的向量,并为其分配与 IRQL 相对应的 TPR 优先级。然后,它将向量映射到该向量的 IDT 条目中的处理程序地址。为此,它传递地址&Interrupt->DispatchCode[0]进入 IDT 映射例程KeSetIdtHandlerAddress。看来这是一个template https://reactos.org/archives/public/ros-diffs/2006-August/013809.html对于所有中断对象来说都是相同的,根据 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。然后它会删除调度程序数据库自旋锁并将关联性切换回来并返回中断对象。

然后驱动程序设置一个 DPC——DeferredRoutine作为设备对象的成员——使用IoInitializeDpcRequest.

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

KeInitializeDpc calls KiInitializeDpc这是硬编码的 https://doxygen.reactos.org/d1/d92/dpc_8c.html#ae288b59fa56efcc73de60cc1b6127923将优先级设置为中,这意味着KeInsertQueueDpc会将其放置在 DPC 队列的中间。KeSetImportanceDpc and KeSetTargetProcessorDpc可以在调用后使用来分别设置生成的返回 DPC 的优先级和目标处理器。它将 DPC 对象复制到设备对象的成员,如果已经存在 DPC 对象,则将其排队到已存在的 DPC 中。

当中断发生时,KiInterruptTemplate中断对象的模板是被调用的 IDT 中的地址,然后将哪一个是DispatchAddress成员将是KiInterruptDispatch对于正常中断或KiChainedDispatch对于链式中断。它将中断对象传递给KiInterruptDispatch(它能做到这一点是因为,正如我们之前看到的,RtlCopyMemory copied KiInterruptTemplate进入中断对象,这意味着它可以使用具有相对 RIP 的 asm 块来获取它所属的中断对象的地址(它也可以尝试对CONTAINING_RECORD函数)但是intsup.asm包含以下代码来执行此操作:lea rbp, KiInterruptTemplate - InDispatchCode ; get interrupt object address jmp qword ptr InDispatchAddress[rbp]; finish in common code). KiInterruptDispatch然后将获取中断的自旋锁,可能使用KeAcquireInterruptSpinLock。情监侦(ServiceContext) calls IoRequestDpc为设备和 ISR 创建的设备对象地址作为参数,以及中断特定上下文和可选的 IRP(我猜它是从头部获得的)DeviceObject->Irp如果例程旨在处理 IRP)。我希望它是一个单行包装器KeInsertQueue但传递设备对象的 Dpc 成员,这正是它的本质:KeInsertQueueDpc(&DeviceObject->Dpc, Irp, Context);。首先KeInsertQueue将 ISR 的设备 IRQL 的 IRQL 提高到 31,从而防止所有抢占。WRK https://github.com/markjandrews/wrk-v1.2/blob/master/base/ntos/ke/dpcobj.c第 263 行包含以下内容dpcobj.c:

#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 结构的内核结构。

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

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

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

请注意,Windows 将使用KeAcquireSpinLockAtDpcLevel要获取这个自旋锁,ReactOS 没有。KeAcquireSpinLockAtDpcLevel 不触及IRQL https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-keacquirespinlockatdpclevel。虽然,在WRK中它直接使用KiAcquireSpinLock可以在第275行看到dpcobj.c它只获取自旋锁,对 IRQL 不执行任何操作(KiAcquireSpinLock(&DpcData->DpcLock);).

获取自旋锁后,它首先确保 DPC 对象尚未在队列中(DpcData当执行以下操作时,成员将为空cmpxchg初始化它与DpcData从返回KiSelectDpcData(TargetPrcb, Dpc)) 如果是,则丢弃自旋锁并返回;否则,它会设置 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 to set TargetPrcb->DpcSetEventRequest分别为 true 然后设置TargetPrcb->DpcThreadRequested and TargetPrcb->QuantumEnd为 true 并设置RequestInterrupt如果目标 PRCB 是当前 PRCB,则设置为 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 中的休眠标志))它设置了一个DpcInterruptRequested目标核心的 PRCB 中的标志。如果 DPC 的目标是当前核心,则如果 DPC 的重要性不低(注意:这将允许中等重要性!),或者如果 DPC 队列深度超过最大队列深度,并且核心上的 DPC 请求率没有增加t 超过最小值 it在当前核心的 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,则它发送 IPIDISPATCH_LEVEL使用处理器编号的优先级KiSendSoftwareInterrupt;否则,它使用KiRequestSoftwareInterrupt。根本没有文档,但我猜测这是一个 Self IPI,它将包装一个 HAL 函数,该函数对 ICR 进行编程,以调度级别优先级向自身发送 IPI(我的推理是 ReactOS 在这个阶段调用HalRequestSoftwareInterrupt https://doxygen.reactos.org/d2/d8f/hal_2halarm_2generic_2pic_8c.html#ac06227bbf507880cbff06875d4bdcc97这显示了未实现的 PIC 写入)。所以这不是软件中断INT有意义,但实际上,简单地说,是一个硬件中断。然后,它将 IRQL 从 31 降低回之前的 IRQL(即 ISR IRQL)。然后它返回到ISR,然后它会返回到KiInterruptDispatch; KiInterruptDispatch然后将使用释放 ISR 自旋锁KeReleaseInterruptSpinLock这会将 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 将是less高于调度级别。我们可以看到,在这种情况下,它等于DISPATCH_LEVEL而不是让它低于并将其写入 LAPIC TPR 寄存器。然后它检查是HalReserved[HAL_DPC_REQUEST]这似乎是 ReactOS 使用的而不是DpcInterruptRequested我们之前看到过,所以只需用它替换即可。然后将其设置为 0(请注意,PCR 从内核模式下 FS 段所指向的段描述符的开头开始)。然后它启用中断和调用KiDispatchInterrupt之后如果eflags寄存器在期间改变了IF标志KiDispatchInterrupt它禁用中断。然后,在最终将 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 https://doxygen.reactos.org/d9/d85/ntoskrnl_2include_2internal_2ke_8h.html#afe4303889868fcf9b40b0361859d87df它基本上包含一个 while 队列深度 != 0 循环,它首先检查它是否是一个计时器到期请求(我现在不会讨论),如果不是,则获取 DPC 队列自旋锁,从队列中取出一个 DPC 并将成员解析为参数(中断仍然禁用),减少队列深度,删除自旋锁,启用中断并调用DeferredRoutine。当。。。的时候DeferredRoutine返回,它再次禁用中断,如果队列中有更多中断,它会重新获取自旋锁(禁用自旋锁和中断可确保从队列中删除 DPC 是原子的,以便另一个中断和 DPC 队列排出无法在同一个 DPC 上工作 -它将已经从队列中删除)。由于 DPC 队列自旋锁尚未在 ReactOS 上实现,我们可以假设 Windows 上可能会发生什么:如果它无法获取自旋锁,那么假设它是一个自旋锁,并且我们仍然处于DISPATCH_LEVEL并且中断被禁用,它会旋转,直到其他核心上的线程调用KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);这并没有那么大的阻碍,因为每个线程都有大约 100 uops 的自旋锁,所以我们可以在以下位置禁用中断:DISPATCH_LEVEL.

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

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

你看到这有问题吗? WRK 显示正在请求“软件”中断(其 ISR 可能调用KiDispatchInterrupt因为它是一个多用途函数,并且只有一个函数被使用过:KiRequestSoftwareInterrupt(DISPATCH_LEVEL) in all scenarios)但随后 ReactOS 显示KiDispatchInterrupt当 IRQL 下降时被调用as well。你会期望当KiInterruptDispatch删除 ISR 自旋锁,执行此操作的函数将仅检查延迟就绪线程或计时器到期请求,然后删除 IRQL,因为一旦 LAPIC TPR 被编程,就会发生用于排空队列的软件中断,但 ReactOS 实际上会检查队列上的项目(使用 PRCB 上的标志)并在过程中启动队列排空以降低 IRQL。没有用于自旋锁释放的 WRK 源代码,但我们假设它只是不执行 ReactOS 上发生的事情,而是让“软件”中断处理它——也许它会将整个 DPC 队列检查出其等价物HalpLowerIrql。但等一下,这是什么Prcb->DpcInterruptRequested那么如果它不像 ReactOS 那样用于启动队列耗尽呢?也许它只是用作控制变量,以便它不会对 2 个软件中断进行排队。我们还注意到 ReactOS 还在此阶段请求“软件”中断 https://doxygen.reactos.org/d1/d92/dpc_8c.html#a0af285a1d39243f12185b860548b260c(对于arm的向量中断控制器)这非常奇怪。所以也许不是那时。这明显表明它被调用了两次。看起来它耗尽了队列,然后当 IRQL 丢弃后立即出现“软件”中断(很可能也调用KiRetireDpcList在某个阶段)在 ReactOS 和 WRK 上都做同样的事情。我想知道有人对此有何看法。我的意思是为什么既要Self IPI,又要排空队列?其中一项操作是多余的。

至于惰性IRQL。我在 WRK 或 ReactOS 上没有看到任何证据,但它的实施地点是KiInterruptDispatch。可以使用以下命令获取当前的 IRQLKeGetCurrentIrql然后将其与中断对象的IRQL进行比较and then对 TPR 进行编程以对应于当前的 IRQL。它要么静默中断并使用自 IPI 为该向量排队另一个中断,要么只是简单地切换陷阱帧。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当IRQL下降时,Windows中如何触发软件中断? 的相关文章

随机推荐

  • 在 .pyx 文件中导入非整数常量

    如何声明常量 各种类型 不仅仅是枚举值 并在多个之间共享它们 pyx files 在一个 pyx文件中 我可以声明和使用常量 DEF FavouriteFood spam DEF ArraySize 42 print FavouriteFo
  • Spring Security OAuth2 JWT 中的 JWE

    是否可以将 JSON Web 加密 JWE 与 Spring Security OAuth2 JWT 一起使用 现在我有一个追随者JwtAccessTokenConverter Bean public JwtAccessTokenConve
  • 使用 groovy DOMBuilder 省略空属性

    格罗维的标记生成器 http groovy codehaus org api groovy xml MarkupBuilder html has an omitNullAttributes and an omitEmptyAttribute
  • 迁移到 Hibernate 5.x

    我正在将我的应用程序从 Hibernate 3 迁移到 Hibernate 5 我们正在使用DatabaseMetadata类获得TableMetadata 因此使用TableMetadata对象获取数据库表列信息 如列大小 类型等 似乎在
  • python中打印字符串的长度

    有没有什么方法可以找到 即使是最好的猜测 Python中字符串的 打印 长度 例如 potaa bto 是 8 个字符len但 tty 上只打印 6 个字符宽 预期用途 s potato x1b 01 32mpotato x1b 0 0mp
  • gnome-terminal 新选项卡,使用别名作为要执行的命令

    我已经创建了一个别名 bashrc文件如下 alias myproject cd Desktop myproject 当我重新启动终端时保存文件后 输入myproject带我到项目目录 但是当我尝试使用别名作为新的命令参数时gnome te
  • 使用表达式动态决定打开哪个子报表

    我有一份报告 其中包含几页带有图表 表格等的内容 该报告由总是希望对报告进行小修改的不同客户使用 目前 如果客户想要对第 5 页进行更改 则会通过修改重新创建整个报告 即使唯一的更改是在第 5 页的图表中 更重要的是 一些客户不想看到第 3
  • JSP 作为电子邮件模板

    有没有办法发送 MIME 电子邮件 其中电子邮件正文源自 JSP 我需要使用 Javamail 发送一封电子邮件 其中包含一个表格 我认为如果我可以使用 JSP 来完成所有格式设置和布局 将会很方便 在这个线程中 Java 电子邮件模板的建
  • C#中Enum中定义的value__是什么

    What value 可能在这里 value MSN ICQ YahooChat GoogleTalk 我运行的代码很简单 namespace EnumReflection enum Messengers MSN ICQ YahooChat
  • PHP 中 while (true){ 是什么意思?

    我看过这段代码 但不知道这意味着什么 while true echo Hello world 我知道什么是 while 循环 但是 while true 是什么意思 会执行多少次 这不是无限循环吗 虽然是无限循环 但您可以使用以下命令退出它
  • 在 Clojurescript 中查找不匹配的分隔符

    有时我会发现自己遇到大括号 圆括号或方括号不匹配的情况 在 Clojurescript 中 我在用leininingen and cljsbuild我很难追踪到错误源 通常它可以归结为这样 Compiling resources publi
  • 如何在 Api-Platform 中按链接属性过滤结果?

    我有一个User实体和一个Organisation实体 存在关系ManyToOne之间Booking and User ORM ManyToOne targetEntity App Entity User inversedBy bookin
  • 尝试使用curl进行GET,发送的值允许为空

    我正在尝试使用curl 来执行一个简单的GET 其中包含一个名为redirect uri 的参数 被调用的 php 文件打印出 GET redirect uri 的空字符串 它显示 red 并且似乎没有发送任何内容 执行获取操作的代码 Ge
  • 是否有 CSS 选择器可以选择文本溢出的元素?

    是否可以编写一个 css 选择器 仅选择已调用文本溢出行为的元素 div style width 200px I don t want to select this div div style width 200px But I do wa
  • 特征密集稀疏矩阵乘积是线程化的吗?

    我知道稀疏密集产品是根据文档进行线程化的 https eigen tuxfamily org dox TopicMultiThreading html https eigen tuxfamily org dox TopicMultiThre
  • 从 Xamarin Forms 的输入控件中删除下划线

    我是 Xamarin Forms 的新手 正在开发登录表单并使用 Material Design IVisual 我创建了一个自定义 Entry 类并继承它MaterialEntryRenderer来定制它 我想要实现的目标是删除下划线En
  • 获取键盘事件中的鼠标位置

    我试图在用户按住 Shift 键时出现选择轮 滚轮应以鼠标位置为中心 然而当我测试这个时 pageX and clientX两者在事件对象上都未定义 是否可以通过键盘事件获取鼠标坐标 不 只需跟踪mousemove事件并持续保存当前位置 以
  • 为什么 Visual Studio 不移动发布中的某些文件?

    当我尝试发布项目时 Visual Studio 不会将某些文件移动到目标发布文件夹 目前我有这个问题 json文件 我正在使用 Visual Studio 2010 和 MVC4 是否有关于应发布哪些文件或 mime 类型并将其移动到目标文
  • 列出 Docker 容器的卷

    如何列出 Docker 容器的所有卷 我知道它应该很容易获得 但我找不到如何获得 另外 是否可以获取已删除容器的卷并将其删除 您可以使用 docker ps 获取容器 id 并写入 docker检查container id 像这儿 Volu
  • 当IRQL下降时,Windows中如何触发软件中断?

    我知道对于硬件中断 当 KeAcquireInterruptSpinLock 调用 KeLowerIrql 时 HAL 会调整 LAPIC 中的中断掩码 这将允许自动服务排队的中断 可能在 IRR 中 但是对于软件中断 例如 ntdll d