Система приоритетов
Прерывания и планирование
Сведения об IRQL в Таблице 5 показывают, что уровень Dispatch Level связан с операциями планирования. Когда уровень IRQL соответствует Dispatch Level или выше, NT маскирует программные прерывания планировщика, что означает, что NT отключает планировщик. Фактически, драйверы устройств (и NT) не должны осуществлять операции, требующие немедленного ответа от планировщика, когда процессор находится на уровне IRQL большем или равном Dispatch Level.
Это ограничение включает в себя выполнение всего, что может указывать NT, что текущий поток уступает CPU для ожидания осуществления некоторого события, так как эта акция заставит планировщик искать новый поток для исполнения.
Другое действие, которое требует вмешательства планировщика, это ошибка отсутствия страницы. Когда поток обращается к виртуальной памяти, ссылающейся на данные в страничном файле, NT обычно блокирует поток до тех пор, пока данные не будут прочитаны.
Поэтому на уровне Dispatch Level или выше NT не позволяет доступ к памяти, не заблокированной в физической памяти.
Если вы когда либо видели код останова синего экрана IRQL_NOT_LESS_OR_ EQUAL, вы вероятно были свидетелем эффекта от нарушения драйвером этих правил.
Отключение планировщика во время обработки прерывания имеет другой, менее очевидный эффект: NT подсчитывает время, затраченное функциями ISR (процедурами обработки прерываний) и DPC (вызовами отложенных процедур) на фоне величины времени, которое поток был активным к моменту, когда CPU получил прерывание.
Например допустим, что Word выполняет операцию проверки правописания, от устройства поступает прерывание, и драйвер устройства имеет DPC, которое отбирает весь квант времени программы Word (а затем еще сколько-то). Когда IRQL процессора упадет ниже уровня Dispatch Level, планировщик может решить переключиться на другой поток другого приложения, чувствительно наказывая Word за обработку прерывания.
Хотя такая практика кажется несправедливой, почти каждый раз, когда NT распределяет прерывание, оно равномерно распределяется между прикладными программами в системе.
Определение текущего уровня IRQL
Текущий уровень IRQL свой у каждого CPU. Код режима ядра может определить IRQL, в котором он выполняется, посредством вызова функции KeGetCurrentlrql (), прототип которой:
KIRQL KeGetCurrentlrql ();
KeGetCurrentlrql() возвращает IRQL текущего CPU.
Большинство подпрограмм драйвера устройства вызывается Диспетчером ввода/вывода на определенном архитектурой уровне IRQL. To есть разработчик драйвера знает уровень (или уровни) IRQL, на котором будет вызываться данная функция. Подпрограммы режима ядра могут изменять IRQL, на котором они выполняются, вызывая функции KeRaiselrql() и KeLowerlrql(), прототипы которых:
VOID KeRaiselrql (IN PKIRQL Newlrql, OUT PKIRQL Oldlrql);
Где: Newlrql – значение, до которого должен быть поднят уровень IRQL текущего процессора; Oldlrql – указатель на место, в которое будет помещен IRQL, на котором текущий процессор выполнялся перед тем, как был поднят к Newlrql.
VOID KeLowerlrql (IN KIRQL Newlrql);
Где: Newirql – значение, до которого должен быть понижен IRQL текущего процессора.
Так как уровни IRQL являются методом синхронизации, большинство подпрограмм режима ядра (в особенности драйверы устройств) никогда не должны понижать свой уровень IRQL ниже того, на котором они вызывались. Таким образом, драйверы могут вызывать KeRaiselrql (), чтобы поднять IRQL до более высокого уровня, и затем вызывать KeLowerlrql(), чтобы возвратиться обратно к первоначальному уровню IRQL, на котором они были вызваны (например, из Диспетчера ввода/вывода). Однако драйвер никогда не должен вызывать функцию KeLowerlrql(), чтобы понизить IRQL до уровня, меньшего, чем тот, на котором он был вызван. Такое поведение может привести к крайне непредсказуемой работе операционной системы, которая наверняка закончится полным отказом системы.