[Previous] [Next]

Исполнительные компоненты

Так как Исполнительные компоненты представляют базисные сервисы операционной системы Windows NT 5 (в дополнение к планированию потоков, осуществляемых ядром), их обязанности ясно очерчены.

В таблице 4.1 представлены наименования основных исполнительных компонентов операционной системы. В первом столбце указаны сокращения, обычно являющиеся первыми символами имен функций, которые данный компонент предоставляют разработчику для использования при программировании в режиме ядра. Например, функция RtlCopyMemory, являющаяся аналогом известной функции пользовательского режима memcpy, предоставляется библиотекой времени выполнения Rtl (Run Time Library).

Таблица 4.1. Исполнительные компоненты Windows NT5

Сс Диспетчер кэша
Dbg Поддержка отладки
Ex Поддержка исполняющей подсистемы Ex(ecutive)
FsRtl Библиотека времени выполнения для поддержки файловой системы (File System Run-Time Library)
Hal Диспетчер уровня аппаратных абстракций, the Hardware Abstraction Layer (HAL)
Inbv

Драйвер инициализации системы/загрузки VGA

Init Инициализация системы
Interlocked Потокобезопасное оперирование переменными
Io Диспетчер ввода/вывода Io (I/O Manager)
Kd Поддержка Kernel Debugger
Ке Подпрограммы ядра (Kernel)
Ki Обработка ядра
Ldr Загрузчик образа
Lpc Локальный вызов процедур (Local Procedure Call)
Lsa Local Security Authority
Mm Менеджер памяти (Менеджер Виртуальной памяти, VMM)
Nls Лингвистическая поддержка (National Language Support)
Nt NT Native API
Ob Менеджер объектов (Object Manager)
Pfx Обработка префиксов
Po Менеджер электропитания
Ps Поддержка процессов (Process Structure)
Rtl Библиотека времени выполнения (Run-Time Library)
Se Управление безопасностью и обеспечение привилегий
Zw Альтернативный интерфейс Native API
другие Вспомогательные функции и библиотека времени выполнения С

Наиболее важные для разработчика драйверов компоненты будут рассмотрены ниже подробнее.

Интерфейс системных служб

System Service Interface. Данный компонент обеспечивает точки перехода из пользовательского режима в код режима ядра, что позволяет пользовательским приложениям (потокам этих приложений) безопасно осуществлять вызовы системных сервисов (процедур режима ядра). В зависимости от платформы, переход из пользовательского режима к коду режима ядра может быть и простой процессорной инструкцией, и достаточно сложным переключателем контекста Save или Restore.

Менеджер (диспетчер) объектов

Object Manager. Практически все услуги, предоставляемые операционной системой, оперируют с такой распространенной абстракцией, как объекты, хотя это и не стопроцентные объекты, по некоторым признакам, известным из объектно-ориентированного программирования. Например, программа, выполняемая в пользовательском режиме, которой необходимо синхронизировать несколько собственных потоков, может запросить у операционной системы объект синхронизации Event (событие), а на самом деле — просто особым образом обслуживаемую структуру данных. Система предоставляет Event в форме системного объекта, на который из программы пользовательского режима можно ссылаться только по дескриптору (handle). Файлы, процессы, потоки, события (Events), секции памяти (Memory Sections) и даже подразделы Системного Реестра (Registry Keys) поддерживаются операционной системой как системные объекты. Все объекты создаются и уничтожаются централизованно — Менеджером Объектов. Это позволяет получить унификацию (единообразие) доступа к объектам, контроля над их временем жизни и обеспечивать безопасность и права доступа к ним.

Всем исполнительным компонентам дозволено вступать в работу только на определенном уровне приоритета IRQL — для обеспечения слаженности в их совместной работе. В результате, функции, предоставляемые этими компонентами, так же могут быть вызваны не с любого произвольного уровня IRQL. В частности, функции Менеджера объектов (например, ObDereferenceObject) следует вызывать с уровня IRQL не выше DISPATCH_LEVEL. Нарушение этого правила ставит систему в двусмысленное положение. В самом деле, поток с более высоким IRQL может ожидать окончания работы кода, который должен работать на более низком IRQL (в силу своей медлительности или других внутренних вызовов). Чтобы не приводить систему к деградации, соблюдение такого типа правил жестко контролируется операционной системой.

