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

В былые времена разработчик драйвера


В былые времена разработчик драйвера мог изучить новую аппаратуру, интерфейс общения операционной системы и драйверов, окинуть все это критическим взором, сосредоточиться и создать драйвер. Хорошо это или плохо, но пора монолитных драйверов практически прошла. Сегодня разработчик драйверов должен изучить архитектуру аппаратных шин, архитектуру многослойной подсистемы ввода/вывода для того лишь, чтобы определить мотивы выбора конструкции драйвера. Определение, какого типа драйвер следует написать, уже само по себе важное решение. Выбор, следует ли заново реализовать или можно повторно использовать какой-нибудь уже имеющийся в подсистеме ввода/вывода слой, является еще одним важным решением.

Если взять самый крупный план, то можно разделить драйверы, используемые Windows NT 5, на две группы: драйверы пользовательского режима и драйверы режима ядра. Первые, как подразумевает их название, являются системным программным кодом, функционирующим в пользовательском режиме. В качестве примера можно назвать драйверы-симуляторы (виртуализаторы) для воображаемой аппаратуры или новых исполнительных подсистем (WOW, POSIX, OS/2). Так как Windows 2000 не допускает непосредственной работы с аппаратурой для кода пользовательского режима, то такие драйверы должны полагаться в этой части на драйверы, работающие в режиме ядра. Предположим, в примере главы 3 (драйвер Example.sys) была бы создана DLL, которая работала бы в пользовательском режиме и общалась бы с драйвером на манер приложения ExampleTest. В свою очередь, если бы пользовательские приложения обращались бы к ней (а не к помощи функций CreateFile, DeviceIoControl

и т.п.) в те моменты, когда желали бы обратиться к драйверу, то тогда такая DLL и была бы типичным драйвером пользовательского режима.

Драйверы режима ядра (kernel-mode drivers) целиком состоят из кода системного уровня, выполняющегося в режиме ядра. Поскольку коду режима ядра разрешено работать непосредственно с аппаратурой (мы это видели в коде примера главы 3 при обработке IOCTL запроса IOCTL_TOUCH_PORT_378H), такие драйверы имеют прямой доступ к управлению устройствами, содержащимися в компьютере или подключенными к компьютеру.
Разумеется, ничто не может помешать такому драйверу представлять вымышленную аппаратуру &#8212 воля разработчика, где этим заниматься &#8212 в пользовательском режиме или режиме ядра.

Ограничившись категорией драйверов режима ядра, чему и посвящена данная книга, перемещаемся как раз в код режима ядра. На этом уровне можно выполнить деление драйверов еще на две категории: наследованные (legacy, доставшиеся как наследство от Windows NT 3.5, 4) и WDM драйверы. Пример драйвера Example.sys, рассмотренный в предыдущей главе, как раз и является примером legacy драйвера. Он использует функции заголовочного файла ntddk.h, скомпилирован без директивы DRIVERTYPE=WDM, не зарегистрировал ни одной процедуры типового WDM драйвера, не выполнил подключение объекта своего устройства к родительскому объекту и не реализует свои запросы путем обращения к стеку устройств. Драйверы типа legacy (если не говорить о задачах проникновения в режим ядра с задачами чистого программирования или исследования) предназначены для работы с теми устройствами, которые не поддерживают PnP спецификацию, поскольку только разработчик драйвера знает, как различить его присутствие в системе, не говоря уже о приемах работы с ним.

К счастью, практически все знания, касающиеся наследуемых драйверов NT, полностью применимы к модели WDM, по которой можно построить драйверы, работающие в Windows 2000/XP/Server 2003 (и Windows 98, Ме). Правда, как мы видели на примере Example.sys, и некоторые экземпляры драйверов типа legacy ("в-стиле-NT") могут работать во всех перечисленных ОС.

Способность драйверов WDM работать по PnP спецификации включает в себя: участие в управлении энергоснабжением системы, автоматическое конфигурирование устройства и возможность его "горячего" подключения. Корректно написанный WDM драйвер может быть использован и под Windows NT 5.x и под Windows 98/Ме, хотя Microsoft не гарантирует бинарной совместимости (простого переноса .sys файла в другую ОС, как это получилось с Example.sys).


B большинстве случаев все еще необходима перекомпиляция под Windows 98 DDK.

Наследуемые и WDM драйверы можно разделить также и на другие три категории: высокоуровневые, средне и низкоуровневые драйверы. Как подразумевает эта классификация, высокоуровневые драйверы зависят от драйверов среднего и низкого уровня в выполнении своих задач. Драйверы среднего уровня (intermediate, промежуточные), соответственно, в своей работе зависят от функционирования драйверов низкого уровня (low-level drivers).

