Как программные прерывания запускаются в Windows, когда IRQL падает? - PullRequest
0 голосов
/ 23 января 2019

Я знаю, что для аппаратных прерываний, когда KeAowerIrql вызывается KeAcquireInterruptSpinLock, HAL корректирует маску прерываний в LAPIC, что позволит автоматически обрабатывать очереди прерываний (возможно, в IRR).Но с программными прерываниями, например, вызовами syntter ntdll.dll системным службам SSDT NtXxx, как они «откладываются» и запускаются, когда IRQL переходит на пассивный уровень То же самое происходит с программным прерыванием диспетчера DPC (если DPC предназначен длятекущий процессор и с высоким приоритетом), как это срабатывает, когда IRQL

while (irql != passive)

Точно такой же вопрос для ленивого IRQL:

Поскольку доступ к PIC являетсяОтносительно медленная работа. HAL, которым требуется доступ к шине ввода-вывода для изменения IRQL, например, для PIC и 32-битных систем с расширенными настройками и интерфейсами питания (ACPI), реализуют оптимизацию производительности, называемую lazy IRQL, которая предотвращает доступ к PIC.Когда IRQL повышается, HAL отмечает новый IRQL внутри, а не меняет маску прерывания.Если впоследствии происходит прерывание с более низким приоритетом, HAL устанавливает маску прерывания на параметры, соответствующие первому прерыванию, и не прекращает прерывание с более низким приоритетом до тех пор, пока IRQL не будет понижен (таким образом, сохраняя ожидание прерывания).Таким образом, если при повышении IRQL не происходит прерываний с более низким приоритетом, HAL не нужно изменять PIC.

Как он поддерживает это прерывание в ожидании?Это просто зацикливает условие, пока ISR с более высоким приоритетом не снизит IRQL, и когда поток будет запланирован, условие в конечном итоге будет выполнено?Разве это так просто?

Редактировать: Я должен что-то упустить здесь, потому что, скажем, ISR на устройстве IRQL запрашивает DPC с помощью IoRequestDpc, если это DPC с высоким приоритетом и целевой процессор является текущим процессоромзатем он планирует прерывание уровня DPC / Dispatch, чтобы истощить очередь DPC процессора.Все это происходит в ISR, который находится на устройстве IRQL (DIRQL), что означает, что программное прерывание с уровнем IRQL Dispatch / DPC будет вращаться на уровне KeAcquireInterruptSpinLock. Я думаю , потому что текущий IRQL слишком высок, но не будетон будет вращаться там вечно, потому что фактическая процедура для понижения IRQL вызывается после того, как ISR возвращает, что означает, что он останется в ISR на устройстве IRQL, ожидая этого программного прерывания, для которого требуется IRQL

1) ISR возвращает объект KDPC KiInterruptDispatch, чтобы он знал, какой приоритет имеет DPC, и затем сам планирует его после того, как он понизилIRQL использует KeReleaseInterruptSpinLock, но KSERVICE_ROUTINE возвращает только несвязанное логическое значение, поэтому это исключено.

Кто-нибудь знает, как избежать этой ситуации?

Редактировать 2: Возможно, он порождает новый поток, который блокирует ожиданиедля IRQL <Отправьте IRQL, затем вернитесь из ISR и сбросьте IRQL.</p>

1 Ответ

0 голосов
/ 05 февраля 2019

Это то, что на самом деле явно не объяснено ни в одном источнике, и что интересно, второй комментарий также задает тот же вопрос.

После изучения ядра ReactOS и WRK я теперь точно знаю, что происходит

