[Previous] [Next]

Словарь разработчика драйвера

Впервые открывая документацию, поставляемую вместе с пакетом Microsoft DDK, новичок неожиданно обнаруживает, что понять ее практически невозможно - периодически на форумах в Интернете возникают удивленные сообщения об этом феномене. Между тем, никакого феномена нет. Документация DDK - это весьма лаконичное "издание" справочного характера (с коварным множеством "любезных" переходов по ссылкам), и изучение предмета по нему во многом похоже на изучение медицины по энциклопедии.

Для русскоязычного читателя-новичка, даже хорошо владеющего языком оригинала, положение усугубляется тем, что для многих терминов отсутствуют устоявшиеся отечественные аналоги. Особенно печально положение некоторых терминов, представленных в оригинале словосочетаниями. Порой, при самом добросовестном переводе так и не получается хорошего определения, поскольку добросовестный 'пословник' дает смысловые оттенки, как раз затрудняющие понимание внутренней логики предмета обсуждения (как, например, это происходит с термином 'dispatch routine').

Словарь терминов, который приводится ниже, призван уравнять шансы читателя в борьбе со сложностью материала и дать стартовые сведения новичкам. Статьи этого мини-словаря расположены в порядке, при котором более заметны внутренние взаимосвязи терминов. Некоторые термины, как, например 'IRP', переведены, но далее в книге будут использоваться в оригинальном виде, который компактнее выглядит и обеспечивает привыкание к синтаксису будущего программного кода.

Abstraction

Абстракция, представление сложного предмета искусственно созданным формальным описанием. Абстракция позволяет отойти от рассмотрения некоторых излишне конкретных вопросов реализации своего прототипа. Абстракции, вводимые Microsoft DDK, возникли, главным образом из стремления облегчить переносимость кода на другие аппаратные платформы (обеспечение HAL) и стремления уберечь разработчика драйвера от необходимости вникать в тонкости постоянно меняющихся версий аппаратного обеспечения на каждой конкретной платформы (например, объект адаптера позволяет абстрагироваться от реализаций контроллеров DMA).

Structure

Структура, тип данных языка С. Состоит из простых типов данных (char, int и т.п.) и вложенных структур или объединений (union), Например, тип данных LARGE_INTEGER иногда (файл ntdef.h) определяется как структура, состоящая из одного поля:

typedef struct _LARGE_INTEGER
{
	LONGLONG QuadPart;
} LARGE_INTEGER;

Union

Объединение, тип данных языка С. Состоит из простых типов данных (char, jnt и т.п.) и вложенных структур или объединений. Реализует доступ к одной и той же области памяти, как к данным разных типов. Например, тип данных LARGE_INTEGER может быть определен (в файле ntdef.h это выполняется при помощи условной компиляции) следующим образом:

typedef union _LARGE_INTEGER
{
	struct { ULONG LowPart;
		 LONG HighPart;
	       } u;
	LONGLONG QuadPart;
} LARGE_INTEGER; 

Тип данных LARGE_INTEGER используется, например, в вызове KeSetTimer для установки таймера. Для того чтобы облегчить установку значений такого типа, можно применять вызов RtlConvertLongToLargeInteger, который скроет от разработчика, как конкретно реализован тип LARGE_INTEGER.

LARGE_INTEGER interval = RtlConvertLongToLargeInteger(100*10); 

Здесь и далее обращения к системным функциям (типа RtlXxx, KeXxx, loXxx, read, CreateFile и т.п.) будут называться вызовами, а в тексте они будут обозначаться жирным шрифтом.

Object

Объект. В программировании драйверов объект всегда является структурой или объединением, с которым связана одна из абстракций. Например, объект устройства - это всего лишь структура языка С. Однако она заполнена такими данными и на нее возложена такая логическая нагрузка, что все это позволяет говорить об этой структуре — почти что — как об устройстве.

В режиме ядра имеются трудности с реализацией трюков С++ (оператора new, позднего связывания, идентификации типов во время выполнения и виртуальных методов), следовательно, и основных приемов объектно-ориентированного программирования (ООП). Соответственно, и объекты здесь "ненастоящие". Объекты режима ядра роднит с "настоящими" объектами (в смысле ООП) практически только одно обстоятельство: к каждому объекту прилагается набор функций, и фирма Microsoft рекомендует работать с объектами ядра только при помощи этих специализированных функций. Этим Microsoft достигает решения трех задач. Во-первых, скрывается внутренняя структура объектов (которая в будущем может модифицироваться разработчиком операционной системы или иначе реализовываться на разных платформах). Во-вторых, становится возможным ограничить пределы вмешательства программиста в жизнь ядра, что разработчик ОС считает потенциально опасным. В третьих, программист действительно делает меньше ошибок.

В качестве объектов в ядре Windows реализовано много концепций, например, существуют объекты процессов и потоков, объекты процедур отложенного вызова, объекты драйверов и устройств, объекты синхронизации и т.п.

Kernel mode

Режим ядра. Привилегированный режим, в котором разрешено выполнять ответственные инструкции (команды процессора). Если приложение пользовательского режима в Windows 98 попытается выполнить инструкции

mov dx,0378h
out dx,ах 

то не случится ничего страшного. Но после такого поступка в Windows XP на экране монитора непременно появится сообщение, подобное следующему:

Рис. 1.1
Исключение при выполнении привилегированных инструкций пользовательским приложением..

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

Программирование в режиме ядра имеет существенные особенности, прежде всего, это касается ответственности и необходимости обращать внимание на те вопросы, которые в пользовательском режиме не возникают. Например, на каком уровне приоритета IRQL более оптимально выполнять отдельные рабочие операции и в какой тип области памяти размещать рабочий буфер? Образно говоря, программирование в режиме ядра отличается от программирования в пользовательском режиме так же, как жизнь на высокогорье отличается от жизни на равнине.

User mode

Пользовательский режим. Непривилегированный режим, в котором выполняются обычные приложения, не имеющие возможности получать доступ к системным данным иначе, как обращаясь к вызовам функций подсистем (например, Win32, GDI, Posix), которые, в свою очередь, прибегают к помощи системных вызовов.

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

Callback, callback function

Функция обратного вызова, callback функция. Прием программирования, когда один программный поток сообщает адрес известной ему функции другому программному потоку с той целью, чтобы второй выполнил вызов этой указанной функции в определенный момент (например, если поступил некий сигнал от аппаратного обеспечения или истек определенный интервал ожидания). Процесс передачи адреса вызываемой функции часто называется регистрацией.

Context