К высокоуровневым драйверам, например, относятся драйверы файловых систем (file system drivers, FSDs). Такие драйверы предоставляют инициаторам запросов нефизическую абстракцию получателя, и уже эти запросы транслируется в специфические запросы к лежащим ниже драйверам. Необходимость в создании высокоуровневых драйверов возникает тогда, когда основные услуги аппаратуры уже реализованы драйверами нижнего уровня, и требуется только создать новую фигуру абстрагирования, которая необходима для предъявления инициатору запросов (клиенту драйвера).

Фирма Microsoft поставляет комплект программного обеспечения Installable File System (IFS) Kit, который распространяется отдельно от MSDN или других продуктов. Пакет IFS Kit требует наличия пакета DDK (и некоторых других средств) для того, чтобы заняться разработкой файловой системы всерьез. При этом существуют многочисленные ограничения на то, какие типы файловых систем могут быть получены при помощи этого пакета (IFS Kit). Дополнительную информацию по пакету IFS Kit можно получить на интернет-сайте Microsoft.

Драйверы среднего уровня могут быть проиллюстрированы такими примерами, как драйверы зеркальных дисков, классовые драйверы (class drivers), мини-драйверы (mini drivers) и фильтр-драйверы (filter drivers). Эти драйверы позиционируют себя между высокоуровневыми абстракциям высокоуровневых драйверов и средствами физической поддержки на нижних уровнях. Например, драйвер зеркальных дисков получает запрос от высокоуровневого драйвера файловой системы (FSD), транслирует этот запрос в два запроса к двум разным дисковым драйверам более низкого уровня.


При этом нет никакой необходимости в том, чтобы кто-нибудь из драйверов верхнего или нижнего уровней был в курсе, как на самом деле произошла "зеркализация" и была ли она вообще.

Драйверы класса являются возможностью повторного использования кода в пределах драйверной модели. Так как много драйверов определенного типа могут иметь много общего, то программный код, описывающий общие места, может быть помещен в общий для данного типа классовый драйвер, отдельно от специфичного для обслуживания конкретных устройств кода. Например, драйверы HID (human interface device, устройства ввода по шине USB) используют такие сходства. Драйверы специфических HID устройств могли бы быть, в такой ситуации, реализованы как мини-драйверы, взаимодействующие с классовыми драйверами. Мини-драйверы отличаются тем, что они, как правило, общаются только с другими драйверами верхнего уровня (представляющими для них оболочку), не выходя на "прямой контакт" с Диспетчером ввода/вывода. Мини-драйвер и его клиент наверху имеют заранее обусловленный протокол общения, и обычно мини-драйвер экспортирует набор функций своего интерфейса по запросу драйвера-оболочки, после чего возможно общение между драйверами минуя Диспетчер ввода/вывода, что существенно ускоряет работу.

Фильтр-драйверы (filter drivers) являются драйверами среднего уровня, которые позиционирует себя во время загрузки над или под интересующим их драйвером и перехватывают запросы, идущие к нему или от него. Фильтр-драйверы, как правило, предназначены для модификации запроса к существующему драйверу или для реализации некоей дополнительной функции, изначально не заложенной в существующем драйвере (в простейшем случае это может быть подсчет пропущенных IRP пакетов). В операционной системе фильтр-драйверы с большой долей вероятности можно опознать по отсутствию имен у объектов устройств, созданных такими драйверами (при использовании программы DeviceTree).

Наконец, документация DDK упорно внедряет такую категорию (по отношению к WDM драйверам) как функциональные драйверы (functional drivers), причем эти драйверы могут быть либо классовыми, либо мини-драйверами.


Что имеет в виду документация DDK, когда вводит термин "функциональный"? Дело в том, что такие драйверы всегда работают как интерфейс между абстрактным запросом ввода/вывода и кодом низкоуровневого физического драйвера, "физического" &#8212 в том смысле, что он связан непосредственно с устройством и в его функционировании хорошо просматриваются особенности этого устройства. Характерна в данном случае следующая деталь. Когда типовой WDM драйвер нормального PnP устройства собирается подключить себя к стеку устройств (это должно происходить в процедуре AddDevice), он выполняет подключение функционального объекта устройства (FDO), созданного им самим, к физическому объекту устройства (PDO), предоставленному ему родительским драйвером. Как правило, родительским является драйвер шины, который первоначально обнаружил подключение данного устройства, инициировав затем обращение к рассматриваемому WDM драйверу устройства. Вот здесь и проявляется функциональность последнего: он получает общие функциональные запросы, а превращает их в низкоуровневые (например, в URB запросы для устройств USB), понятные шинному драйверу и устройству, олицетворяя при этом для своего клиента функции устройства, а не его конструкцию и внутреннюю логику.


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