Шрифт:
Каждый IRP описывает операцию В/В, которая может быть выполнена устройством. Для того, чтобы драйвер смог получить информацию о том, какая именно операция должна быть выполнена, IRP содержит целый набор атрибутов: старший и младший коды функции, код статуса и различные параметры: число байт, которые должны быть прочитаны, смещение и т.п. За время своего существования IRP может проходить несколько уровней иерархии драйверов устройств в системе. Поэтому в пакете резервируется место для сохранения данных и параметров, необходимых для следующего драйвера в иерархии — так называемый "стек IRP", "IRP stack location". Когда объект устройства обрабатывает запрос, то он имеет доступ только к тем участкам стека, которые предназначены для использования им или устройством более низкого уровня, которому будет перенаправлен IRP.
Рис. 3 – Интерфейс с драйвером при помощи IRP
IRP могут создаваться как диспетчером В/В, так и самими драйверами. Чаще всего это происходит при выполнении функций CreateFile, CloseFile, ReadFile, WriteFile и DeviceControl.
IRP может быть уничтожен, если необходимо отменить операцию В/В, например, при закрытии приложения. Объект IRP содержит указатель на функцию, вызываемую при уничтожении пакета.
Объекты устройств являются экземплярами класса KDevice или KPnpDevice. Эти классы являются краеугольными камнями архитектуры DriverWorks: они представляют собой как бы программный образ тех устройств, которые присутствуют в системе. Именно объекты устройств обеспечивают управление и обмен данными с внешними устройствами, управление их ресурсами — линиями прерываний, каналами ПДП, диапазонами адресов памяти, портами В/В и т.п. Когда выполняется системный вызов типа CreateFile, ReadFile, WriteFile, диспетчер В/В посылает IRP соответствующему драйверу. Но сам драйвер, вернее объект драйвера, не выполняет никаких операций по обработке этого пакета — он просто передает его объекту устройства и забывает о самом существовании этого IRP. Это естественно, ведь управление физическим устройством — не его задача, это дело соответствующего объекта устройства.
Класс KDevice является суперклассом для всех классов устройств. Но на практике он сейчас почти не используется. Чаще используют его потомка – класс KPnpDevice. Этот класс предназначен для управления PnP-устройствами, т.е. устройствами, которые конфигурируется системой. В данный момент практически все устройства являются PnP-устройствами. Появление таких устройств здорово облегчило жизнь разработчикам драйверов: использовать KPnpDevice намного проще, а часто и безопаснее, чем KDevice. Еще бы, ведь в данном случае все проблемы конфигурирования и инициализации ресурсов оборудования ложатся на широкие плечи системы.
Любой объект устройства содержит стандартные методы обработки запросов на чтение, запись и управление устройством (device control). Эти методы вызываются при вызове соответствующих функций API ReadFile, WriteFile, DeviceControl.
Каждый драйвер содержит минимум один объект устройства. Как было упомянуто выше, объект устройства является экземпляром класса, порожденного программистом от класса KDevice или KPnpDevice. Для придания функциональности объекту устройства разработчик драйвера может переопределять виртуальные методы суперкласса, включая те методы, которые обрабатывают различные типы IRP, а также добавлять свои свойства и методы в класс. В процессе разработки можно использовать как иерархию классов DriverStudio, так и функции DDK. Впрочем, для большинства задач без использования DDK вполне можно обойтись.
Естественно, делать все это надо с осторожностью. Вызов некотроых методов является обязательным. Переопределение виртуальных методов также требует осторожности: многие из них содержат код, который обязательно должен быть выполнен. Если его удалить, то драйвер будет работать неправильно (если будет работать вообще). В результате, система скорее всего, зависнет.
Все вышесказанное, конечно, может быть очень интересным, но остается открытым вопрос: так как же все-таки драйвер, вернее объект устройства, управляет аппаратурой? Официально провозглашается, что Win2000 — переносимая система. Т.е., если она хорошо работает на архитектуре Intel, то она также может быть перенесена и на другие системы, например Alpha. Для того, чтобы система с минимальными исправлениями могла работать на компьютерах с другой архитектурой, и был введен HAL — уровень абстракции аппаратуры. Он действительно абстрагирует драйвера и большую часть кода ядра ОС от того, как именно построен компьютер. Теперь разработчику драйвера становится абсолютно все равно, как на данном компьютере реализован контроллер прерываний или контроллер прямого доступа к памяти – все ресурсы аппаратуры также представлены объектами. Это диапазоны адресов памяти и портов В/В устройства, линии прерываний и ПДП. Все они могут быть реализованы в различных архитектурах по разному, но общие принципы их работы остаются одними и теми же. Соответственно, и интерфейс классов, реализующих управление этими ресурсами, остается одинаковым. Нам, как программистам, теперь не нужно знать тонкости работы аппаратной части на этом компьютере — это задача HAL и тех людей, которые переносили систему.
На практике это означает то, что если устройство, например, имеет диапазон адресов памяти и линию запроса на прерывание, то класс устройства будет содержать два свойства (данные). Одно из них — экземпляр класса KMemoryRange, который будет реализовывать управление памятью устройства, а другое — экземпляр класса KInterrupt, который управлет линией запроса на прерывание, и всем, что с ней связано. Если устройство будет иметь несколько областей адресов памяти, то, соответственно, класс устройства будет содержать несколько экземпляров класса KMemoryRange.
Другим способом управления устройствами является наличие устройств нижнего уровня (Lower devices). Как уже было отмечено, особенностью архитектуры WDM является наличие стека драйверов, когда драйвера могут обмениваться IRP-пакетами между собой. Данную ситуацию легче объяснить при помощи рисунка:
Рис. 4 — стек устройств
На рис. 4 изображен стек устройств, состоящий из трех объектов устройств. Устройство 1 — самое первое (верхнее) в стеке, устройство 3 — самое последнее (нижнее) в стеке. Тогда по отношению к устройству 1 устройство 2 будет устройством нижнего уровня. Устройства верхнего уровня для устройства 1 нет. Устройство 2 имеет и устройство верхнего уровня (устройство 1) и устройство нижнего уровня (устройство 3). Для устройства 3 есть только устройство верхнего уровня (устройство 2), устройства нижнего уровня у него нет, устройство 3 напрямую контролирует оборудование.