Контекст. Этот термин употребляется программистами для обозначения двух существенно различающихся явлений.

    1. Когда производится регистрация callback функции, производится определения параметра, который она получит в качестве аргумента при вызове. Как правило, это указатель на буфер с данными, которые callback функции необходимо знать, чтобы ориентироваться, зачем же ее вызвали. В драйверах чаще всего в роли контекста (контекстного указателя) выступает указатель на структуру описания устройства или структуру описания расширения устройства, например при регистрации DpcForISR (см. ниже). Именно так чаще всего и следует трактовать словосочетание "in device context" - в контексте устройства, иными словами: применительно к данному устройству.

    2. Структура, отражающая состояние программного потока на данный момент. Создаваемая операционной системой Windows для каждого потока, служит, например, для запоминания состояния регистров и некоторой другой информации на момент окончания последнего по времени кванта времени, выделенного потоку. Структура контекста потока режима ядра несколько проще, чем контекста потока пользовательского режима. Для драйвера к контексту (по сути этого понятия) следует отнести также состояние объектов, которыми он владеет, IRP пакеты (см. ниже) в очереди, к которым он может получить доступ, и даже состояние обслуживаемого устройства.

Вольное, но все еще допустимое, значение слова "контекст" подразумевает некие признаки потока, вызвавшего одну из процедур драйвера (Driver Routine), которые перешли на вызванный код. С этой точки зрения, код драйвера режима ядра может выполняться в одном из трех контекстов:

Приняв это определение, несложно понять, почему становится возможным благополучное разрешение следующей ситуации. Предположим, в некотором драйвере рабочая (dispatch) процедура, предназначенная для обработки IOCTL запросов от пользовательских приложений, получает при методе буферизации METHOD_NEITHER виртуальный адрес буфера с пользовательскими данными (или для пользовательских данных). Этот адрес поступит в драйвер в том самом виде, как он был виден в пользовательском приложении (с адресом меньше 0x80000000, например, 0x00012A0). Однако этот виртуальный пользовательский адрес имеет смысл только в адресном пространстве вызывающего пользовательского приложения (такое же число в качестве адреса в другом приложении пользовательского режима указывало бы на совершенно другую область памяти) - и это одно из неотъемлемых свойств виртуальной адресации. Должен произойти крах системы?

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

Routine

Процедура. Строго говоря, под процедурой в программировании (начиная с "древнего" языка Fortran) понимается модуль, получающий через заголовок параметры и ничего не возвращающий, в отличие от функции. В языке С таких традиционно понимаемых процедур нет - он привык обходиться одними функциями (правда, "старого типа" процедуру легко можно представить функцией типа void). В результате "высвободилось" слово 'процедура'. Разработчики драйверов (как и многие программисты С и С++) стали позволять себе следующую вольность: вместо слова "функция" произвольно применяются и "функция", и "процедура" (впрочем, как и слово "вызов"). Поэтому, встречая в тексте книги слово "процедура", следует его понимать исключительно так: функция языка С. То же относится и ко всей документации на английском языке (относительно слова "routine").

ISR, Interrupt Service Routine

Процедура обслуживания прерываний. Функция, которую драйвер регистрирует для того, чтобы она получала управление в момент, когда аппаратура, обслуживаемая драйвером, передала сигнал прерывания. Задача этой функции выполнить некоторую самую минимальную работу и зарегистрировать callback функцию, называемую процедурой отложенного вызова для обслуживания прерывания (часто обозначается именем DpcForISR, однако автор драйвера может дать ей любое имя). Если учесть, что в операционной системе Windows на типовом компьютере ежесекундно "происходит" от 100 до 600 прерываний, то станет понятно, почему так вредно задерживаться на высоких приоритетных уровнях, которые имеют ISR функции.

DpcForISR, Deferred Procedure Call for Interrupt Service Routine

Процедура отложенного вызова для обслуживания прерываний. Функция, которую драйвер регистрирует в момент работы ISR-процедуры для выполнения основной работы при получения сигнала прерывания от устройства. Сама процедура ISR работает при очень высоком приоритете, так что задержка внутри нее может привести к серьезной деградации системы, поэтому длительные операции следует "скидывать" процедуре DpcForISR (далее в тексте практически всегда будет использоваться именно это имя для данной функции, однако автор драйвера может присвоить ей любое название).

Deferred Procedure Call

Процедура отложенного вызова. Функция, которая будет вызвана позже (можно считать, "в более спокойной обстановке"). Из таких функций составляется очередь, чем ведает Менеджер (Диспетчер) ввода/вывода. Одно из применений этого типа функций уже упомянуто выше (DpcForISR), o некоторых других будет рассказано чуть позже. Для учета DPC процедур операционная система поддерживает DPC объекты.

IOManager

Менеджер ввода/вывода. Другой вариант перевода - Диспетчер ввода/вывода (ДВВ, Диспетчер BB). Является облаком кода (по аналогии с электронным облаком в физике), работающего в режиме ядра и относящегося к основным компонентам операционной системы. Ведает вопросами ввода/вывода, в частности, связывая приложения пользовательского режима с собственно драйверами. Все общение ДВВ с драйверами происходит исключительно при помощи вызова его процедур и передачи им (через заголовок) стандартизованной структуры данных IRP, в которой сформулирована вся суть его обращения к драйверу.

Помимо ДВВ в операционной системе выделяются и другие "сгустки" кода, которые обслуживают какую-нибудь часть ее ресурсов и называются Менеджер или Диспетчер, например, Менеджер Памяти, Диспетчер Объектов, Менеджер Энергопотребления (Power Manager), PnP Менеджер, Менеджер Безопасности (Security Manager) и т.п.

IRP, Input/output Request Packet, IRP request, IRP packet

Пакет запроса на ввод/вывод. IRP запрос, IRP пакет. Равноценные переводы, поскольку обращение ДВВ к драйверу есть запрос посредством IRP пакета, a IRP пакет не имеет смысла, кроме как в контексте обращения с каким-то запросом к драйверу (либо от ДВВ, либо от другого драйвера, но, опять-таки, при посредничестве ДВВ). Сам по себе, пакет IRP есть структура, состоящая из фиксированной части и изменяющейся части, носящей название стека IRP или стека ввода/вывода (IO stack). В случае, если драйверы поручают друг другу обработку запроса, то информация, которая меняется при "блуждании по инстанциям", сохраняется в этой изменяемой части IRP.

IO stack location

