Роль драйверных слоев в модели WDM
Драйверная модель WDM построена на организации и манипуляции слоями Объектов Физических устройств (Physical Device Object, PDO) и Объектов Функциональных устройств (Functional Device Object, FDO). Объект PDO создается для каждого физически идентифицируемого элемента аппаратуры, подключенного к шине данных, и подразумевает ответственность за низкоуровневый контроль, достаточно общий для набора функций, реализуемых этим аппаратным элементом. Объект FDO предлагает "олицетворение" каждой логической функции, которую "видит" в устройстве программное обеспечение верхних уровней.
В качестве примера рассмотрим привод жесткого диска и его драйвер. Привод диска может быть представлен объектом PDO, который реализует функции шинного адаптера (присоединяет IDE диск к шине PCI). Как только возникает PDO объект, можно реализовывать объект FDO, который примет на себя выполнение функциональных операций над собственно диском. Обращаясь к FDO, можно будет сделать конкретный функциональный запрос к диску, например, чтение или запись сектора. Однако FDO может выбрать и передачу без модификации конкретного запроса своим партнерам по обслуживанию данного устройства (например, сообщение о снижении напряжения питания).
В действительности, роль PDO объектов быстро усложняется и становится рекурсивной. Например, USB хост-контроллер начинает жизнь как физическое устройство, подключенное к шине PCI. Ho вскоре этот хост-контроллер сам начинает выступать в роли шинного драйвера и, по мере обнаружения устройств, подключенных к USB шине, создает свою коллекцию PDO объектов, каждый из которых контролирует собственный FDO объект.
Эта методология в дальнейшем усложняется еще более, поскольку Функциональным Объектам устройств (FDO) разрешается окружать себя Объектами-Фильтрами (filter device objects, FiDO). Соответственно, каждому FiDO объекту сопоставлен драйвер, выполняющий определенную работу (иначе — зачем их создавать?). Эти фильтрующие объекты верхнего и нижнего уровня могут существовать в любом количестве.
Назначение их в том, чтобы модифицировать или обогатить процесс обработки запросов ввода/вывода возможностью использования всего результирующего стека объектов устройств. Следует отметить, что FDO и FiDO объекты отличаются только в смысловом отношении — FDO объект и его драйвер являются главной персоной, FiDO объекты и их драйверы являются вспомогательными (вплоть до того, что предпочитают не иметь собственных имен).
Для того чтобы сделать различие между FDO объектами, которые представляют аппаратные шины, и FDO объектами, которые аппаратные шины не представляют, в документации DDK используются термины шинные FDO (bus FDO) и не-шинные FDO (nonbus FDO). Первые реализуют обязанности драйвера по перечислению (enumerating) всех устройств, подключенных к шине. Такой шинный FDO объект затем создает новые PDO объекты для каждого из подключенных к шине устройств.
Добавляет проблем тот факт, что существует лишь небольшая смысловая разница между не-шинным FDO и фильтрующим объектом устройства (filter device object). C точки зрения Менеджера PnP, все объекты устройств позиционируют себя в стеке устройств (device stack), a тот факт, что некоторые устройства считают себя более чем просто объектами-фильтрами, кажется ему малозначительным.
Последовательность в стеке устройств показана на рисунке 9.2. Различия между шинными и не-шинными FDO отражены на рисунке 9.3.
|
Рис. 9.2 Стек устройств |
Как правило, Microsoft поставляет все шинные драйверы, однако могут быть установлены и специализированные драйвера для патентованных шин данных.
|
Рис. 9.3 Шинные и не-шинные FDO |
Таблица 9.2. Прототип функции IoAttachDeviceToDeviceStack
PDEVICE_OBJECT IoAttachDeviceToDeviceStack | IRQL == PASSIVE_LEVEL |
Параметры | Выполняет подключение вновь созданного объекта устройства, pNewDevice, к стеку устройств |
IN PDEVICE_OBJECT pNewDevice | Указатель на подключаемый к стеку объект (созданный в данном драйвере) |
IN PDEVICE_OBJECT pOldDevice | Указатель на объект устройства, к которому подключается новое устройство |
Возвращаемое значение |
• Указатель на устройство, бывшее на вершине стека до данного вызова • NULL (в случае ошибки, например, если драйвер целевого устройства еще не загружен) |
Как видно из таблицы 9.2, для подключения данного объекта устройства (по указателю pNewDevice) необходимо владеть указателем на целевой объект устройства (pOldDevice).
Прекрасна ситуация, когда драйвер подключает свой объект устройства к родительскому объекту устройства (шинного драйвера), указатель на который поступает в процедуру AddDevice при вызове через заголовок (pPDO, см. таблицу 9.1). Но что делать, если имеется желание подключить новый объект устройства к объекту устройства другого драйвера, отличающегося от pPDO? (Заметим, что подключение к стеку устройств не есть исключительное право процедуры AddDevice драйверов WDM модели — это могут делать и драйверы "в-стиле-NT", правда, к результатам такой операции следует относиться критически — по причинам, о которых ниже.)
При подключении драйвера к произвольному объекту устройства можно поступить двумя способами. Во-первых, если известно имя нужного устройства, можно получить указатель на искомый объект устройства, воспользовавшись предварительно вызовом IoGetDeviceObjectPointer (см. таблицу 9.3). Полученный указатель на искомый объект устройства (возвращаемый по адресу ppDevObj), можно применить в вызове IoAttachDeviceToDeviceStack, описанном выше.
Таблица 9.3. Прототип функции IoGetDeviceObjectPointer
NTSTATUS IoGetDeviceObjectPointer | IRQL == PASSIVE_LEVEL |
Параметры | Получает указатель на объект устройства по имени устройства |
IN PUNICODE_STRING DeviceName | Имя устройства |
IN ACCESS_MASK Access | Маска доступа: FILE_READ_DATA, FILE_WRITE_DATA или FILE_ALL_ACCESS |
OUT PFILE_OBJECT *ppFileObj | Указатель на файловый объект, которым представлен искомый объект устройства для кода пользовательского режима |
OUT PDEVICE_OBJECT *ppDevObj | Указатель на искомый объект устройства |
Возвращаемое значение |
• STATUS_SUCCESS • STATUS_Xxx — код ошибки |
Вообще говоря, Диспетчер ввода/ вывода автоматически устанавливает необходимое значение StackSize (то есть StackSize нижнего объекта плюс 1) в подключаемых к стеку объектах устройств, если это делается при помощи вызовов IoAttachDeviceToDeviceStack
или IoAttachDevice. Но в том случае, если драйвер пытается обойтись без этих вызовов, то должен установить StackSize своего объекта явным образом.
Таблица 9.4. Прототип функции IoAttachDevice
NTSTATUS IoAttachDevice | IRQL == PASSIVE_LEVEL |
Параметры | Выполняет подключение вновь созданного объекта устройства, pNewDevice |
IN PDEVICE_OBJECT pNewDevice | Указатель на подключаемый объект устройства |
IN PUNICODE_STRING TagDevName | Имя целевого устройства |
OUT PDEVICE_OBJECT *ppTagDevice | Указатель на объект устройства, к которому подключается новое устройство (точнее, указатель на место для указателя) |
Возвращаемое значение | • STATUS_SUCCESS • STATUS_Xxx — код ошибки |
В результате вызовов IoAttachDeviceToDeviceStack или IoAttachDevice
будет найден объект устройства, находящийся на вершине стека над указанным целевым объектом (по имени или по указателю). К нему и будет подключен новый объект устройства. Соответственно, разработчик, подключающий свой объект устройства к устройству в "середине" стека и надеющийся, что таким образом через его драйвер будут "протекать" IRP запросы от вышестоящих драйверов к нижестоящим, глубоко заблуждается. На самом деле, для достижения этой цели необходимо не просто выполнить подключение к нужному объекту устройства, но и сделать это в строго определенный момент загрузки — ранее, чем будет выполнена загрузка вышестоящих драйверов, чьи запросы предполагается перехватывать. Однако рассмотрение данной проблемы выходит за рамки данной книги.
Полученный указатель на объект устройства, к которому произведено подключение, следует сохранить, поскольку он может понадобиться, например, в обработчике запросов IRP_MJ_PNP, см.ниже. Это можно сделать в структуре расширения объекта устройства.
Заключительной задачей функции AddDevice драйверов модели WDM является создание символьного имени-ссылки (symbolic link name), если это необходимо, для вновь созданных и доступных устройств. Для этого используется вызов IoCreateSymbolicLink, применение которого было продемонстрировано ранее в DriverEntry, глава 3.