Менеджер конфигурирования

Configuration Manager. Менеджер конфигурирования Windows NT 5 конструирует модель всей доступной аппаратуры и всего инсталлированного программного обеспечения, которое имеется на компьютере. Для хранения образа этой модели используется база данных, хорошо известная под названием Системный Реестр. Драйверы устройств используют информацию Реестра для уточнения множества характеристик окружения, в котором им придется работать. С введением спецификации Plug and Play роль Системного Реестра для драйверов, реализованных по WDM модели, существенно снизилась.

Менеджер процессов

Process Manager. Предоставляет функции, начинающиеся с префикса Ps (например, PsGetVersion — получить версию операционной системы). Функции PsXxx могут выполняться на разных уровнях IQRL, что следует уточнять в документации DDK.

В чем заключается работа Менеджера процессов?

Процесс является средой, в которой существует (выполняется) поток. Каждый процесс определяет собственное адресное пространство и содержит элементы идентификации для определения прав доступа (security identity). Важно отметить, что в Windows процесс не выполняется; выполняется поток, который является единицей выполнения, в то время, как процесс является "фигурой" собственности. Процесс владеет одним или несколькими потоками.

Менеджер процессов Windows 2000/XP/2003 является исполнительным компонентом, который управляет созданием процесса и предоставляет ему окружение, в котором работают программные потоки. Менеджер Процессов в своей работе опирается, главным образом, на другие исполнительные компоненты (например, Менеджера Объектов и Менеджера виртуальной памяти), так что можно сказать, что он представляет верхний уровень абстрагирования над другими системными сервисами более низкого уровня.

Драйверы редко контактируют непосредственно с Менеджером процессов. Как правило, они опираются на другие системные сервисы для доступа к среде процесса (process environment). Например, драйвер хочет быть уверен, что буферная область памяти в области адресов, находящихся во владении процесса, удерживается в течение всего времени передачи данных. Системные процедуры с префиксом Mm (относящиеся к Менеджеру памяти) предоставляют драйверу средства для такого удержания.

Менеджер виртуальной памяти

Virtual Memory Manager, VMM. В операционной системе Windows 2000/XP/2003 (впрочем, как и в Windows 9x) адресное пространство процесса является непрерывным и непрерывно адресуемым (flat). Размер адресуемого таким образом пространства в 32 разрядной версии Windows составляет 4 ГБ (2 в степени 32), что соответствует использованию 32 разрядного указателя. При этом только нижние 2 ГБ доступны для использования кодом пользовательского режима. Программный код и данные пользовательских программ должны размещаться в этой нижней половине адресного пространства. В случае, если пользовательские программы используют совместно динамически подключаемые библиотеки (DLL), то этот библиотечный код также должен размещаться в первых двух гигабайтах адресного пространства (эта схема претерпевает некоторые изменения — в сторону увеличения пользовательского адресного пространства до 3 ГБ — лишь в Enterprise Server при его соответствующей настройке).

Верхние 2 ГБ адресного пространства каждого процесса содержат код и данные, доступ к которым возможен только из программного кода, выполняющегося на уровне ядра. Верхние 2 ГБ используются кодом уровня ядра совместно от процесса к процессу. Если вы выполните распечатку адресов каких-нибудь объектов, переменных или процедур в окне DebugView или DebugPrint, то сразу же увидите, что код драйвера отображается на адресное пространство выше 2 ГБ (адреса превышают 0x80000000).