Ячейка стека ввода/вывода в пакете IRP. Одна позиция в изменяемой части пакета IRP, называемой стеком ввода/вывода IRP пакета. Ячейка стека сама является составной структурой. Если драйверы объединены в цепочку (называемую стеком устройств, Device Stack(1)), то, как правило, число ячеек стека IRP равно числу устройств в Device Stack перед данным устройством (возможны варианты). Собственно, ячейки стека IRP и предназначены для хранения "переменной" информации при хождении пакета IRP по стеку драйверов (или стеку устройств - на некотором этапе разработчики драйверов перестают делать различия между этими словосочетаниями). Однако хотя информация и "переменная", но имеет вполне определенный формат. При путешествии по процедурам драйвера, те извлекают из "своей" ячейки стека ввода/вывода полезную информацию. В некоторых случаях, передавая IRP пакет вниз по стеку устройств (драйверов), драйверы могут и сохранять там, в пределах "своей" ячейки, некоторые текущие данные - если ожидают, что получат этот IRP пакет при его обратном движении по стеку устройств.

Dispatch Routines

Рабочие процедуры. Функции, которые регистрируется в вызываемой самой первой процедуре драйвера (DriverEntry). Регистрация производится путем заполнения элементов массива MajorFunction (указатель на начало этого массива DriverEntry получает косвенно через аргументы своего вызова). Индексом в этом массиве являются коды IRP_MJ_Xxx, то есть описанные числами типы пакетов IRP. Если драйвер считает необходимым обрабатывать IRP запросы какого-либо типа, то в соответствующем элементе массива MajorFunction он регистрирует соответствующую функцию (записывает ее адрес). Диспетчер ввода/вывода, ориентируясь на заполнение этого массива, вызывает нужные функции драйвера - dispatch routines. Смысл данного словосочетания - "диспетчеризуемые" процедуры драйвера (а не диспетчерские!), что правильнее будет заменить на "рабочие процедуры" (функции) драйвера. Поскольку вне драйвера важны только адреса рабочих процедур (которые и регистрирует функция DriverEntry), то все рабочие процедуры драйвера могут иметь совершенно произвольные имена. Редкие исключения составляют обязательные имена функций в некоторых специальных драйверах, например, видео.

Major IRP Code

