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

       

Создание IRP пакетов вызовами IoBuild(A)SynchronousFsdRequest


Как уже, наверное, понял читатель, пакеты IRP можно создавать с нуля (обладая только областью памяти достаточного размера), но можно и прибегнуть к помощи рекомендованных системных вызовов IoBuildSynchronousFsdRequest, IoBuildAsynchronousFsdRequest и IoBuildDeviceControlRequest. Первые два вызова предназначены для конструирования IRP пакетов с кодами IRP_MJ_READ, IRP_MJ_WRITE, IRP_MJ_FLUSH_BUFFERS и IRP_MJ_SHUTDOWN, вполне пригодные для использования во всех драйверах, несмотря на устрашающий суффикс Fsd. Последний из этих вызовов, IoBuildDeviceControlRequest, предназначен для конструирования таких IRP пакетов, как если бы они были инициированы пользовательским API вызовом DeviceIoControl, то есть с кодом IRP_MJ_DEVICE_CONTROL или IRP_MJ_INTERNAL_DEVICE_CONTROL

Таблица 9.13. Описание прототипа функций IoBuild(A)SynchronousFsdRequest



PIRP IoBuildSynchronousFsdRequest PIRP IoBuildAsynchronousFsdRequest IRQL == PASSIVE_LEVEL

IRQL &#60= DISPATCH_LEVEL

Параметры Построение IRP пакета (выделение памяти и настройка полей)
IN ULONG MajorFunction • IRP_MJ_PNP или

• IRP_MI_READ или

• IRP_MJ_WRITE или

• IRP_MJ_FLUSH_BUFFERS или

• IRP_MJ_SHUTDOWN

IN PDEVICE_OBJECT pTargetDevice Объект устройства, которому отдается IRP
IN OUT PVOID pBuffer Адрес буфера данных ввода/вывода
IN ULONG uLenght Размер порции данных в байтах
IN PLARGE_INTEGER StartingOffset Смещение в устройстве, где начинается/продолжается операция ввода/вывода
Только для IoBuildSynchronousFsdRequest:

IN PREVENT pEvent

Объект события, используемый для сигнализации об окончании ввода/вывода (должен быть инициализирован к моменту вызова). Объект переходит в сигнальное состояние, когда нижний драйвер завершил обработку данного IRP пакета.
OUT PIO_STATUS_BLOCK Iosb Для получения завершающего статуса операций ввода/вывода
Возвращаемое значение • Не NULL &#8212 адрес нового пакета IRP

• NULL &#8212 невозможно создать новый IRP

Число ячеек, создаваемых в стеке ввода/вывода, размещающемся в пакете IRP, равно значению, указанному в поле pTargetDevice-&#62StackSize.
В данном случае нет простого способа создать дополнительную ячейку в стеке пакета IRP собственно для самого вызывающего драйвера.

Значения аргументов Buffer, Length и StartingOffset требуются для операций чтения и записи. Для операций flush и shutdown они должны быть установлены равными 0.

Нужные значения в области Parameters ячейки стека, соответствующей нижнему драйверу, устанавливаются автоматически, то есть нет необходимости передвигать указатель стека. Для запросов чтения или записи эти функции еще выделяют системное буферное пространство или выполняют построение MDL &#8212 в зависимости от того, выполняет ли вызываемое устройство (по указателю pTargetDevice) буферизованный или прямой ввод/вывод. При буферизованных операциях вывода производится также копирование содержимого буфера инициатора вызова в системный буфер, а в конце операции буферизованного ввода данные автоматически копируются из системного буфера в буферное пространство инициатора вызова.

Здесь общие черты этих двух функций заканчиваются. Начинаются различия.

Как следует из названия функции IoBuildSynchronousFsdRequest, она работает синхронно. Другими словами, поток, который выполняет вызов IoCallDriver, прекращает свою работу до тех пор, пока не завершится операция ввода/вывода в нижних драйверных слоях. Для более удобной реализации такой блокировки, в создаваемый пакет IRP в виде аргумента передается адрес инициализированного объекта события (event object). Затем, после передачи созданного пакета драйверу нижнего уровня (вызовом IoCallDriver) следует использовать функцию KeWaitForSingleObject &#8212 для организации ожидания перехода этого объекта синхронизации в сигнальное состояние. Когда драйвер нижнего уровня завершит обработку данного пакета IRP, Диспетчер ввода/вывода переведет данный объект события в сигнальное состояние, что и "разбудит" данный драйвер в нужный момент. Аргумент Iosb позволяет получить информацию о том, как завершилась обработка. Заметим, что, поскольку текущий драйвер узнает о завершении обработки нового IRP пакета от функции KeWaitForSingleObject, то он