Менеджер памяти (VMM) осуществляет управление памятью от имени всей операционной системы. Для обычной программы пользовательского режима это означает выделение памяти и управление адресным пространством и физической памятью ниже границы 2 ГБ. В том, достаточно обычном, случае, когда процессу пользовательского режима не хватает физической памяти, VMM создает иллюзию наличия памяти путем виртуализации запроса. Необходимая память выделяется страницами (то есть блоками соответствующего размера, для Intel платформы — 4 КБ) на жестком диске (что называется — paging). По мере необходимости доступа к ней со стороны потоков процесса выделенные страницы перемещаются в физическую память. Таким образом, физическая память становится совместно используемым ресурсом всех процессов.

Менеджер Виртуальной Памяти выступает также и в роли ответственного за распределение памяти в том смысле, что управляет "кучей" (heap area) для программного кода уровня ядра. Для своих нужд драйверы через соответствующие вызовы (например, вызов ExAllocatePool, в конечном счете, обрабатываемый VMM) могут запросить выделение областей памяти в страничной (то есть организованной странично и допускающей сброс на жесткий диск) или нестраничной памяти.

Средства локальных процедурных вызовов

Local Procedure Call (LPC), локальный процедурный вызов, является механизмом вызовов между процессами на одном компьютере. Так как меж-"процессный" вызов (interprocess call) может проходить между разными адресными пространствами, то существуют и соответствующий Исполнительный компонент, который делает это действие возможным и эффективным. Как правило, драйверному коду не требуется осуществление вызова другого процесса и, соответственно, LPC средства.

Диспетчер (менеджер) ввода/вывода

Диспетчер ввода/вывода (I/O Manager), ДВВ, является исполнительным компонентом, который реализован в виде множества процедур уровня ядра. ДВВ представляет для процессов пользовательского режима единообразный подход к операциям ввода/вывода. Предлагаемая модель не делает различий, получает ли пользовательский процесс доступ к клавиатуре, коммуникационному порту или файлу на диске — способ обращения оформлен одинаково.

ДВВ представляет запросы от процессов пользовательского режима драйверным процедурам в форме пакета запроса на ввод/вывод, то есть пакета IRP. Пакет IRP является своего рода рабочим рецептом, созданным ДВВ, который передается в драйверные процедуры. Работа же драйвера состоит в том, чтобы должным образом этот запрос обработать. Большая часть данной книги как раз посвящена правильной организации драйверного кода, обрабатывающего IRP пакеты.

Фактически, Диспетчер ввода/вывода является интерфейсом между кодом пользовательского режима и драйверами устройств. Таким образом, он является первым и важнейшим системным компонентом, с которым взаимодействует драйвер.

При посредничестве Диспетчера ввода/вывода с драйвером могут общаются и компоненты уровня ядра, например, могут обращаться другие драйверы (вспомним, хотя бы, случай с программой Debug Print Monitor, глава 2).

Расширения базовой операционной системы

Исполнительные компоненты Windows 2000/XP/2003 определяют и представляют основные сервисы операционной системы. Однако эти сервисы никогда не предоставляются программам пользовательского режима непосредственно. Вместо этого разработчики из Microsoft определили несколько интерфейсов прикладного программирования (Application Programming Interfaces), при помощи которых код пользовательского режима может обращаться к абстракциям системных служб.

Эти интерфейсы формируют различные среды (environmental subsystems), в которых и обитают прикладные программы. В настоящее время в Windows NT 5 представлены:

Каждое приложение однозначно связано с одной средой выполнения. Приложения не могут осуществлять API вызовы к другим исполнительным средам. Кроме того, подсистема Win32 является основной в 32-разрядных версиях Windows NT 5.x. Другие подсистемы эмулируют соответствующие свойства реализуемых сред через средства и методы Win32. Соответственно, параметры выполнения программ в этих средах деградируют и существенно уступают аналогичным программам для Win32.

Подсистема Win32

В качестве основного для Windows 2000/XP/Server 2003 интерфейса API (32-разрядных версий), подсистема Win32 ответственна за:

