Программирование драйверов Windows

Роль драйверных слоев в модели 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 объекту сопоставлен драйвер, выполняющий определенную работу (иначе &#8212 зачем их создавать?). Эти фильтрующие объекты верхнего и нижнего уровня могут существовать в любом количестве.
Назначение их в том, чтобы модифицировать или обогатить процесс обработки запросов ввода/вывода возможностью использования всего результирующего стека объектов устройств. Следует отметить, что FDO и FiDO объекты отличаются только в смысловом отношении &#8212 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

Стек устройств
Понимание концепции стека устройств важно для того, чтобы правильно описать, когда вызывается функция AddDevice конкретного устройства. Общий алгоритм, используемый для загрузки драйверов и вовлечения в работу функции MyAddDeviceRoutine, описывается следующей последовательностью:

  • Во время инсталляции операционной системы, операционная система обнаруживает и составляет список (enumerate) всех шин в Системном Реестре (System Registry). Кроме того, детектируется и регистрируется топология и межсоединения этих шин.


  • Во время процесса загрузки производится загрузка шинного драйвера для каждой известной системе шины.


    Как правило, Microsoft поставляет все шинные драйверы, однако могут быть установлены и специализированные драйвера для патентованных шин данных.


  • Одна из первоочередных задач шинного драйвера состоит в том, чтобы составить перечень (enumerate) всех устройств, подключенных к шине. Объект PDO создается для каждого обнаруженного устройства.


  • Для каждого обнаруженного устройства в Системном Реестре определен класс устройств (class of device), который определяет верхний и нижний фильтры, если таковые имеются, так же, как и драйвер для FDO.


  • В случае если фильтрующий драйвер или FDO драйвер еще не загружены, система выполняет загрузку и вызывает DriverEntry.


  • Функция AddDevice вызывается для каждого FDO, которая, в свою очередь, вызывает IoCreateDevice и IoAttachDeviceToDeviceStack, обеспечивая построение стека устройств (device stack).


  • Рис. 9.3

    Шинные и не-шинные FDO
    Функция IoAttachDeviceToStack вызывается из AddDevice для того, чтобы разместить FDO в вершине (на текущий момент) стека устройств. Прототип функции IoAttachDeviceToStack описывается в таблице 9.2.

    Таблица 9.2. Прототип функции IoAttachDeviceToDeviceStack

    PDEVICE_OBJECT IoAttachDeviceToDeviceStack IRQL == PASSIVE_LEVEL
    Параметры Выполняет подключение вновь созданного объекта устройства, pNewDevice, к стеку устройств
    IN PDEVICE_OBJECT pNewDevice Указатель на подключаемый к стеку объект (созданный в данном драйвере)
    IN PDEVICE_OBJECT pOldDevice Указатель на объект устройства, к которому подключается новое устройство
    Возвращаемое значение • Указатель на устройство, бывшее на вершине стека до данного вызова

    • NULL (в случае ошибки, например, если драйвер целевого устройства еще не загружен)
    Возвращаемый вызовом IoAttachDeviceToDeviceStack указатель может отличаться от переданного значения pOldDevice, например, по той причине, что над объектом устройства pOldDevice уже размещены объекты устройств, предоставленные фильтр-драйверами.

    Как видно из таблицы 9.2, для подключения данного объекта устройства (по указателю pNewDevice) необходимо владеть указателем на целевой объект устройства (pOldDevice).


    Прекрасна ситуация, когда драйвер подключает свой объект устройства к родительскому объекту устройства (шинного драйвера), указатель на который поступает в процедуру AddDevice при вызове через заголовок (pPDO, см. таблицу 9.1). Но что делать, если имеется желание подключить новый объект устройства к объекту устройства другого драйвера, отличающегося от pPDO? (Заметим, что подключение к стеку устройств не есть исключительное право процедуры AddDevice драйверов WDM модели &#8212 это могут делать и драйверы "в-стиле-NT", правда, к результатам такой операции следует относиться критически &#8212 по причинам, о которых ниже.)

    При подключении драйвера к произвольному объекту устройства можно поступить двумя способами. Во-первых, если известно имя нужного устройства, можно получить указатель на искомый объект устройства, воспользовавшись предварительно вызовом 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 &#8212 код ошибки
    Вызов IoGetDeviceObjectPointer может быть интересен и тем, что драйвер мог бы адресовать IRP запросы непосредственно искомому объекту устройства (при помощи IoCallDriver), создавая IRP пакеты с размером стека StackSize+1, где значение StackSize получено из найденного объекта устройства.



    Вообще говоря, Диспетчер ввода/ вывода автоматически устанавливает необходимое значение 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 &#8212 код ошибки
    Второй способ подключения к стеку устройств через объект устройства с известным именем осуществляется при помощи вызова IoAttachDevice, прототип которого представлен в таблице 9.4.

    В результате вызовов IoAttachDeviceToDeviceStack или IoAttachDevice

    будет найден объект устройства, находящийся на вершине стека над указанным целевым объектом (по имени или по указателю). К нему и будет подключен новый объект устройства. Соответственно, разработчик, подключающий свой объект устройства к устройству в "середине" стека и надеющийся, что таким образом через его драйвер будут "протекать" IRP запросы от вышестоящих драйверов к нижестоящим, глубоко заблуждается. На самом деле, для достижения этой цели необходимо не просто выполнить подключение к нужному объекту устройства, но и сделать это в строго определенный момент загрузки &#8212 ранее, чем будет выполнена загрузка вышестоящих драйверов, чьи запросы предполагается перехватывать. Однако рассмотрение данной проблемы выходит за рамки данной книги.

    Полученный указатель на объект устройства, к которому произведено подключение, следует сохранить, поскольку он может понадобиться, например, в обработчике запросов IRP_MJ_PNP, см.ниже. Это можно сделать в структуре расширения объекта устройства.

    Заключительной задачей функции AddDevice драйверов модели WDM является создание символьного имени-ссылки (symbolic link name), если это необходимо, для вновь созданных и доступных устройств. Для этого используется вызов IoCreateSymbolicLink, применение которого было продемонстрировано ранее в DriverEntry, глава 3.


    Содержание раздела