[Previous] [Next]

Процедура DriverEntry

Процедура DriverEntry присутствует в любом драйвере и имеет данное стандартное имя. Драйверы "в-стиле-NT" выполняют в DriverEntry большую работу, нежели WDM драйвера — последние откладывают часть работы по инициализации устройства до момента обнаружения устройства системой, когда будет вызвана процедура AddDevice.

Таблица 8.1. Параметры вызова функции DriverEntry

NTSTATUS DriverEntry IRQL == PASSIVE_LEVEL
Параметры Описание
IN PDRIVER_OBJECT pDriverObject Адрес объекта драйвера
IN PUNICODE_STRING pRegistryPath Путь в регистре к подразделу драйвера
Возвращаемое значение • STATUS_SUCCESS
• STATUS_XXX — код ошибки

Получив от Диспетчера ввода/вывода указатель на структуру DRIVER_OBJECT (см. заголовочные файлы DDK ntddk.h или wdm.h), драйвер должен заполнить в ней определенные поля, а именно:

Регистрация рабочих процедур происходит обычно в виде:

DriverObject->MajorFunction[IRP_MJ_READ]= ReadWrite_IRPhandler;
DriverObject->MajorFunction[IRP_MJ_WRITE]=ReadWrite_IRPhandler;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=
	DeviceControlRoutine;
. . . . . . . . .

Однако для фильтр-драйверов модели WDM, которым интересен только определенный тип запросов, возможен следующий тип регистрации:

int i;
for( i=0; i<IRP_MJ_MAXIMUM_FUNCTION; i++)
{
	DriverObject->MajorFunction[i]= myPassIrpDown;
}
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=
	DeviceControlRoutine;  

Здесь все запросы будут приводить к вызову функции myPassIrpDown, которая занимается только лишь переадресацией запросов нижним драйверам в стеке WDM драйверов. Исключение составит функция-обработчик IOCTL запросов, которые будут поступать в функцию DeviceControlRoutine фильтр-драйвера.

Помимо регистрации функций, процедура DriverEntry драйвера "в-стиле-NT" может выполнять следующую работу:

  1. DriverEntry определяет аппаратное обеспечение, которое драйвер будет контролировать. Это аппаратное обеспечение выделяется драйверу, то есть помечается как находящееся под управлением данного драйвера.
  2. Если драйвер управляет многокомпонентным (multiunit) или многофункциональным контроллером, используется IoCreateController для создания объекта контроллера, после чего инициализируется структура расширения контроллера.
  3. Выполняет вызов IoCreateDevice для создания объекта устройства для каждого физического или логического устройства под управлением данного драйвера, в процессе которого инициализируется структура расширения устройства для каждого созданного объекта устройства. Рекомендуется сразу же после этого вызова явно установить флаги (поле Flags в объекте устройства), описывающие способ буферизации, используемый данным устройством.
  4. Созданные устройства затем делаются видимыми для приложений пользовательского режима путем выполнения вызова IoCreateSymbolicLink.
  5. Устройство подключается к объекту прерываний. В случае, если ISR процедура требуют использования объекта DPC (отложенного процедурного вызова), то он создается и инициализируется на этом этапе.
  6. Шаги с 3 по 5 повторяются для каждого физического или логического устройства, работающего под управлением данного драйвера.
  7. В случае успешного завершения, функция DriverEntry должна возвратить Диспетчеру ввода/вывода значение STATUS_SUCCESS.

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

Таблица 8.2. Параметры системного вызова IoRegisterDriverReinitialization

VOID IoRegisterDriverReinitialization IRQL == PASSIVE_LEVEL
Параметры Регистрирует функцию драйвера для отложенной инициализации
IN PDRIVER_OBJECT pDriverObject Указатель на объект драйвера
IN PDRIVER_REINITIALIZE DriverReinitializationRoutine Указатель на процедуру реинициализации, предоставляемую драйвером (см. таблицу 8.3 ниже).
IN PVOID Context Контекстный указатель, который получит регистрируемая функция при вызове
Возвращаемое значение void

Таблица 8.3. Описание параметров вызова myReinitializeFunction

VOID myReinitializeFunction IRQL == PASSIVE_LEVEL
Параметры Функция драйвера, регистрируемая для выполнения отложенной инициализации
IN PDRIVER_OBJECT pDriverObject Указатель на объект драйвера
IN PVOID Context Контекстный блок, указанный при регистрации
IN ULONG Количество вызовов процедуры ре-инициализации (отсчет от 0)
Возвращаемое значение void

Как было указано в главе 7, процедуру DriverEntry можно разместить в "отстреливающемся" сегменте кода INIT.

Процедура AddDevice

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

Процедура Unload

Как правило, однажды загруженный драйвер остается в системе до перезагрузки. Для того чтобы сделать драйвер выгружаемым, необходимо написать и зарегистрировать процедуру выгрузки Unload. Диспетчер ввода/вывода затем производит вызов этой процедуры в момент ручной либо автоматической выгрузки драйвера — как раз перед удалением драйвера из памяти.

Таблица 8.4. Описание прототипа функции Unload

VOID Unload IRQL == PASSIVE_LEVEL
Параметры Выполняет завершающие действия
IN PDRIVER_OBJECT pDriverObject Указатель на объект драйвера
Возвращаемое значение void