не должен
устанавливать свою процедуру завершения перед тем, как обратиться к нижнему драйверу вызовом IoCallDriver. Если же процедура завершения все-таки установлена, она всегда должна возвращать STATUS_SUCCESS.

Пакеты, созданные функцией IoBuildSynchronousFsdRequest, должны освобождаться только косвенно &#8212 в результате вызова IoCompleteRequest

после получения сигнала от объекта события, а Диспетчер ввода/вывода уже сам очистит и освободит память, занятую IRP пакетом. Это включает освобождение системных буферных областей или MDL, выделенных для использования в обработке этого IRP. Использовать IoFreeIrp нельзя, так как такой IRP пакет участвует в очереди, организованной для пакетов, ассоциированных с данным программным потоком. Применение к нему вызова IoFreeIrp ранее, чем он будет удален из данной очереди, приведет к краху системы. Кроме того, во избежание неприятностей, следует следить за тем, чтобы объект события существовал к моменту, когда Диспетчер ввода/вывода соберется перевести его в сигнальное состояние.

Соответственно, фрагмент кода, который создает синхронный IRP и адресует его объекту устройства pTargetDeviceObject в нижнем драйвере, мог бы выглядеть следующим образом:

PIRP pIrp; KEVENT Event; IO_STATUS_BLOCK iosb; KeInitializeEvent(&Event, NotificationEvent, FALSE); pIrp = IoBuildSynchronousFsdRequest(IRP_NJ_Xxx, pTargetDeviceObject, . . . &Event, &iosb); status = IoCallDriver(pTargetDeviceObject, pIrp); if( status == STATUS_PENDING ) { // Ожидаем окончания обработки в нижних слоях KeWaitForSingleObject(&Event, Executive, KErnelMode, FALSE,NULL); status = iosb.Status; } . . .

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


Процедура завершения и должна выполнить очистку IRP с освобождением выделенных ему системных буферных областей или MDL, а затем и освобождения памяти, занятой под структуру самого IRP пакета. В данном случае, процедура завершения должна возвратить значение STATUS_MORE_PROCESSING_REQUIRED. Соответствующий пример кода может выглядеть следующим образом:

. . . . . PIRP pIrp; IO_STATUS_BLOCK iosb; pIrp = IoBuildAsynchronousFsdRequest(IRP_NJ_Xxx, pTargetDeviceObject, . . . &iosb); IoSetCompletionRoutine( pIrp, (PIO_COMPLETION_ROUTINE) MyCompletionRoutine, pThisDevExtension, TRUE,TRUE,TRUE); //Чтобы целевое устройство не "растворилось" за время обработки IRP: ObReferenceObject(pTargetDeviceObject); status = IoCallDriver(pTargetDeviceObject, pIrp); ObDereferenceObject(pTargetDeviceObject); . . . . . // Процедура завершения, зарегистрированная ранее NTSTATUS MyCompletionRoutine( PDEVICE_OBJECT pThisDevice, PIRP pIrp, VOID pContext ) { // Действия по очистке IRP . . . . IoFreeIrp( pIrp ); return STATUS_MORE_PROCESSING_REQUIRED; }

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

с повышенных уровней IRQL. В этом случае они могут остановиться на неопределенно долгое время, ожидая отклика с нижних уровней. Это противоречит общей философии построения Windows NT 5. Видимо, поэтому разработчики Windows искусственно затруднили построение синхронных IRP пакетов на повышенных уровнях IRQL тем, что вызов IoBuildSynchronousFsdRequest можно сделать только с уровня IRQL, равного PASSIVE_LEVEL.

Кроме того, объект события, используемый для организации ожидания, когда же закончится обработка пакета IRP на нижних уровнях, должен использоваться с максимальной осторожностью, поскольку при использовании такого драйвера в многопоточной манере могут возникнуть сложные ситуации. Допустим, два программных потока одного и того же пользовательского процесса делают запрос на запись с использованием одного и того же дескриптора (иными словами, делают запрос к одному и тому же драйверу).Тогда рабочая процедура WriteRequestHandler выполняется в контексте первого потока и останавливается в том месте, где она желает дождаться сигнала от объекта события. Затем, та же самая процедура WriteRequestHandler, выполняемая в контексте другого потока, использует повторно тот же самый объект события для обработки другого запроса. Когда запускаются оба потока, то ни один из них не может быть уверен, чей же конкретно пакет IRP обработан, поскольку при окончании обработки любого из IRP с одинаковым успехом возникает сигнал от объекта события. Решение может состоять в том, чтобы подстраховать объект события при помощи быстрого мьютекса или даже создавать новые объекты события для каждого вновь конструируемого IRP пакета.


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