Так как подсистема Win32 имеет особый статус среди остальных подсистем, а вследствие этого к ней предъявляются повышенные требования, то и реализация этой подсистемы существенно отличается. В частности, подсистема Win32 разделена на несколько компонентов, часть из которых работает пользовательском режиме, а другая в режиме ядра. Функции Win32 можно разделить на три категории:

Со времен NT 4.0 большая часть функций первых двух категорий из приведенной выше классификации была реализована в режиме ядра. Пользовательские процессы, которые запрашивают услуги GUI, обращаются непосредственно к коду режима ядра при использовании System Service Interface (Интерфейса Системных Служб). Код, представляющий эти функции и работающие в режиме ядра, локализован в модуле WIN32K.SYS.

Функции третьей категории при обработке запросов от пользовательских процессов опираются на стандартный серверный процесс CSRSS.exe (Client-Server Runtime Subsystem), который и обращается собственно к коду исполнительных компонентов для завершения обработки этих обращений.

Другие существенные компоненты операционной системы

В дополнение к подсистемам, реализующим среду выполнения кодов DOS, Windows, POSIX и OS/2, имеется еще несколько ключевых системных компонентов, которые реализованы как процессы пользовательского режима. Среди них:

Компоненты обслуживания операций ввода/вывода, работающие в режиме ядра

Цели разработки подсистемы ввода/вывода

Подсистема ввода/вывода вносит корректировки в список задач новых систем Windows, но особенно следует отметить:

Помимо уже указанных задач, существует и насущная необходимость обеспечения повторного использования кода (так называемой реентерабельности, повторной входимости в код). Это означает необходимость не только сложного структурирования собственно кода, отвечающего за операции ввода/вывода в одном устройстве, но и другого взгляда на взаимодействие слоев всей подсистемы ввода/вывода. Например, код, отвечающий за управление шиной, должен быть реализован автономно от кода, отвечающего за конкретное устройство, подключенное к шине. Выполнение этого требования позволяет повторно использовать код шинного драйвера программными блоками (например, драйверами), отвечающими за другие устройства на этой шине, которые, кстати сказать, могут подключаться, и удаляться, и, весьма возможно, пока еще и вовсе не изготовлены.

Типы драйверов Windows NT5

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

Если взять самый крупный план, то можно разделить драйверы, используемые 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), такие драйверы имеют прямой доступ к управлению устройствами, содержащимися в компьютере или подключенными к компьютеру. Разумеется, ничто не может помешать такому драйверу представлять вымышленную аппаратуру — воля разработчика, где этим заниматься — в пользовательском режиме или режиме ядра.

Ограничившись категорией драйверов режима ядра, чему и посвящена данная книга, перемещаемся как раз в код режима ядра. На этом уровне можно выполнить деление драйверов еще на две категории: наследованные (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, когда вводит термин "функциональный"? Дело в том, что такие драйверы всегда работают как интерфейс между абстрактным запросом ввода/вывода и кодом низкоуровневого физического драйвера, "физического" — в том смысле, что он связан непосредственно с устройством и в его функционировании хорошо просматриваются особенности этого устройства. Характерна в данном случае следующая деталь. Когда типовой WDM драйвер нормального PnP устройства собирается подключить себя к стеку устройств (это должно происходить в процедуре AddDevice), он выполняет подключение функционального объекта устройства (FDO), созданного им самим, к физическому объекту устройства (PDO), предоставленному ему родительским драйвером. Как правило, родительским является драйвер шины, который первоначально обнаружил подключение данного устройства, инициировав затем обращение к рассматриваемому WDM драйверу устройства. Вот здесь и проявляется функциональность последнего: он получает общие функциональные запросы, а превращает их в низкоуровневые (например, в URB запросы для устройств USB), понятные шинному драйверу и устройству, олицетворяя при этом для своего клиента функции устройства, а не его конструкцию и внутреннюю логику.

Специальные драйверные архитектуры

Microsoft предлагает специфические драйверные архитектуры для нескольких типов или классов устройств, а именно:

Рассмотрение этих архитектур выходит за рамки задач данной книги, поэтому ограничимся лишь данным перечислением.