Драйвер, когда он получает IRP_MN_START_DEVICE от диспетчера PnP, инициализирует объект прерывания, используя IoConnectInterrupt, используя данные в CM_RESOURCE_LIST, которые он получает в IRP. Особый интерес представляют вектор и сходство, которые были назначены PnP-менеджером устройству (это легко сделать, если устройство предоставляет возможность MSI в своем пространстве конфигурации PCIe, поскольку ему не нужно беспокоиться о базовой маршрутизации IRQ). Он передает вектор, указатель на ISR, контекст для ISR, IRQL на IoConnectInterrupt, который вызывает KeInitializeInterrupt для инициализации объекта прерывания с использованием параметров, а затем вызывает KeConnectInterrupt, который переключает сходство текущего потока на целевой процессор блокирует базу данных диспетчера и проверяет, что эта запись IDT указывает на оболочку BugCheck KxUnexpectedInterrupt0[IdtIndex]. Если это так, тогда IRQL поднимается до 31, поэтому следующая операция является атомарной и использует HAL API, чтобы включить вектор, который был отображен диспетчером PnP в LAPIC, и назначить ему уровень приоритета TPR, соответствующий IRQL. Затем он отображает вектор на адрес обработчика в записи IDT для вектора. Для этого он передает адрес &Interrupt->DispatchCode[0] в процедуру отображения IDT KeSetIdtHandlerAddress. Похоже, что это шаблон , который одинаков для всех объектов прерываний, который согласно WRK равен KiInterruptTemplate. Конечно же, проверяя ядро ​​ReactOS, мы видим в KeInitializeInterrupt - который вызывается IoConnectInterrupt - код:

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

KiInterruptDispatchTemplate пока пусто, потому что порт ReactOS amd64 находится на ранней стадии разработки. На окнах это будет реализовано как и KiInterruptTemplate.

Затем IRQL возвращается к старому IRQL. Если запись IDT не указывает на ISR BugCheck, то она инициализирует связанное прерывание - потому что в записи 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 вызывает KiInitializeDpc, который жестко задан для установки приоритета на средний, что означает, что KeInsertQueueDpc поместит его в середину очереди DPC. KeSetImportanceDpc и KeSetTargetProcessorDpc могут использоваться после вызова для установки возвращенного DPC, для которого был сгенерирован приоритет и целевой процессор соответственно. Он копирует объект DPC в член объекта устройства и, если объект DPC уже существует, ставит его в очередь на уже существующий DPC.