Основной код IRP пакета. Число, которое обозначает назначение пакета IRP, а значит и основной смысл данного обращения к драйверу. В пакете Microsoft DDK каждое такое число имеет еще и символьное обозначение (установленное через #define директиву). Для наглядности в литературе всегда используются присвоенные таким образом имена. Например, код IRP_MJ_DEVICE_CONTROL имеет IRP пакет, который поступил в драйвер (разумеется, из кода Диспетчера ввода/вывода) в результате того, что пользовательское приложение вызвало функцию DeviceIoControl (см. пример драйвера в главе 3). Пакеты IRP, соответствующие вызовам функций чтения или записи (read, write) имеют коды IRP_MJ_READ, IRP_MJ_WRITE.

IOCTL

I/O ConTroL code. Код управления вводом/выводом. Позволяет обращаться к драйверу с запросами, отличающимися от операций чтения и записи в устройство (хотя и они легко реализуются через IOCTL запросы). Разработчик драйвера имеет возможность создавать свои собственные коды IOCTL. Данный код является одним из аргументов функции пользовательского режима DeviceIoControl (в приложениях пользовательского режима). Поступающий в драйвер в результате работы этой пользовательской функции и Диспетчера ввода/вывода пакет IRP будет иметь код IRP_MJ_DEVICE_CONTROL, а одним из внутренних параметров данного IRP пакета будет указанный в вызове функции DeviceIoControl код IOCTL.

Minor IRP Code

Младший код IRP пакета. Часто значение основного кода IRP пакета обозначает слишком большой диапазон проблем. Для уточнения были придуманы младшие коды IRP запросов, которым даны имена IRP_MN_Xxx (бывают случаи применения и третьего уровня детализации). Например, запрос типа IRP_MJ_PNP, который обязаны обрабатывать все драйверы PnP устройств, слишком обширен, и его конкретизирует, например, младший код IRP_MN_STOP_DEVICE.

Далее в тексте основной код IRP запроса будем называть просто код IRP, а младший код — суб-код IRP.

DriverEntry

Процедура DriverEntry. Функция драйвера, которая будет вызвана первой при его загрузке. Единственная функция драйвера, которую все разработчики предпочитают называть именно так (поскольку изменение стоит существенных и, в общем-то, бессмысленных усилий). Данная функция выполняется регистрацию основных процедур, в том числе рабочих (Dispatch Routines, см. выше). Для WDM и не-WDM драйверов задачи, которые должна решить эта функция, немного различаются. Функция DriverEntry выполняется на уровне IRQL равном PASSIVE_LEVEL и даже может быть размещена в странично организованной памяти (путем специальных директив для редактора связей, линкера).

WDM, Windows Driver Model

Драйверная модель для Windows. Ориентирована на устройства, поддерживающие спецификацию PnP (в особенности, самоидентификацию — сообщение своих идентификационных номеров — при подключении к соответствующей шине). В драйвер WDM модели введены новые рабочие процедуры, которые отражают стадии подключения-обнаружения PnP устройства, события в изменении энергоснабжения и факт отключения устройства. "Правильное" PnP устройство обнаруживается шинным драйвером (той шины, к которой оно подключается), затем о нем узнает PnP Менеджер, после чего и загружается драйвер — в соответствии с полученными идентификаторами. Если WDM драйвер "правильный", то он должным образом подключает себя к стеку драйверов на этой шине. После этого почти все в дальнейшей жизни драйвера зависит от обращения с IRP запросами к драйверам, подключившимся к стеку ранее (начиная от запросов о конфигурации шины и заканчивая запросами к "своему" устройству). Драйвер модели WDM использует только определения, доступные из файла wdm.h, в результате чего не может использовать "полупартизанские" функции прежних NT-драйверов (функции типа HalGetBusData и т.п.). Драйверы модели WDM зачастую полностью (вплоть до бинарной формы) совместимы для использования в Windows 98, Windows Me, Windows 2000, Windows XP и Windows Server 2003, хотя бывают и исключения.

Разумеется, можно не поддерживать обработку всех требуемых для PnP драйверов запросов, что вполне приемлемо для драйверов, которые нужны лишь в качестве окна в режим ядра. Однако для таких исследований проще использовать драйверы "в-стиле-NT" (NT style или legacy драйверы, о чем рассказывается ниже).

Layering

Многослойность. (Следовало бы перевести этот термин как "слоирование" — "насильственная многослойность", но такого слова нет в русском языке). Поддерживаемая моделью WDM возможность реализовывать стековое соединение между драйверами. Находясь в стеке, верхний драйвер (подключившийся к стеку позднее) имеет возможность адресовать/переадресовывать IRP запросы нижним драйверам (находящимся в стеке до него). Абстракциями, которые выступают действующими лицами в обменах запросами, на самом деле являются не драйверы, а объекты устройств - именно они соединяются в стек и являются адресатами в получении и передаче пакетов IRP.

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

AddDevice

Функция, которую должны поддерживать WDM драйверы, для того, чтобы в своем составе иметь обработчик, который будет вызван в момент обнаружения устройства. Регистрация AddDevice выполняется в момент работы DriverEntry. Сама же процедура AddDevice в WDM драйверах, как правило, выполняет такую важную работу, как создание объекта устройства и подключение его к стеку устройств в момент вызова. (Справедливости ради, следует отметить, что если не-WDM драйвер зарегистрирует процедуру AddDevice, то ничего страшного не произойдет — и ее в соответствующий момент вызовет ДВВ, правда это событие уже не будет исполнено того смысла, как это имеет место в случае WDM драйверов для PnP устройств.)

Device Instance

Физический аппаратный компонент. Экземпляр устройства, возможно, один из нескольких других однотипных устройств, обслуживаемых данным драйвером. Это может быть как геометрически большой и автономный фрагмент (монитор или привод CD-ROM), так и мелкий, физически неотделимый от других, несомненно солидных устройств внутри современного компьютера, компонентов, например, набор микросхем на материнской плате для обслуживания PCI шины.

Device Object, PDO, FDO

Объект устройства, объект физического устройства (Physical Device Object), объект функционального устройства (Functional Device Object). Абстракции, созданные для представления дееспособных единиц в драйверной архитектуре. Объекты физических устройств (далее будем обозначать их аббревиатурой 'PDO') создаются шинными драйверами, когда те находят подключенные к шине реальные (физические) устройства - для отображения самого факта их существования. Именно к PDO объектам, принадлежащих шинному драйверу, будут подключаться объекты функциональных устройств (далее будем обозначать их как 'FDO' либо 'объект-устройство') полноценных WDM драйверов. Объект FDO создается собственно драйвером обнаруженного устройства (при помощи вызова IoCreateDevice). Объект FDO может иметь свою "символьную ссылку" (symbolic link), по которой будет получать запросы от клиентов своего драйвера. Следует обратить внимание на то, что IRP запросы получает не драйвер (хотя, и он тоже), а именно FDO объект, созданный в драйвере: в каждую рабочую процедуру (Dispatch Routine) драйвера в качестве аргументов передается не только указатель на пакет IRP запроса, но и указатель на FDO, которому адресован этот запрос. В результате подключения FDO-объекта (что внутри драйвера устройства) к PDO-объекту (что внутри шинного драйвера) создается впечатление, что один драйвер подключается к другому. Отсюда и берет начало жаргонизм "стек драйверов"!

Те, кто не боится трудностей, может, например, создать в своем драйвере несколько FDO и получить для каждого символьные ссылки. Чего этим можно добиться? Например, можно предоставлять разным клиентам драйвера функционально разные услуги по доступу к обслуживаемому данным драйвером устройству — в зависимости от того, какую символьную ссылку использовал новый клиент при открытии драйвера.

Драйвер в-стиле-NT, который не обслуживает реального устройства или обслуживает не-PnP устройство, не получает никаких PDO ни от каких шинных драйверов. Однако он все равно создает FDO объект, хотя бы только для того, чтобы иметь символьную ссылку (symbolic link) для связи с внешним миром. Возможны возражения, что существуют способы, как "добраться" до драйвера и без символьной ссылки — по зарегистрированному им при помощи вызова IoRegisterDeviceInterface интерфейсу (в большинстве случаев — путь для тех, кто коллекционирует трудности), однако, в данном случае для регистрации интерфейса опять-таки необходимо иметь указатель на PDO объект! Иными словами, трудно представить себе драйвер, которому не нужен был бы свой FDO объект, — этакий живущий сам по себе обрывок кода режима ядра.

Device Extension

Расширение объекта устройства. Структура, конечный вид которой определяется автором драйвера (иными словами, он может делать в ней все что угодно). Данная структура создается в самый момент создания объекта устройства при помощи системного вызова IoCreateDevice, одним из аргументов которого является ее размер — в момент создания объекта устройства система выделяет место (в нестраничном пуле памяти) и под эту "авторскую" структуру. В программировании драйверов плохим тоном считается создание переменных, глобальных для всего кода драйвера. Взамен, рекомендуется размещать эти претендующие на глобальность данные в структуре расширении устройства. Указатель на структуру расширения можно найти в объекте FDO сразу же после его создания по вызову IoCreateDevice.

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

Symbolic Link

Символьная ссылка, символическая ссылка, строго говоря — символьная связь. В области разработки драйверов — файловый объект с особыми свойствами. Выполняя операцию по открытию такого файла (CreateFile, ZwCreateFile), клиент драйвера получает к нему доступ (в виде ненулевого дескриптора, HANDLE), причем все запросы клиента будут адресованы как раз тому FDO объекту, с которым соотнесена данная символьная ссылка (созданная по запросу драйвером в результате вызова IoCreateSymbolicLink).

Device Stack, Driver Stack

Стек устройств, стек драйверов. Страшилка для легковерного новичка, которому может показаться, что все объекты устройств (PDO и FDO) всех устройств, создаваемые в драйверах, включаются в один стек, в соответствии с многослойным подходом WDM модели. Как там только не пропадают IRP пакеты и еще остается какое-то быстродействие?!

Между тем все устройства образуют довольно объемное дерево устройств (а вовсе не единый сквозной стек!) — и его можно рассмотреть, если обратиться в программе DeviceTree, поставляемой в составе пакета Microsoft DDK. B этом дереве, например, из точки, которую можно назвать "PCI шина" вырастают ветви шины USB, шины FireWire, ISA шины, устройств SCSI протокола. На самом деле, стеком следует считать относительно короткий отрезок соответствующей ветви, начиная от драйвера и его объекта устройства, куда вошел IRP запрос (например, от пользовательского приложения), до места, где запрос окончательно был обработан. Можно считать, что в случае монолитного драйвера стека устройств нет вовсе, поскольку драйвер сам занимается обработкой всех своих IRP. В случае же WDM драйвера для устройства на шине USB — стек от объекта устройства внутри этого драйвера "дотягивается" до объекта устройства внутри драйвера хост-контроллера USB.

Стек устройств формируется динамически по мере загрузки драйверов и подключения к нему устройств. Следовательно, подключение FDO нового драйвера к какому-нибудь драйверу в середине стека по окончании его формирования означает лишь одно — это будет боковой "сучок" и никакие запросы от верхних драйверов через него не пойдут (это замечание к популярной теме фильтр-драйверов).

Monolithic Driver

Монолитный драйвер. Драйвер, который не участвует ни в одном стеке устройств, получая и завершая обработку всех поступающих IRP пакетов, самостоятельно обращаясь к своему обслуживаемому устройству. Несложно представить себе монолитный драйвер, который обслуживает старые устройства (не-PnP) или служит лишь в качестве средства доступа к функциям режима ядра (для исследовательских целей). Однако практически невозможно представить современный драйвер для устройств SCSI, USB и FireWire, который был бы реализован как монолитный в прежнем смысле этого слова. В некоторых источниках информации по драйверам предпринята попытка модификации этого понятия. В них монолитным WDM драйвером (!) называется драйвер, который сам получает свои IRP запросы и доводит их до шинного драйвера, на чем его работа по общению с устройством завершается (остается лишь перехватить обратный отклик и его интерпретировать).

Legacy Driver, NT Style Driver

Унаследованный драйвер (устаревший драйвер), драйвер в-стиле-NT. Драйвер, который не является WDM драйвером, работает не с PnP устройством (если вообще работает с реальными устройствами) и не участвует в обмене данными по поводу изменений в энергоснабжении. При компиляции использует только определения из файла ntddk.h, отчего может в полной мере пользоваться устаревшими функциями HalGetBusData, HalGetInterruptVector и т.п., но зато должен надеяться только на свои способности, поскольку у него нет могущественных прародителей в лице шинных драйверов, способных предоставить поддержку.

"Правильный" драйвер в-стиле-NT может быть запущен и остановлен при помощи программы Monitor (из состава пакета Numega Kernel Driver) или процедурами SCM Менеджера. При наличии PnP вкраплений (в частности, из-за наличия зарегистрированной процедуры для обработки IRP_MJ_PNP запросов) эта возможность исчезает.

Следует обратить внимание, что при компиляции кода с использованием ntddk.h, не только становятся недоступными отдельные функции, открытые раннее (для модели WDM), но и изменяется назначение некоторых все еще доступных функций и полей внутри доступных структур. Тем не менее, подключение такого типа драйверов к другим драйверам по-прежнему возможно (при помощи вызовов IoAttachDeviceToDeviceStack или IoAttachDevice).

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

IRQL, Interrupt ReQuest Level

Уровень приоритета выполнения. Термин, исторически доставшийся от задач по обслуживанию прерываний (IRQ), но теперь связанный не только с ними. Приоритеты, принятые для программного кода, работающего в режиме ядра. Планирование потоков с приоритетами IRQL хотя бы на 1 выше минимального (PASSIVE_LEVEL) сильно отличается от планирования потоков в пользовательском режиме. В режиме ядра поток, работающий при некотором приоритете IRQL может быть прерван только для выполнения работы потоком с более высоким IRQL. Даже поток с равным приоритетом IRQL должен дожидаться естественного окончания работы своего "равноправного коллеги". Поток может самостоятельно повысить свой IRQL, однако, величина повышения в некоторых версиях Windows не произвольна. Например, работая на уровне PASSIVE_LEVEL (0), поток может получить от операционной системы Windows XP согласие только на уровень IRQL равный 10 (для сравнения, DISPATCH_LEVEL имеет численное значение 3, a INTERRUPT_LEVEL численно равен 13).

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

Приоритеты IRQL называются еще приоритетами диспетчеризации. Лишь внутри самого низкого приоритета IRQL, равного PASSIVE_LEVEL имеется градация потоков по приоритетам планирования (scheduling), часть из которых могут иметь потоки приложений пользовательского режима.

IRQ, Interrupt Request Line

Аппаратная линия (проводник) от периферийного устройства, контроллера шины, другого процессора (в многопроцессорной системе), по которой они могут подавать сигналы о том, что им требуется обслуживание от данного процессора (микропроцессора).

DIRQL

Уровни аппаратных прерываний. Обработка сигналов прерываний (IRQ), поступающих от реальных устройств, считается важным делом и должна проходить при повышенных уровнях приоритета режима ядра (IRQL). Поэтому данным уровням дано особое название Device IRQL, то есть DIRQL. Уровни приоритетов DIRQL в системах на базе Intel занимают верхние 16 IRQL уровней в операционной системе.

Polling

Метод программирования работы с устройством, когда драйвер периодически опрашивает устройство на предмет изменения его состояния — в отличие от работы по сигналу прерывания, который может поступать от устройств, конструктивно имеющих такую способность. В частности, устройства USB конструктивно не могут генерировать сигналы прерывания, но могут сообщать о подобных желаниях в момент их опроса хост-контроллером.

Virtual Memory

Виртуальная память.

Рассмотрим пример. Предположим, некая фирма имеет оплачиваемое пространство на складе своего оптового поставщика. Учет ее имущества производится хозяином склада в следующей форме. Каждая коробка имеет двузначный номер ПК (Полка/Коробка), где К — это действительно номер коробки 0..9 на соответствующей полке, а вот с номером полки дело обстоит сложнее. Для того чтобы узнать сквозной (физический) номер полки на складе, необходимо заглянуть в журнал выделения полок фирмам и получить из него настоящий номер полки. Правда, склад в нашем примере небольшой — всего десять полок!

Неудивительной будет ситуация, если через несколько дней работы по предложенной системе коробка, относящаяся к рассматриваемой фирме, с виртуальным номером 19 будет находиться на полке 2, притом, что коробка номер 20 — уже на полке 8. Но нумерация коробок осталась непрерывной, что очень нравится владельцам коробок!

Что дает такая система? Предположим, что очередному пользователю склада понадобилось 5 свободных полок, они имеются, но не подряд. Такая ситуация не внесет никакого разлада в систему учета, и понравившаяся клиентам склада непрерывная нумерация его пространства так и останется непрерывной.

Аналогичная методика принята в современных операционных системах. Преимущества ее использования огромны. Приложениям не нужно ожидать получения непрерывных пространств памяти — достаточно наличия фрагментов стандартной длины. Более того, если какое-либо приложения имеет низкую активность, можно его виртуальную память "сбросить" в файл на жестком диске (этот процесс называется swapping), a физическую память предоставить активным приложениям. (То есть — хранить коробки в подвале, но к моменту приезда клиентов — элегантно размещать их на полках склада. При определенной сноровке можно внушить каждому клиенту с большими запасами, что склад используется только для хранения его товара.)

Помимо этого, можно относительно легко контролировать несанкционированный доступ приложений к памяти по адресам, которые им не были предоставлены.

Столь объемное лирическое отступление было выполнено ради того, чтобы описать ситуацию, в которой проявляются весьма существенные проблемы.

System Paging File

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

User Space

Пользовательское пространство памяти. Область виртуальной памяти, выделенная для работы пользовательских приложений. Обычно составляет 2 гигабайта и ограничена сверху адресом 0x79999999 (в серверной конфигурации возможны варианты, когда, в результате регулировки настроек операционной системы, приложения пользовательского режима получают возможность использовать 3 гигабайта виртуальной памяти, оставляя под системные нужды лишь 1 гигабайт). Все сказанное относится к 32-разрядным версиям Windows. B 64-разрядных версиях Windows XP и Windows Server 2003 ситуация более сложная, хотя для 32-разрядных приложений и там ничего не меняется. Подробнее вопросы адресации в 64-разрядных версиях Windows будут рассмотрены в главе 4.

Pool Memory

Память в пулах (страничном или нестраничном). Области в пространстве памяти ядра (адреса выше 0x80000000 для стандартной конфигурации системы — поскольку для сервера можно определить иначе), в которых можно динамически выделять (получать, аллокировать) и освобождать (деаллокировать) области памяти. Менеджер Памяти (Memory Manager) различает два типа пулов, к которым драйвер может получать доступ при помощи вызовов функций исполнительного блока Ex(ecutive):

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

Paged Memory, Paged Pool

Странично организованная память, страничный пул, страничная память.

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

Однако при работе в режиме ядра кода, имеющего IRQL уровень приоритета равный или выше DISPATCH_LEVEL, возникает катастрофическая ситуация. Если обстоятельства сложились так, что этот программный код должен получить доступ к виртуальной странице, сброшенной на диск, и эта страница могла бы быть размещена в физической памяти усилиями системного обработчика исключения типа "page fault", но... Но уровень IRQL обработчика ниже приоритета DISPATCH_LEVEL, и система не дает ему возможности приступить к работе, вместо этого прекращая всю работу системы! Обращаться к страничной памяти (если не предпринимать специальных мер) и производить получение новых областей категорически рекомендуется только из кода, работающего на IRQL уровнях PASSIVE_LEVEL или APC_LEVEL.

Можно, конечно, организовать собственный перехват исключения в сомнительном месте, как это сделано во фрагменте кода ниже.

__try
{
	char x=*(char*)0x0L;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
	DbgPrint("Exception detected in my driver");
}; 

Однако такое решение позволяет лишь уйти от конкретной ошибки (да и работает только на IRQL уровне PASSIVE_LEVEL). Получение доступа к нужным данным так и остается нерешенной задачей. Другое, более правильное решение предлагается ниже.

Nonpaged Memory, Nonpaged Pool

Нестранично организованная память, нестраничный пул, нестраничная память.

Виртуальная память, которая никогда не может быть перемещена системой на жесткий диск и всегда остается в физической оперативной памяти. Ценный ресурс операционной системы, которым следует распоряжаться весьма осмотрительно. К памяти данного типа можно безопасно обращаться из потоков, работающих на любом уровне IRQL.

Недостатки нестраничной памяти (помимо того, что ее не очень много) проявляются практически в одном случае — при работе с устройствами, поддерживающими режим прямого доступа к памяти (DMA или, в русском варианте, ПДП). Будь то страничная виртуальная память, либо нестраничная — соответствующие области в физической памяти могут быть разрывными. Однако работа с DMA устройствами требует физической непрерывности областей, из которых берутся или куда передаются данные при DMA операциях. Выход, который могла бы предложить функция MmAllocateContiguousMemory (она умеет выделять непрерывные области физической памяти), может лишь отсрочить момент краха, который неминуем, если все потоки в режиме ядра начнут практиковать исключительно такие методы.

Объем памяти, которую система отводит под нестраничную память, ограничен. Даже при наличии достаточного объема физической оперативной памяти Windows 2000 позволяла иметь до 660 Мбайт нестраничной памяти, а 32-разрядная версия Windows XP до 1,3 Гбайт. Использование физической памяти свыше 4 Гбайт в 32-разрядных версиях Windows (даже если это позволяет аппаратура) производится через механизм физических адресов, что определяется параметром /РАЕ (разрешающим использование Physical Address Extension через загрузку другой под-версии ядра) в файле конфигурирования процесса загрузки boot.ini, см. главу 4.

Scatter/Gather Problem

Проблема сборки/разборки адресов. Возникла в момент определения методов работы аппаратуры DMA в системах с виртуальной адресацией. В рамках общего подхода Windows, предлагается выполнять разборку непрерывной виртуальной области на локально непрерывные физические фрагменты. Результат помещается в MDL список, специально приспособленный для этого пакет данных. Драйвер получает MDL список и настраивает <своеЛ DMA устройство, так, чтобы оно могло выполнить DMA перенос в/из физически разрывной области клиентских данных (разумеется, используя информацию MDL списка) — как бы по фрагментам./p>

DMA, Direct Memory Access

Метод обмена данными между устройством и оперативной памятью без участия центрального процессора. Процесс DMA переноса данных может протекать либо под управлением самого устройства (bus-mastering DMA или first-party DMA) либо под управлением системного DМА контроллера (slave DMA или third-party DMA).

Access Violation

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

SEH, Structured exception handling

Поддерживаемая операционной системой передача управления обработчику исключений, которые возникли во время работы (runtime exceptions).

Thread, Thread Object

Программный поток ("нитка"), объект потока.

Поток является минимальной единицей исполнения и планирования в многозадачной операционной системе. Для учета потоков Windows использует объекты потоков. В режиме ядра следует планировать работу с такими потоками, чтобы обеспечивать их естественное завершение (например, сигнализируя им при помощи объектов синхронизации). Особенностью многопроцессорных систем является возможность определения, на каком из конкретных процессоров будет исполняться код потока (параметра, известного как affinity). Состояние потока описывается его контекстом.

Process, Process Object

Процесс, объект процесса.

Процесс является объектом владения ресурсами. Чтобы процесс начал работать, необходимо, чтобы в нем был запущен хотя бы один поток (при создании процесса первый, первичный, поток создается автоматически). По окончании работы всех потоков процесса пользовательского режима операционная система освобождает все занятые им ресурсы, в частности, области динамически выделенной памяти, закрывает файлы, которые программист забыл закрыть. В режиме ядра такого сервиса со стороны системы просто нет. Разработчик драйвера должен тщательно следить за освобождением более не используемых ресурсов, чтобы к моменту выгрузки драйвера (если таковая предусматривается по логике его работы) что-нибудь не осталось забытым.

Affinity

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

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

Synchronization Objects

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

В данную категорию входят События (Event), Мьютексы (Mutex, Mutual Exception — взаимное исключение), Семафоры (Semaphore), Спин-блокировки (Spin Lock) и даже объекты потоков. Не вполне объектами, но элементами синхронизации можно также считать аналог критических секций, что в режиме ядра реализуется при помощи вызовов KeEnterCriticalRegion и KeLeaveCriticalRegion.

Следует отдавать себе отчет, что владение объектами синхронизации или возможность установки состояний объектов синхронизации не ведет непосредственно к остановке или запуску какого-либо из потоков. Имеется в виду тот факт, что объект синхронизации подобен светофору на перекрестке — только тот водитель, который соблюдает правила, останавливается. Лихач же, игнорирующий правила, может проехать, игнорируя любой сигнал.

Для простой остановки выполнения кода потока (из него же самого) на некоторое время используются системные вызовы, например KeStallExecutionProcessor. Для организации запуска определенных процедур (функций) драйвера используются объекты таймеров, управление которыми выполняется при помощи соответствующих вызовов (KeSetTimer и т.п.).

Все объекты синхронизации являются объектами ядра, общими для использования и в режиме ядра, и в пользовательском режиме. Если передать в пользовательское приложение дескриптор, например, объекта события (Event Object), который был создан в драйвере, то можно организовать пробуждение пользовательских потоков в нужное время вместо того, чтобы заставлять пользовательское приложение постоянно опрашивать драйвер.

PnP Manager

PnP Менеджер, один из ключевых компонентов операционной системы, конструктивно состоящий из двух частей, PnP Менеджера, работающего в режиме ядра, и PnP Менеджера пользовательского режима. Первый взаимодействует с остальными компонентами системы и драйверами в процессе загрузки программного обеспечения, необходимого для обслуживания имеющихся в системе устройств. Его часть, работающая в пользовательском режиме, отвечает за взаимодействие с пользователем в ситуациях, требующих установки новых драйверов или настройки рабочих параметров в существующих.

Все драйверы должны обеспечивать PnP поддержку. В противном случае будут существенно ограничены PnP поддержка и управление энергопотреблением системы в целом.

Если обратиться к процессу нумерации (enumeration) устройств и воспользоваться программой DeviceTree из состава пакета DDK, то станет очевидно, что во главе этого процесса стоит именно PnP Менеджер режима ядра (см. рисунки 2.6 и 2.7).

Enumeration

Процесс перечисления (нумерации). Обозначает последовательность действий со стороны системных компонентов по обнаружению устройств, реализованных в соответствии со спецификацией PnP. Возможен вследствие того, что устройства предоставляют о себе информацию в виде идентификационных кодов по протоколам, специфичным для каждого типа шины (PCI, USB и т.д.), подробнее см. главу 11.

Enumerator

Системный компонент, осуществляющий процесс перечисления (enumeration). Как правило, в этой роли выступают шинные драйверы, которые сообщают об обнаруженных устройствах PnP Менеджеру, после чего тот либо предпринимает шаги по загрузке и старту соответствующего драйвера (который он определяет по записям в Системном Реестре), либо начинает процесс установки драйвера (если подходящих записей не обнаружено). Нумерация некоторых устройств выполняется шинными фильтр-драйверами, например, ACPI драйвером.

ACPI

Advanced Configuration and Power Interface. Абстрактный интерфейс, определяющий механизмы управления энергопотреблением и конфигурирования аппаратного обеспечения и компонентов операционной системы. Является частью так называемой "Инициативы OnNow".

ACPI Driver

ACPI драйвер. В системах с ACPI BIOS программное обеспечение HAL обеспечивает загрузку ACPI драйвера во время старта операционной системы в корне дерева устройств. Драйвер ACPI функционально прозрачен для других драйверов, которые не должны делать каких-либо допущений о наличии или отсутствии в своих стеках устройств фильтр-объектов от ACPI драйвера. В обязанности ACPI драйвера входит поддержка PnP возможностей системы и управления энергопотреблением, так что результаты работы ACPI драйвера в виде многочисленных PDO или фильтр-объектов можно найти в ветвях дерева устройств (если воспользоваться программой DeviceTree), связанных с реальной аппаратурой.

Filter Device Object

Объект устройства, создаваемый фильтр-драйвером. Имеет следующее формальное отличие — это устройство остается безымянным (драйвер не указывает его имени при создании объекта) и не имеет символьной ссылки. Этот объект устройства, как правило, не предназначен для непосредственного доступа к нему, но после должного подключения его под или над устройством основного драйвера, это фильтр-устройство пропускает через себя все IRP пакеты, на самом деле предназначенные основному драйверу.

Filter Driver

Фильтр-драйвер. Драйвер, предназначенный для выполнения дополнительных манипуляций над IRP пакетами основного драйвера (вплоть до того, что самостоятельно отвергает их), к которому он подключается в стеке либо сверху, Upper Filter, либо снизу, Lower Filter. У одного основного драйвера может быть несколько фильтр-драйверов, но они для основного драйвера как бы не существуют. Как правило, разбиение на основные и фильтр-драйвера делает сам разработчик основного драйвера, определяя порядок их установки и загрузки, обеспечивающий должную конфигурацию стека устройств в этом месте дерева устройств.

HAL, Hardware Abstraction Layer

Слой аппаратных абстракций. Слой программного обеспечения в Windows NT, который призван скрыть специфику аппаратной платформ (Intel32, Intel64, Alpha) от остальных компонентов операционной системы, обеспечивая малые затраты при переносе системы или элементов программного обеспечения. Уровень HAL предоставляет процедуры, которые позволяют абстрагироваться от аппаратных тонкостей, как, например, детали реализации шин ввода/вывода, прерываний, DMA операций и т.п.

Для не-WDM драйверов, например, уровень HAL предоставляет функции для получения информации о подключенных к шинам устройствах. Следует особо отметить процедуру HAL для выполнения отображения прерывания на конкретной аппаратной шине на общесистемный вектор прерывания с соответствующим уровнем приоритета DIRQL.

Персонально драйверам WDM остается лишь скромный набор макроопределений для доступа к портам ввода/вывода.

Предполагается, что при дотошном использовании HAL инструментария проблемы по переносимости кода на другую аппаратуру будут минимальны.

Registry

Системный Реестр. База данных разнообразных настроечных и информационных параметров (вплоть до ключа, использованного при инсталляции данного экземпляра Windows), как для приложений пользовательского режима, так и конфигурационных параметров для всей системы. Файлы, составляющие эту базу, в 32-разрядных версиях операционной системы хранятся в директории %systemroot%\System32\Config\ и защищены системой от изменений. Несмотря на то, что программисты предпочитают обходить стороной это "страшное" место, тем не менее, запись конфигурационных параметров в Системный Реестр является нормальной и рядовой практикой. Такая программа, как Internet Explorer при простом движении курсора мышки по верхней части окна (где размещается меню и кнопки) делает до трех десятков операций над Реестром.

Информация об обнаруженном PnP оборудовании и установленных драйверах также размещается в Системном Реестре.

Составляющие разделов будем называть подразделами. В каждый подраздел могут быть вложены другие подразделы, но он может иметь и собственные данные, которые имеют имя и значение. Имена данных в подразделе будем далее называть параметрами (именами параметров), а собственно данные - значениями (значениями параметров.).

Для редактирования и просмотра содержимого Системного Реестра предназначена программа редактирования Реестра, которая в Windows 2000 запускается командой regedit32, а в Windows XP и Windows Server 2003 командой regedit.

Весь Реестр разделен на разделы. Самые крупные разделы носят названия hive (улей). Различают разделы HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, HKEY_CURRENT_CONFIG, HKEY_CURRENT_USER, HKEY_USERS. (Здесь НКЕY, очевидно, образовано от HiveKEY.)

Наиболее употребительным для разработчика драйверов является раздел HKEY_LOCAL_MACHINE, который далее будет часто упоминаться в сокращенной форме как HKLM. Он содержит общую информацию об аппаратном обеспечении и операционной системе (в том числе — установленных службах и драйверах). Инсталляция не-WDM драйвера может быть сведена к созданию подраздела в HKLM\System\CurrentControlSet\Services с занесением туда трех-четырех параметров (и их значений) с последующей перезагрузкой, после чего драйвер появляется в системе.

Работа с Системным Реестром через системные функции в Windows NT требует использования кодировки Unicode.

hardware branch

Подраздел Системного Реестра HKLM\Hardware. Расширенное множество, описывающее всю аппаратуру, когда-либо установленную на данном компьютере. Подмножество этого списка, называемое hardware tree, резидентно находящиеся в оперативной памяти, содержит только реально присутствующие в системе устройства.

CurrentControlSet

Текущая работающая конфигурация. Подраздел Системного Реестра HKLM\System\CurrentControlSet, описывающий текущую конфигурацию системы, на самом деле представляет собой информацию из одного из подразделов Системного Реестра, называющихся HKLM\System\CurrentControlSet00X, где X - число от 1 до 3. Информация какого конкретно фрагмента реестра используется в качестве текущей (то есть значение X), можно определить по значению параметра Current в разделе HKLM\System\Select, который является указателем на один из подразделов HKLM\System\CurrentControlSet00X. (В самом деле, если что-то добавить или удалить в подразделе CurrentControlSet, то точно такое же изменение произойдет и в том подразделе CurrentControlSet00X, на который "указывает" параметр Current.)

LastKnownGood

Последняя работающая конфигурация. Подраздел Системного Реестра HKLM\System\CurrentControlSet00X, где X - число от 1 до 3, описывающий состояние системы, когда была выполнена полностью удачная загрузка. Какой же конкретно фрагмент реестра используется в качестве LastKnownGood (то есть значение X), можно определить по значению параметра LastKnownGood в разделе HKLM\System\Select.

Unicode

Двухбайтная кодировка символов алфавитов. Делает возможной поддержку всех языков, имеющих буквенный или слоговый алфавит. Давно и широко применяется в Windows NT. B режиме ядра существует достаточный набор функций для работы со строками Unicode.

DeviceID

Идентификатор устройства. Информация, идентифицирующая устройство. В inf-файле, используемом при инсталляции, а позже — в Системном Реестре информация об устройстве хранится в виде строки, формат которой может быть определен, например, как

 VEN_XXXX&DEV_YYYY&SUBSYS_ZZZZZZZZZ&REV_VV 

Где XXXX — идентификатор производителя (VENDOR — поставщик), YYYY — идентификатор устройства, ZZZZZZZZ и W уточняющие параметры. (Заметим, что эти числа хранятся во внутренних регистрах PnP устройства и становятся доступными при его подключении к шине.)

Драйвер шины, к которой подключается устройство, передает идентификатор устройства в PnP Менеджер по запросу IRP_MN_QUERY_ID. PnP Менеджер использует эту информацию для того чтобы определить — какой драйвер следует использовать (если он уже был до этого установлен) или начинает процесс инсталляции драйвера, в результате чего будет создан и соответствующий подраздел в Реестре.

Подробно форматы идентификационных записей будут рассмотрены в главе 12.

Class Driver

Классовый драйвер. Высокоуровневый драйвер, который представляет поддержку целого класса устройств (например, клавиатуры и мыши). При этом классовый драйвер абстрагируется от их аппаратных особенностей, поддержка которых возлагается на драйверы более низкого уровня, общение с которыми происходит при помощи IOCTL запросов, callback функций или функций, экспортируемых этими драйверами нижнего уровня.

Port Driver

Порт-драйвер. Драйвер самого низкого уровня, который отвечает на стандартные системные запросы и, возможно, дополнительно — на специальные IOCTL запросы соответствующего классового драйвера. Порт-драйвер изолирует классовые драйверы от специфики аппаратуры (возможно, при помощи мини-порт-драйвеpa) и синхронизирует их операции.

Вообще говоря, любой драйвер устройства, являющегося "интеллектуальным контроллером" или шинным адаптером, может считаться порт-драйвером, если он общается с хотя бы одним классовым драйвером по определенному протоколу и синхронизирует доступ к контроллеру или шине. Среди примеров — SCSI порт-драйвер, видео порт-драйвер, драйвер параллельного порта.

Minidriver

Мини-драйвер. Представляет нечто меньшее, чем "полный" драйвер (являющийся для него оболочкой), и отражает аппаратную специфику обслуживаемого устройства. Реализуется, как правило, в виде динамически загружаемой библиотеки (DLL). Вероятно, самым известным примером мини-драйвера является SCSI мини-порт-драйвер (мини-драйвер для SCSI порт-драйвера). В данном термине в глоссарии MS Windows DDK наблюдается откровенный сбой, поскольку он трактуется как DLL, которая использует (?!) классовый драйвер для завершения своих запросов.


(1) Это совершенно другое понятие, вовсе не стек ввода/вывода IRP пакета!