Иногда во время технических дискуссий я слышал, как некоторые профессионалы предполагали, что Windows IRQL (уровни запросов на прерывание) равны приоритетам Windows. Однако они НЕ равны (даже не близки…).

В целом у нас есть следующие прерывания IRQL:

я. Программное обеспечение: PASSIVE_LEVEL (0), APC_LEVEL (1), DISPATCH_LEVEL (2) — используются для внутренней синхронизации.

II. Аппаратное обеспечение: уровень прерывания 3 или выше, например DIRQL (обычно называется IRQL устройства), CLOCKx_LEVEL, POWER_LEVEL и т. д.

APC_LEVEL обычно используется во время APC (асинхронных вызовов процедур), которые работают как обратные вызовы, которые могут возникать во время времени выполнения процесса/потока (или контекста). сильный>

Например, когда диспетчеру ввода-вывода необходимо вернуть приложению статус ввода-вывода, он может запросить APC ядра (на уровне APC_LEVEL) и уведомить целевой поток. которому этот APC будет поставлен в очередь. В этом случае, если поток выполняется в процессоре и его IRQL меньше APC_LEVEL, APC ядра будет немедленно «обслуживаться». Конечно, если поток либо не запущен, либо выполняется с IRQL выше APC_LEVEL, то APC ядра будет выполняться при соблюдении условия (поток запущен и/или с IRQL ниже APC_LEVEL).

DISPATCH_LEVEL обычно используется для запуска диспетчера и обработки DPC (отложенных вызовов процедур). Интересно помнить, что любая подпрограмма, работающая на этом уровне (или выше), не является «выгружаемой», поэтому она выполняется до конца выполнения. Кроме того, код, работающий на этом уровне, также должен быть невыгружаемым.

Честно говоря, рассказ о DPC (отложенный вызов процедуры) заслужил бы несколько страниц, но мы можем сделать эти концепции проще, чем обычно, упомянув, что когда драйвер устройства обрабатывает запрос, он выполняет эту обработку в два этапа. : самая срочная часть обрабатывается ISR (процедурой обслуживания прерываний), которая выполняется с высоким уровнем прерывания (останавливая выполнение другого низкоуровневого IRQ), и любой другой некритической обработкой. осуществляется с помощью ЦОД. DPC инициализируется с помощью KeInitializeDpc() (по умолчанию он не указывает никакого процессора, поэтому выполняется на том же процессоре, что и запрос), а объект DPC выделяется в невыгружаемая память.

Таким образом, эта дополнительная обработка, выполняемая DPC, настраивается драйвером устройства, который вызывает IoInitializeDpcRequest() (внутренне вызывает KeInitializeDpc()), который инициализирует объект DPC путем передачи указателя на объект устройства, содержащий объект DPC.

Процедура DpcForIsr (поставленная в очередь на выполнение путем вызова IoRequestDPC()) отвечает за завершение обслуживания операции ввода-вывода (из ISR) путем вызова IoRequestDPC(), завершение операции ввода-вывода, установка блока состояния в IRP (пакете запроса ввода-вывода), удаление из очереди следующего IRP (используя IoStartNextPacket()) и вызовом IoCompleteRequest() для выполнения запроса.

После создания объекта DPC с помощью KeInitializeDpc() его можно поставить в очередь на выполнение, вызвав функцию KeInsertQueueDpc() (если объект уже находится в очереди, поэтому функция только возвращает). Наконец, когда процессор возвращается к DISPATCH_LEVEL (помните, что DPC настраивается из ISR, который имеет высокий уровень IRQL), очередь DPC обрабатывается путем вызова отложенной подпрограммы в объекте DPC. . Будьте осторожны: самая большая проблема заключается в том, что подпрограмма DPC выполняется в произвольном контексте, который может не быть связан с запросом, который обрабатывает DPC.

Приоритеты Windows используются для планирования процессов. Кстати, не путайте диспетчеризацию (задача переключения с одного потока на другой для выполнения, что происходит на уровне DISPATCH_LEVEL)) и планирование (задача определения, какой поток должен выполняться следующим на процессоре). Это разные понятия.

Поэтому мы должны помнить, что все потоки пользовательского режима происходят на PASSIVE_LEVEL, но любое прерывание выше PASSIVE_LEVEL прерывает любой поток пользовательского режима независимо от приоритета потока.

Как мы уже знаем, можно определить текущий IRQL (на процессор) с помощью функции KeGetCurrentIrql() и даже изменить его, вызвав KeRaiseIrql()/ Функции KeLowerIrql(). По нескольким причинам (возможен сбой системы) не используйте функцию KeLowerIrql() для понижения IRQL до уровня меньшего, чем тот, на котором он был введен.

Я надеюсь, что эти несколько слов помогут вам, и вскоре я напишу еще один короткий текст об архитектуре Windows. Быть в курсе. :)