Когда происходит прерывание, шаблон KiInterruptTemplate объекта прерывания - это адрес в IDT, который вызывается, который затем вызывает реальный диспетчер прерываний , который является членом DispatchAddress, который будетKiInterruptDispatch для нормального прерывания или KiChainedDispatch для цепного прерывания.Он передает объект прерывания в KiInterruptDispatch (он может сделать это, потому что, как мы видели ранее, RtlCopyMemory скопировал KiInterruptTemplate в объект прерывания, это означает, что он может использовать блок asm с относительным RIP для получения адресаобъекта прерывания, которому он принадлежит (он также может пытаться что-то сделать с помощью функции 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) вызывает IoRequestDpc с адресом объекта устройства, который был создан для устройства и ISR, в качестве параметра вместе с конкретным контекстом прерывания и необязательным IRP (который, я предполагаю, он получает из головы вDeviceObject->Irp, если подпрограмма предназначена для обработки IRP).Я ожидал, что это будет однострочная оболочка KeInsertQueue, но вместо этого передается член Dpc объекта устройства, и это именно то, что и есть: KeInsertQueueDpc(&DeviceObject->Dpc, Irp, Context);.Во-первых, KeInsertQueue поднимает IRQL с устройства IRQL устройства ISR до 31, что предотвращает любое прерывание. WRK содержит следующую строку в строке 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 поддерживает только одно ядро, то есть на другом ядре нет потока, который мог бы получить доступ к очереди DPC (ядро может иметь целевой DPC для очереди этого ядра),Существует только одна очередь DPC.Если бы это была многоядерная система, она должна была бы получить спин-блокировку, чтобы они выглядели как заполнители для реализации многоядерной функциональности.Если ему не удалось получить спин-блокировку для очереди DPC, то он либо ожидал бы вращение на IRQL 31, либо перешел на IRQL самого прерывания и ожидал, что позволило бы другим прерываниям происходить с ядром, но другие потоки не могли работать на ядре..

Обратите внимание, что Windows использует KeAcquireSpinLockAtDpcLevel для получения этого спинлока, а ReactOS - нет.KeAcquireSpinLockAtDpcLevel не касается IRQL .Хотя в WRK он напрямую использует KiAcquireSpinLock, который можно увидеть в строке 275 из dpcobj.c, которая только получает спин-блокировку и ничего не делает с IRQL (KiAcquireSpinLock(&DpcData->DpcLock);).

После получения спин-блокировки онаво-первых, гарантирует, что объект DPC еще не находится в очереди (элемент DpcData будет иметь значение NULL, если он cmpxchg инициализирует его с помощью DpcData, возвращенного из KiSelectDpcData(TargetPrcb, Dpc)), и если это так, он удаляет спин-блокировкуи возвращается;в противном случае он устанавливает членов DPC так, чтобы они указывали на определенный контекст прерывания, который был передан, а затем вставляет его в очередь либо в начале (InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);), либо в хвосте (InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);) в зависимости от приоритета (* 1108).*).Затем он проверяет, что 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 в значение соответственноа затем он устанавливает TargetPrcb->DpcThreadRequested и TargetPrcb->QuantumEnd в значение true и устанавливает RequestInterrupt в значение true, если целевой PRCB является текущим PRCB, в противном случае он устанавливает его в значение 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, по-видимому, точно то же самое (он проверяет флаг Sleeping в целевом PRCB)), тогда он устанавливает флаг DpcInterruptRequested в PRCB целевого ядра . Если целью 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 не является целевым PRCB DPC, то он отправляет IPI с приоритетом DISPATCH_LEVEL на номер процессора, используя KiSendSoftwareInterrupt; в противном случае он использует KiRequestSoftwareInterrupt. Документации вообще нет, но я предполагаю, что это Self IPI, и он обернет функцию HAL, которая запрограммирует ICR для отправки IPI себе с приоритетом уровня диспетчеризации (на этом этапе я считаю, что ReactOS вызывает HalRequestSoftwareInterrupt, который показывает невыполненную запись 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 (или MSR для x2APIC), они являются пустыми функциями в многопроцессорной реализации ReactOS AMD64, как это в развитии; но на окнах они не будут, и они фактически запрограммируют 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 изменил флаг IF во время KiDispatchInterrupt, он отключает прерывания.Затем он также проверяет, ожидает ли ядро ​​APC (что выходит за рамки этого объяснения), прежде чем окончательно установить IRQL на пассивный уровень

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 и имеет память и cc в списке clobber (для предотвращения переупорядочения компилятора).Это похоже на синтаксис arm.

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

Это гарантирует, что он может использовать очередь DPC как непрерывную процедуру;как и при отключенных прерываниях, оно не может быть прервано прерыванием по времени и перенесено.Это предотвращает сценарий запуска двух планировщиков одновременно, например, если поток с Sleep() завершает вызов KeRaiseIrqlToSynchLevel, что аналогично отключению прерываний.Это предотвратит прерывание таймером, прервав его и запланировав переключение другого потока поверх текущей выполняемой процедуры переключения потоков - это гарантирует, что планирование является атомарным.

Он проверяет, есть ли DPC в нормальной очередитекущего ядра или наличия истечения таймера или отложенных готовых потоков, а затем вызывает KiRetireDpcList, который в основном содержит глубину очереди while! = 0, которая сначала проверяет, является ли это запросом истечения таймера(в который я сейчас не буду вдаваться), если нет, получает спин-блокировку очереди DPC, снимает DPC с очереди и разбирает членов на аргументы (прерывания все еще отключены), уменьшает глубину очереди, сбрасывает спин-блокировку, включает прерывания и вызываетDeferredRoutine.Когда возвращается DeferredRoutine, он снова отключает прерывания, и, если в очереди их больше, он снова получает спин-блокировку (блокировка блокировки и прерывания гарантируют, что удаление DPC из очереди является атомарным, так что другое прерывание и, следовательно, утечка очереди DPC не работаютна том же ЦОДе - он уже будет удален из очереди).Поскольку спин-блокировка очереди DPC еще не реализована в ReactOS, мы можем постулировать, что может произойти с окнами: если он не может получить спин-блокировку, то, учитывая, что это спин-блокировка и что мы все еще находимся на DISPATCH_LEVEL, а прерывания отключены, он будет вращатьсяпока поток на другом ядре не вызовет KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);, что не так уж и сильно, поскольку каждый поток имеет спин-блокировку в течение примерно 100 моп, я бы сказал, поэтому мы можем позволить отключить прерывания на DISPATCH_LEVEL.

