Уровни запросов программных прерываний (IRQL)
Хотя контроллеры прерываний устанавливают приоритетность прерываний, Windows устанавливает свою собственную схему приоритетности прерываний, известную как уровни запросов прерываний (IRQL). В ядре IRQL-уровни представлены в виде номеров от 0 до 31 на системах x86 и в виде номеров от 0 до 15 на системах x64 и IA64, где более высоким номерам соответствуют прерывания с более высоким приоритетом.
Хотя ядро определяет для программных прерываний стандартный набор IRQL-уровней, HAL отображает номера аппаратных прерываний на IRQL-уровни. На рисунке показаны IRQL-уровни для архитектуры x86, а на следующем рисунке показаны IRQL-уровни для архитектур x64 и IA64.
Прерывания обслуживаются в порядке их приоритета, и прерывания с более высоким уровнем приоритета получают преимущество в обслуживании. При возникновении прерывания с высоким уровнем процессор сохраняет состояние прерванного потока и запускает связанный с прерыванием диспетчер системных прерываний. Тот, в свою очередь, поднимает IRQL и вызывает процедуру обработки прерывания.
После выполнения этой процедуры диспетчер прерываний понижает IRQL-уровень процессора до значения, на котором он был до возникновения прерывания, а затем загружает сохраненное состояние машины.
Прерванный поток продолжает выполнение с того места, в котором оно было прервано. Когда ядро понижает IRQL, могут реализоваться те прерывания с более низким уровнем приоритета, которые были замаскированы. Если так и происходит, ядро повторяет процесс для обработки новых прерываний.
Уровни приоритетов IRQL имеют совершенно другое значение, чем приоритеты, используемые при планировании потоков. Приоритет планирования является атрибутом потока, а IRQL является атрибутом источника прерывания, такого как клавиатура или мышь. Кроме того, у каждого процессора есть установка IRQL, которая меняется при выполнении кода операционной системы.
Установка IRQL каждого процессора определяет, какие прерывания данный процессор может получать. IRQL-уровни также используются для синхронизации доступа к структуре данных режима ядра. Как только запускается поток режима ядра, он повышает или понижает IRQL процессора либо напрямую, путем вызова функций KeRaiseIrql и KeLowerIrql, либо, что случается чаще, опосредованно, через вызовы функций, которые запрашивают объекты ядра, используемые для синхронизации. Как показано на рисунке, прерывания, поступающие от источника с IRQL, превышающим текущий уровень, прерывают работу процессора, а прерывания, поступающие от источников с IRQL-уровнями равными или ниже текущего уровня, маскируются до тех пор, пока выполняющийся поток не понизит IRQL.
Поскольку обращение к PIC является относительно медленной операцией, HAL-механизмы, требующие доступа к шине ввода-вывода для изменения IRQL-уровней (например, для PIC и 32-разрядных систем усовершенствованного интерфейса управления конфигурированием и энергопотреблением — Advanced Configuration and Power Interface, ACPI), реализуют оптимизацию производительности, которая называется «ленивой IRQL» (lazy IRQL) и позволяет избежать обращений к PIC.
При повышении IRQL HAL отмечает для себя новый IRQL, не изменяя маски прерывания. Если же после этого произойдет прерывание с более низким уровнем, HAL устанавливает маску прерывания с настройками, соответствующими первому прерыванию, и не замораживает прерывание с более низким уровнем (сохраняя его тем самым в отложенном состоянии) до тех пор, пока IRQL не будет понижен.
Таким образом, если прерываний с более низким уровнем при повышении IRQL не происходит, модифицировать PIC HAL-механизмам не требуется.
ПРИМЕЧАНИЕ. Исключения из правила блокировки прерываний равного или более низкого уровня при повышении IRQL касаются прерываний APC-уровня. Если поток повышает IRQL до уровня APC, а затем его выполнение подвергается перепланированию из-за прерывания dispatch/DPC-уровня, система может передать прерывание APC-уровня заново спланированному потоку. Таким образом, уровень APC может считаться IRQL, локальным для потока, а не для всего процессора.
Поток режима ядра повышает или понижает IRQL того процессора, на котором он запущен, в зависимости от того, что он пытается сделать. Например, когда возникает прерывание, обработчик системных прерываний (или, возможно, процессор) повышает IRQL процессора до IRQL, установленного для источника прерывания. Это повышение маскирует все прерывания с таким же и более низким IRQL (только на этом процессоре), гарантируя, что процессор, обслуживающий прерывание, не захватывается прерываниями на том же или на более низком уровне.
Замаскированные прерывания либо обрабатываются другим процессором, либо придерживаются до тех пор, пока IRQL не упадет. Поэтому все компоненты системы, включая ядро и драйверы устройств, стараются держать IRQL на пассивном уровне (который иногда называют низким уровнем). Это сделано для своевременной реакции драйверов устройств на аппаратные прерывания при условии, что IRQL не держится неоправданно высоким в течение продолжительных периодов времени.
Эксперимент: просмотр IRQL
Просмотреть сохраненный для процессора IRQL можно с помощью команды отладчика !irql. Сохраненный IRQL представляет собой IRQL на момент, непосредственно предшествующий проникновению в отладчик, повышающему IRQL до статического, ничего не значащего значения:
kd> !irql
Debugger saved IRQL for processor 0x0 -- 0 (LOW_LEVEL)
Учтите, что значение IRQL сохраняется в двух местах. Первое место, в котором представлен текущий IRQL, — это область управления процессором (processor control region, PCR), а второе — его расширение, блок управления областью процессора (processorregioncontrolblock, PRCB), который содержит сохраненный IRQL в поле DebuggerSaveIrql. PCR и PRCB содержат информацию о состоянии каждого процессора в системе, в том числе текущий IRQL, указатель на аппаратную IDT-таблицу, сведения о текущем потоке и следующем, выбранном для запуска потоке.
Ядро и HAL используют эту информацию для выполнения действий, ориентированных на конкретную архитектуру и конкретную машину. Части структур PCR и PRCB открыто определены в заголовочном файле Ntddk.h, относящемся к инструментарию Windows Driver Kit (WDK).
Содержимое PCR текущего процессора можно просмотреть с помощью отладчика ядра, используя команду !pcr. Для просмотра PCR конкретного процессора нужно после команды добавить номер процессора, отделив его от команды пробелом:
lkd> !pcr 0
KPCR for Processor 0 at fffff80001bfad00:
Major 1 Minor 1
NtTib.ExceptionList: fffff80001853000
NtTib.StackBase: fffff80001854080
NtTib.StackLimit: 000000000026ea28
NtTib.SubSystemTib: fffff80001bfad00
NtTib.Version: 0000000001bfae80
NtTib.UserPointer: fffff80001bfb4f0
NtTib.SelfTib: 000007fffffdb000
SelfPcr: 0000000000000000
Prcb: fffff80001bfae80
Irql: 0000000000000000
IRR: 0000000000000000
IDR: 0000000000000000
InterruptMode: 0000000000000000
IDT: 0000000000000000
GDT: 0000000000000000
TSS: 0000000000000000
CurrentThread: fffff80001c08c40
NextThread: 0000000000000000
IdleThread: fffff80001c08c40
DpcQueue:
Поскольку изменение IRQL процессора существенно влияет на работу системы, это изменение должно вноситься только в режиме ядра — потоки пользовательского режима внести это изменение не могут. Это означает, что IRQL процессора при выполнении кода в пользовательском режиме всегда находится на пассивном уровне. Уровень IRQL может быть повышен только при выполнении кода в режиме ядра.
У каждого уровня прерывания есть своя конкретная цель. Например, ядро выдает межпроцессорное прерывание (Interprocessor interrupt, IPI), чтобы запросить выполнение действия на другом процессоре, например, диспетчеризацию конкретного потока для выполнения или обновления кэш-памяти его буфера быстрого преобразования адреса (Translation look-aside buffer, TLB). Через определенные периоды времени системные часы генерируют прерывание, а ядро реагирует на него, обновляя значение часов и измеряя время выполнения потока.
Если аппаратная платформа поддерживает двое часов, ядро добавляет еще один уровень прерывания от системных часов для измерения производительности.
HAL предоставляет сразу несколько уровней прерываний для использования устройствами, управляемыми с помощью прерываний. Конкретное количество этих уровней зависит от процессора и конфигурации системы. Ядро использует программные прерывания (рассматриваемые далее) для инициации планирования потоков и для асинхронного вмешательства в выполнение потока.