Заметки IRQL для драйверов
Все разработчики драйверов должны учитывать уровни запросов прерывания (IRQL). IRQL — это целое число от 0 до 31; PASSIVE_LEVEL, DISPATCH_LEVEL и APC_LEVEL обычно называются символическими, а остальные — числовыми значениями. Повышение и снижение IRQL должно соответствовать строгой дисциплине стека. Функция должна быть направлена на возврат в том же irQL, в котором она была вызвана. Значения IRQL не должны уменьшаться в стеке. Кроме того, функция не может понизить значение IRQL, не вызвав его. Заметки IRQL предназначены для обеспечения соблюдения этих правил.
Если в коде драйвера есть заметки IRQL, средства анализа кода могут сделать более точный вывод о диапазоне уровней, на которых должна выполняться функция, и более точно находить ошибки. Например, можно добавить заметки, указывающие максимальное значение IRQL, при котором можно вызывать функцию; Если функция вызывается в более высоком irQL, средства анализа кода могут выявить несоответствия.
Функции драйвера должны быть помечены как можно больше сведений о IRQL, которые могут быть подходящими. Если доступны дополнительные сведения, это помогает средствам анализа кода при последующей проверке вызывающей функции и вызываемой функции. В некоторых случаях добавление заметки является хорошим способом подавления ложноположительных результатов. Некоторые функции, например служебную функцию, можно вызывать в любом IRQL. В этом случае правильная заметка IRQL отсутствует.
При добавлении примечаний к функции для IRQL особенно важно учитывать, как она может развиваться, а не только ее текущая реализация. Например, реализованная функция может правильно работать на более высоком уровне IRQL, чем предполагалось в конструкторе. Хотя заманчиво добавлять к функции заметки на основе того, что на самом деле делает код, конструктору может быть известно о будущих требованиях, таких как необходимость снижения максимального значения IRQL для некоторого будущего улучшения или ожидающих системных требований. Заметка должна быть производным от намерения конструктора функций, а не из фактической реализации.
С помощью примечаний в следующей таблице можно указать правильный IRQL для функции и ее параметров. Значения IRQL определяются в Wdm.h.
Примечание IRQL | Описание |
---|---|
_IRQL_requires_max_(irql) | Irql — это максимальное значение IRQL, при котором может быть вызвана функция. |
_IRQL_requires_min_(irql) | Irql — это минимальное значение IRQL, при котором можно вызывать функцию. |
_IRQL_requires_(irql) | Функция должна быть введена в irQL, заданном irql. |
_IRQL_raises_(irql) | Функция завершает работу в указанном irql, но ее можно вызвать только для того, чтобы вызвать (не ниже) текущий IRQL. |
_IRQL_saves_ | Параметр с заметками сохраняет текущий IRQL для последующего восстановления. |
_IRQL_restores_ | Параметр с заметками содержит значение IRQL из IRQL_saves , которое необходимо восстановить при возврате функции. |
_IRQL_saves_global_(kind, param) | Текущий irQL сохраняется в расположении, которое является внутренним для средств анализа кода, из которых необходимо восстановить IRQL. Эта заметка используется для добавления примечаний к функции. Расположение определяется по типу и дополнительно уточняется с помощью параметра. Например, OldIrql может быть типом, а FastMutex — параметром, который содержал это старое значение IRQL. |
_IRQL_restores_global_(kind, param) | IrQL, сохраненный функцией с примечаниями IRQL_saves_global, восстанавливается из расположения, которое является внутренним для средств анализа кода. |
_IRQL_always_function_min_(значение) | Значение IRQL — это минимальное значение, до которого функция может понизить значение IRQL. |
_IRQL_always_function_max_(значение) | Значение IRQL — это максимальное значение, до которого функция может вызвать IRQL. |
_IRQL_requires_same_ | Функция с заметками должна ввести и выйти в том же IRQL. Функция может изменить irQL, но перед выходом из нее необходимо восстановить исходное значение IRQL. |
_IRQL_uses_cancel_ | Параметр с заметками — это значение IRQL, которое должно быть восстановлено функцией обратного вызова DRIVER_CANCEL. В большинстве случаев используйте заметку IRQL_is_cancel. |
Заметки для DRIVER_CANCEL
Существует разница между заметками _IRQL_uses_cancel_ и _IRQL_is_cancel_. Заметка _IRQL_uses_cancel_ просто указывает, что аннотированный параметр является значением IRQL, которое должно быть восстановлено функцией обратного вызова DRIVER_CANCEL. Заметка _IRQL_is_cancel_ представляет собой составную заметку, состоящую из _IRQL_uses_cancel_ и нескольких других заметок, которые обеспечивают правильное поведение функции DRIVER_CANCEL служебной программы обратного вызова. Сама по себе заметка _IRQL_uses_cancel_ полезна лишь иногда; например, если остальные обязательства, описанные в _IRQL_is_cancel_ уже выполнены каким-то другим способом.
Примечание IRQL | Описание |
---|---|
_IRQL_is_cancel_ | Параметр с заметками — это irQL, передаваемый в рамках вызова функции обратного вызова DRIVER_CANCEL. Эта заметка указывает, что функция является служебной программой, вызываемой из подпрограмм отмены и выполняющей требования для DRIVER_CANCEL функций, включая освобождение блокировки отмены спина. |
Взаимодействие с заметками IRQL
Заметки параметров IRQL взаимодействуют друг с другом больше, чем другие заметки, так как значения IRQL задаются, сбрасываются, сохраняются и восстанавливаются различными вызываемыми функциями.
Указание максимального и минимального значений IRQL
Заметки _IRQL_requires_max_ и _IRQL_requires_min_ указывают, что функция не должна вызываться из irQL, который выше или ниже указанного значения. Например, когда PREfast видит последовательность вызовов функций, которые не изменяют IRQL, если обнаруживает последовательность со значением _IRQL_requires_max_ ниже ближайшего _IRQL_requires_min_, он сообщает предупреждение о втором вызове. Ошибка может произойти в первом вызове; сообщение указывает, где произошла другая половина конфликта.
Если заметки функции упоминание IRQL и не применяются явным образом _IRQL_requires_max_, средство анализа кода неявно применяет заметки _IRQL_requires_max_(DISPATCH_LEVEL), что обычно верно с редкими исключениями. Неявное применение этого параметра в качестве значения по умолчанию устраняет множество заметок и делает исключения гораздо более заметными.
Заметка _IRQL_requires_min_(PASSIVE_LEVEL) всегда подразумевается, так как irQL не может быть ниже; следовательно, отсутствует соответствующее явное правило о минимальном irQL. Очень немногие функции имеют верхнюю границу, отличаемую от DISPATCH_LEVEL, и нижнюю границу, отличаемую от PASSIVE_LEVEL.
Некоторые функции вызываются в контексте, в котором вызываемая функция не может безопасно поднять IRQL выше некоторого максимума или, чаще всего, не может безопасно понизить его ниже некоторого минимума. Заметки _IRQL_always_function_max_ и _IRQL_always_function_min_ помогают PREfast находить случаи, когда это происходит непреднамеренно.
Например, функции типа DRIVER_STARTIO помечены _IRQL_always_function_min_(DISPATCH_LEVEL). Это означает, что при выполнении функции DRIVER_STARTIO значение IRQL ниже DISPATCH_LEVEL является ошибкой. Другие заметки указывают на то, что функция должна быть введена и завершена в DISPATCH_LEVEL.
Указание явного IRQL
Используйте заметку _IRQL_raises_ или _IRQL_requires_, чтобы помочь PREfast лучше сообщить о несогласованности, обнаруженной с помощью _IRQL_requires_max_ или _IRQL_requires_min_ заметок, так как затем он знает IRQL.
Заметка _IRQL_raises_ указывает, что функция возвращает с новым значением IRQL. При использовании заметки _IRQL_raises_ она также эффективно задает для заметки _drv_maxFunctionIRQL то же значение IRQL. Однако если функция поднимает значение IRQL выше конечного, а затем понижает его до конечного значения, следует добавить явное _IRQL_always_function_max_ заметку после заметки _IRQL_raises_, чтобы разрешить более высокое значение IRQL.
Повышение или понижение IRQL
Заметка _IRQL_raises_ указывает, что функция должна использоваться только для создания IRQL и не должна использоваться для понижения IRQL, даже если синтаксис функции разрешает это. KeRaiseIrql — это пример функции, которую не следует использовать для понижения IRQL.
Сохранение и восстановление IRQL
Используйте заметки _IRQL_saves_ и _IRQL_restores_, чтобы указать, что текущий irQL (точно ли он известен или только приблизительно) сохранен в или восстановлен из параметра с заметками.
Некоторые функции сохраняют и восстанавливают IRQL неявно. Например, системная функция ExAcquireFastMutex сохраняет IRQL в непрозрачном расположении, связанном с быстрым объектом мьютекса, который идентифицирует первый параметр; сохраненный IRQL восстанавливается соответствующей функцией ExReleaseFastMutex для этого быстрого объекта мьютекса. Чтобы явно указать эти действия, используйте заметки _IRQL_saves_global_ и _IRQL_restores_global_. Параметры типа и параметра указывают, где сохраняется значение IRQL. Расположение, в котором сохраняется значение, не обязательно указывать точно, если заметки, сохраняющие и восстанавливающие значение, согласованы.
Поддержка того же IRQL
Все функции, создаваемые драйвером, изменяющие IRQL, следует добавлять заметки к _IRQL_requires_same_ или одной из других заметок IRQL, чтобы указать, что изменение в IRQL ожидается. При отсутствии примечаний, указывающих на любые изменения в IRQL, средства анализа кода выдают предупреждение для любой функции, которая не завершает работу в том же irQL, в котором была введена функция. Если изменение в IRQL предназначено, добавьте соответствующую заметку, чтобы подавить ошибку. Если изменение в IRQL не предназначено, код следует исправить.
Сохранение и восстановление IRQL для процедур отмены ввода-вывода
Используйте заметку _IRQL_uses_cancel_, чтобы указать, что аннотированный параметр является значением IRQL, которое должно быть восстановлено функцией обратного вызова DRIVER_CANCEL. Эта заметка указывает, что функция является служебной программой, вызываемой из подпрограмм отмены, и что она выполняет требования, которые были выполнены для DRIVER_CANCEL функций (т. е. выполняет обязательство для вызывающего объекта).
Например, ниже приведено объявление для типа функции обратного вызова DRIVER_CANCEL. Одним из параметров является IRQL, который должен быть восстановлен этой функцией. Заметки указывают все требования функции отмены.
// Define driver cancel routine type. //
__drv_functionClass(DRIVER_CANCEL)
_Requires_lock_held_(_Global_cancel_spin_lock_)
_Releases_lock_(_Global_cancel_spin_lock_)
_IRQL_requires_min_(DISPATCH_LEVEL)
_IRQL_requires_(DISPATCH_LEVEL)
typedef
VOID
DRIVER_CANCEL (
_Inout_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ _IRQL_uses_cancel_ struct _IRP *Irp
);
typedef DRIVER_CANCEL *PDRIVER_CANCEL;