Многоуровневая модель драйверов
Создание новых ассоциированных пакетов IRP для передачи драйверу нижележащего уровня.
Немного другой подход к созданию нового пакета IRP для передачи драйверу нижележащего уровня состоит в создании ассоциированного пакета IRP с помощью функции loMsakeAssociatedlrp():
PIRP loMakeAssociatedlrp (IN PIRP Masterlrp, IN CCHAR StackSize);
Где:
- StackSize – Число Стеков размещения Ввода/вывода, требуемых в IRP;
- Masterlrp – Указатель на IRP, с которым должен быть связан создаваемый пакет IRP.
Функция loMakeAssociatedlrp() позволяет создавать IRP, которые "связаны" с некоторым "главным" IRP. Драйвер, который вызывает loMakeAssociatedlrp(), должен вручную инициализировать поле Irp.IrpCount главного IRP счетчиком ассоциированных с ним IRP, которые созданы до вызова loMakeAssociatedlrp. Ассоциированные IRP являются особым видом пакетов IRP, при завершении которых значение поля IrpCount в главном IRP уменьшается. Когда значение поля IrpCount в главном IRP становится равным нулю, Диспетчер ввода/вывода автоматически завершает главный IRP.
Ассоциированные пакеты IRP могут создаваться только драйвером высшего уровня. Драйвер высшего уровня, использующий ассоциированные пакеты IRP, может вернуть управление диспетчеру ввода/вывода после вызова IoCallDriver() для каждого из ассоциированных IRP и вызова IoMarklrpPending() для главного IRP. Если такой драйвер устанавливает процедуру завершения для созданного им ассоциированного IRP (с помощью вызова loSetCompletion Routine(), описанного ниже), Диспетчер ввода/вывода не завершит автоматически главный IRP. В этом случае процедура завершения должна напрямую завершить главный IRP посредством вызова IoCompleteRequest().
Получение драйвером вышележащего уровня уведомления о завершении обработки IRP драйвером нижележащего уровня.
В некоторых случаях драйвер вышележащего уровня может захотеть получить уведомление о завершении обработки переданного им запроса ввода/вывода драйвером нижележащего уровня(CompletionNotification). Это может быть сделано с помощью вызова функции IoSetCompletionRoutine() перед передачей пакета IRP драйверу нижележащего уровня любым указанным выше способом.
VOID loSetCompletionRoutine(IN PIRP Irp, IN PIO_COMPLETION_ROUTINE CompletionRoutine, INPVOID Context, IN BOOLEAN InvokeOnSuccess, IN BOOLEAN InvokeOnError, IN BOOLEAN InvokeOnCahcel);
Где:
- Irp – Указатель на IRP, при завершении которого вызывается точка входа CompletionRoutine;
- CompletionRoutine – Указатель на точку входа драйвера, вызываемую при завершении IRP;
- Context – определенное драйвером значение, которое нужно передать как третий параметр для точки входа CompletionRoutine;
- InvokeOnSuccess, InvokeOnError, IwokeOnCancel – Параметры, которые, указывают должна ли точка входа CompletionRoutine быть вызвана при завершении IRP с указанным состоянием.
В качестве второго параметра в вызове loSetCompletionRoutine() передается адрес точки входа драйвера, которая должна быть вызвана при завершении указанного в первом параметре пакета IRP.
Прототип функции – точки входа драйвера:
NTSTATUS CompletionRoutine(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context);
Где:
- DeviceObject – Указатель на объект-устройство, которому предназначался закончившийся пакет IRP;
- IRP – Указатель на закончившийся пакет IRP;
- Context – Определенное драйвером значение контекста, переданное, когда была вызвана функция IoSetCompletionRoutine().
При вызове IoSetCompletionRoutine() указатель на функцию завершения сохраняется в IRP в следующем после текущего Стеке Размещения ввода/вывода (то есть в Стеке Размещения ввода/вывода нижележащего драйвера). Из этого следуют два важных вывода:
- Если драйвер установит функцию завершения для некоторого IRP и завершит этот IRP, функция завершения не будет вызвана.
- Драйвер низшего уровня (либо монолитный драйвер) не может устанавливать функции завершения, так как, во-первых, это бессмысленно (см. предыдущий вывод), а во-вторых, это ошибка, так как следующего (за текущим) стека размещения ввода/вывода для таких драйверов не существует.
Функция завершения вызывается при том же уровне IRQL, при котором нижележащим драйвером была вызвана функция завершения обработки IRP – loComplete Request(). Это может быть любой уровень IRQL меньший либо равный IRQL_ DISPATCH_LEVEL.
Если драйвер вышележащего уровня создавал новые пакеты IRP для передачи драйверу нижележащего уровня, он обязан использовать функцию завершения для этих IRP, причем параметры InvokeOnSuccess, InvokeOnError, IwokeOnCancel должны быть установлены в TRUE. В этих случаях функция завершения должна освободить созданные драйвером IRP с помощью функции IoFreeIrp() и завершить первоначальный пакет IRP.
Требования к реализации функции завершения достаточно сложные. Эти требования можно найти в [Developing Windows NT Device Drivers, pages 481-485].