Многоуровневая модель драйверов
Ранее в качестве одной из характеристик подсистемы ввода/вывода упоминалась ее многоуровневость. Что это такое?
NT позволяет выстраивать драйверы в соответствии с некоторой функциональной иерархией. При этом, например, одни драйверы имеют своей единственной целью обмен данными с некоторым физическим устройством. Что это за данные и что с ними делать, такие драйвера не знают. Другие драйвера выполняют обработку данных, но не знают в точности, как эти данные получены и как будут отправлены. Такая концепция разделения полномочий драйверов носит название многоуровневой (или послойной) модели драйверов (layered driver model), а сами драйвера – уровневыми драйверами (layered drivers).
В NT 4.0 концепция многоуровневых драйверов занимает важное место, но ее использование не является обязательным требованием.
В Win2000 все драйвера, считающиеся родными, будут уровневыми (для того, чтобы драйвер считался родным для Win2000, он должен как минимум поддерживать управление питанием, а для этого он должен быть уровневым). Большинство драйверов, которые в NT4 мы считали монолитными, в Win2000 будут по своей сути уровневыми. Будем выделять следующие типы драйверов:
- драйверы, представляющие некоторый уровень в многоуровневой архитектуре. Далее именно эти драйверы мы будем называть уровневыми драйверами;
- драйверы-фильтры;
- драйверы файловой системы (File System Driver, FSD);
- мини-драйверы (mini-driver).
Для каждого типа драйверов существует свой протокол реализации многоуровневой структуры. Мы рассмотрим только уровневые драйверы и драйверы-фильтры.
Реализация уровневых драйверов
Стек драйверов обычно создается самими драйверами. Корректное создание стека зависит от правильной последовательности и момента загрузки каждого драйвера из стека. Первыми должны грузиться драйвера самого нижнего уровня и т.д.
При рассмотрении организации стека драйверов необходимо понимание трех моментов:
- объединение драйверов в стек;
- обработка запросов IRP стеком;
- освобождение драйверов стека.
Объединение драйверов в стек
Для объединения драйверов в стек обычно используется функция loGetDeviceObject Pointer(). Функция вызывается драйвером вышележащего уровня для получения указателя на объект-устройство драйвера нижележащего уровня по его имени.
Функция имеет следующий прототип:
NTSTATUS loGetDeviceObjectPointer( IN PUNICODE_STRING ObjectName, IN ACCESS_MASK DesiredAccess, OUT PFILE_OBJECT FileObject, OUT PDEVICE_OBJECT DeviceObjct);
Где:
- ObjectName – Имя требуемого Объекта-устройства;
- DesiredAccess – Требуемый доступ к указанному Объекту-устройству;
- FileObject – Указатель на Объект-файл, который будет использоваться для обращения к устройству;
- DeviceObject – Указатель на Объект-устройство с именем ObjectName.
Функция IoGetDeviceObjectPointer() принимает имя Объекта-устройства, и возвращает указатель на Объект-устройство с этим именем. Функция работает, посылая запрос CREATE на названное устройство. Этот запрос будет неудачным, если никакого устройства по имени ObjectName не существует, или вызывающая программа не может предоставить доступ, указанный в параметре DesiredAccess. Если запрос CREATE успешен, создается Объект-файл, что увеличивает счетчик ссылок Объекта-устройства, с которым связан Объект-файл.
Затем Диспетчер ввода/вывода искусственно увеличивает счетчик ссылок на Объект-файл на единицу, и посылает на устройство запрос CLOSE. В результате всего этого процесса, Объект-устройство (чей указатель возвращен в DeviceObject) не может быть удален, пока не обнулится счетчик ссылок соответствующего ему Объекта-файла. Таким образом, Объект-устройство нижнего уровня не может быть удален, в то время как драйвер вышележащего уровня имеет указатель на него.
Выделим из всего вышесказанного, что функция предусматривает в качестве выходного параметра указатель на Объект-файл специально для того, чтобы при выгрузке стека драйверов освободить устройство нижележащего уровня. Это должно быть сделано в функции Unload драйвера вышележащего уровня с помощью вызова функции ObDereferenceObject (FileObject).
После получения указателя на объект-устройство драйвера нижележащего уровня, драйвер вышележащего уровня должен установить корректное значение полей Characteristics, StackSize, Flags и AlignmentRequirement своего объекта-устройства. Поля Characteristics, Flags и AlignmentRequirement объектов-устройств всех драйверов в стеке должны совпадать, а значение поля StackSize вышележащего устройства должно быть на 1 больше значения этого поля у нижележащего устройства.