Обратите внимание, что процедура слива только когда-либо истощает очередь текущего ядра.Когда очередь DPC пуста, она активирует прерывания и проверяет наличие отложенных готовых потоков и делает их все готовыми.Затем он возвращает вниз цепочку вызовов на KiInterruptTemplate, а затем ISR официально завершается.

Итак, в качестве обзора, в KeInsertQueuedpc, если DPC для очереди относится к другому ядру и имеет высокий приоритет, или глубина очереди превышает максимум, определенный в PRCB, тогда он устанавливает флаг DpcRequested в PRCBcore и отправляет IPI ядру, которое, скорее всего, каким-то образом запускает KiDispatchInterrupt (ISR может быть просто процедурой IRQL более низкого уровня, которая на самом деле вызывает KiDispatchinterrupt), которая истощает очередь DPC на этом ядре;фактическая оболочка, которая вызывает KiDispatchinterrupt, может или не может отключить флаг DpcRequested в PRCB, как HalpLowerIrql, но я не знаю, это действительно может быть HalpLowerIrql, как я и предлагал.После KeInsertQueuedpc, когда он понижает IRQL, ничего не происходит, потому что флаг DpcRequested находится в другом ядре, а не в текущем ядре.Если DPC для очереди нацелен на текущее ядро, то если оно имеет высокий или средний приоритет или глубина очереди превысила максимальную глубину очереди, и скорость DPC меньше минимальной скорости, определенной в PRCB, тогда он устанавливает флаг DpcRequestedв PRCB и запрашивает собственный IPI, который будет вызывать ту же универсальную оболочку, которая используется планировщиком, так что, вероятно, что-то вроде HalpLowerIrql.После KeInsertQueuedpc он понижает IRQL с HalpLowerIrql и видит DpcRequested, поэтому сливает очередь текущего ядра перед понижением IRQL.

Вы видите проблему с этим, хотя?WRK показывает запрашиваемое «программное» прерывание (чей ISR, вероятно, вызывает KiDispatchInterrupt, поскольку это многоцелевая функция, и когда-либо используется только одна функция: KiRequestSoftwareInterrupt(DISPATCH_LEVEL) in all scenarios), но затем ReactOS показывает, что KiDispatchInterrupt вызывается, когдаIRQL также падает .Вы ожидаете, что когда KiInterruptDispatch удалит спин-блокировку ISR, функция, которая сделает это, просто проверит наличие отложенных готовых потоков или запрос истечения таймера, а затем просто отбросит IRQL, потому что программное прерывание для опустошения очереди произойдет, как толькоLAPIC TPR запрограммирован, но ReactOS фактически проверяет элементы в очереди (используя флаг в PRCB) и инициирует слив очереди в процедуре, чтобы понизить IRQL.Не существует исходного кода WRK для освобождения спин-блокировки, но давайте предположим, что он просто не делает то, что происходит в ReactOS, и позволяет «программному» прерыванию обрабатывать его - возможно, он оставляет эту проверку всей очереди DPC вне эквивалента HalpLowerIrql.Но подождите секунду, что тогда означает Prcb->DpcInterruptRequested, если он не используется для инициации опустошения очереди, как в ReactOS?Возможно, он просто используется в качестве управляющей переменной, чтобы не ставить в очередь 2 программных прерывания.Также отметим, что ReactOS также запрашивает «программное» прерывание на этом этапе (для контроллера Vectored Interrupt Controller), что крайне странно.Так что, может быть, не тогда.Это явно говорит о том, что его вызывают дважды.Похоже, что она истощает очередь, а затем сразу же после «прерывания» IRQL (который, скорее всего, также вызывает KiRetireDpcList на некотором этапе) как в ReactOS, так и в WRK поступает «программное» прерывание, что делает то же самое.Интересно, что кто-нибудь делает из этого?Я имею в виду, почему оба Self IPI, а затем истощать очередь в любом случае?Одно из этих действий является избыточным.

Что касается ленивого IRQL.Я не вижу доказательств этого на WRK или ReactOS, но где это будет реализовано, будет KiInterruptDispatch.Можно было бы получить текущий IRQL, используя KeGetCurrentIrql, а затем сравнить его с IRQL объекта прерывания и затем , запрограммировав TPR для соответствия текущему IRQL.Он либо прерывает прерывание и ставит в очередь другое для этого вектора, используя собственный IPI, либо просто переключает кадры прерываний.

...