Хотя действия процедуры Unload могут меняться от драйвера к драйверу, общими являются следующие шаги, характерные более для драйверов "в-стиле-NT".

  1. Для некоторых типов аппаратуры необходимо сохранить ее состояние в Системном Реестре. При последующей загрузке драйвера эти данные могут быть использованы в процедуре DriverEntry. Скажем, драйвер принтера может сохранить последнее значение разрешения печати.
  2. Если прерывания разрешены для обслуживаемого устройства, то процедура выгрузки должна запретить их и произвести отключение от объекта прерываний. Ситуация, когда устройство будет порождать запросы на прерывание в то время, как объект прерываний не существует, неминуемо приведет к краху системы.
  3. Символьная ссылка должна быть удалена из пространства имен, видимого пользовательскими приложениями. Это выполняется при помощи вызова IoDeleteSymbolicLink.
  4. Объект устройства должен быть удален вызовом IoDeleteDevice.
  5. В случае, если драйвер управляет многокомпонентным (multiunit) контроллером, необходимо повторить шаги 3 и 4 для каждого устройства, подключенного к контроллеру, а затем необходимо удалить сам объект контроллера при помощи вызова IoDeleteController.
  6. Следует выполнить освобождение памяти, выделенной драйверу, во всех типах оперативной памяти.

Драйверы WDM модели выполняют практически все из описанных выше действий в обработчике IRP_MJ_PNP запросов с субкодом IRP_MN_REMOVE (то есть посвященном удалению устройства из системы).

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

Процедура выгрузки драйвера Unload не вызывается в момент отката системы, и если существует необходимость выполнять какую-либо работу при откате системы, то это следует сделать в специально предназначенной на то процедуре драйвера, зарегистрированной для обработки IRP пакетов с кодом IRP_MJ_SHUTDOWN. Объект устройства должен быть с помощью вызова IoRegisterShutdownNotification занесен в очередь объектов, получающих уведомление о перезагрузке, — только при этом условии будет вызвана процедура, зарегистрированная для обработки пакетов с кодом IRP_MJ_SHUTDOWN.

Адресация и доступ к данным в IRP пакетах чтения/записи

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

PDEVICE_OBJECT pNewDeviceObject;
IoCreateDevice(. . . , &pNewDeviceObject);
pNewDeviceObject->Flags |= DO_BUFFERED_IO;

либо:

pNewDeviceObject->Flags |= DO_DIRECT_IO; 

По умолчанию подразумевается 'pNewDeviceObject->Flags |= 0' — метод NEITHER (ни один из первых двух).

В том случае, если новый объект устройства ориентирован на работу с нижним драйвером в стеке драйверов, следует скопировать эти флаги из объекта устройства, к которому подключен данный (одним из вызовов IoAttachXxx, см. главу 9), например:

pNewDeviceObject->Flags |=
	(pUnderlyingDevObject->Flags) & (DO_BUFFERED_IO | DO_DIRECT_IO);

По традиции, главными считаются запросы в форме пакетов IRP с основным кодом IRP_MJ_READ (в результате вызова ReadFile) либо IRP_MJ_WRITE (в результате вызова WriteFile).

Диспетчер ввода/вывода, если он замечает в описании устройства установленный флаг DO_DIRECT_IO, непременно проверяет возможность доступа к буферу, который клиент драйвера указывает в своем запросе как буфер с данными (WRITE) или для данных (READ), подготавливает MDL список для него и фиксирует страницы в оперативной памяти. Адрес подготовленного таким образом MDL списка вносится в поле IRP пакета под названием MdlAddress. Если попробовать получить виртуальный адрес от данного MDL списка вызовом MmGetMdlVirtualAddress, то получится именно виртуальный адрес (пользовательского адресного пространства), который предоставило пользовательское приложение в качестве адреса буфера с выводимыми данными. Адрес в терминах системного адресного пространства для той же области данных можно получить, если вызвать MmGetSystemAddressForMdl. Когда обработка запроса ввода/вывода полностью завершена, клиентский буфер де-блокируется и удаляется из схемы распределения системной области памяти.

В том случае, если объект устройства, которому адресован IRР пакет, описан флагом DO_BUFFERED_IO, то драйвер должен взять адрес буферной области из поля IRP пакета AssociatedIrp.SystemBuffer. Данный адрес будет адресом в системном адресном пространстве. Диспетчер ввода/вывода выделяет этот буфер в нестраничной памяти после проверки на доступность предоставленного клиентом буфера. При запросе ввода данных (READ-запрос) Диспетчер ввода/вывода по окончании операции переносит данные из системного буфера в клиентский, а адрес клиентского буфера запоминается в поле IRP пакета UserBuffer. При запросе вывода данных (WRITE-запрос) в системный буфер переносятся данные из клиентского буфера (поле UserBuffer равно NULL). B обоих описанных выше случаях, предоставленные в IRP пакете адреса AssociatedIrp.SystemBuffer можно использовать в произвольном контексте в потоках режима ядра.

В том случае, если устройство не объявило признаков DO_DIRECT_IO или DO_BUFFERED_IO, то Диспетчер ввода/вывода не делает никаких особых действий, а просто помещает адрес буферной области, переданной ему инициатором запроса в поле IRP пакета UserBuffer. Оба поля Associate.SystemBuffer и MdlAddress устанавливаются равными NULL.

В последнем случае помещенный в пoлe UserBuffer виртуальный адрес является "нехорошим" адресом. В большинстве случаев инициатором обращения к драйверу является программный поток пользовательского режима. Соответственно, предоставленный им виртуальный адрес требует для своей корректной интерпретации контекст соответствующего пользовательского потока. Что, разумеется, исключает возможность использования такого адреса в произвольных контекстах режима ядра без предварительной подготовки (подготовки MDL списка и т.п.). Поэтому метод NEITHER можно рекомендовать только для драйверов самых верхних драйверных слоев.

Длина буфера во всех случаях передается в полях Parameters.Write.Length (при WRITE-запросах) или Parameters.Read.Length (при READ-запросах).