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

       

Создание IRP пакетов "с нуля"


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

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

Попробуем отказаться и от этой услуги Диспетчера ввода/вывода и создать пакет IRP "совершенно с нуля" на примере IRP пакета для буферизованного ввода/вывода.

В данном случае память под IRP пакет выделяется в нестраничном пуле при помощи вызова ExAllocatePool, а затем производится инициализация необходимых полей внутри созданной области. Общая инициализация выделенной области по типу "IRP пакет" должна быть выполнена при помощи вызова IoInitializeIrp. Установка полей в той ячейке стека IRP пакета, которую будет разбирать драйвер-получатель (владеющий устройством pTargetDevice), и буферных областей для передачи данных возлагается на текущий драйвер.

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

. . . . . #define BUFFER_SIZE (1024) CCHAR nOfRequiredStackLocs = pTargetDevice-&#62StackSize; USHORT irpSize = IoSizeOfIrp(nOfRequiredStackLocs); PIO_STACK_LOCATION pTagDevIrpStackLocation;

PIRP pCreatedIrp = (PIRP) ExAllocatePool( NonPagedPool, irpSize ); IoInitializeIrp( pCreatedIrp, irpSize, nOfRequiredStackLocs);

// Получаем указатель на ячейку стека IRP, которая после вызова // IoCallDriver будет ассоциирована с нижним драйвером: pTagDevIrpStackLocation = IoGetNextIrpStackLocation( pCreatedIrp );

// Подразумевая операцию чтения, устанавливаем поля ячейки: pTagDevIrpStackLocation-&#62MajorFunction = IRP_MJ_READ; pTagDevIrpStackLocation-&#62Parameters.Read.Length = BUFFER_SIZE; pTagDevIrpStackLocation-&#62Parameters.Read.ByteOffset.QuadPart = 0i64;


// B запросе IRP_MJ_READ список MDL не может использоваться. // Передаем собственный буфер в качестве системного, // требующегося при данном типе запросов: PVOID newBuffer = ExAllocatePool ( NonPagedPool, BUFFER_SIZE ); pCreatedIrp -&#62 AssociatedIrp.SystemBuffer = newBuffer;

// Если вызываемое устройство имеет свойство (флаг) DO_DIRECT_IO: if( pTargetDevice-&#62Flags & DO_DIRECT_IO ) { // Описание IoAllocateMdl см. в таблице 7.19. Поскольку третий // параметр равен FALSE, указатель на созданный MDL список будет // сразу занесен в поле pCreatedIrp-&#62 MdlAddress PMDL pNewMdl = IoAllocateMdl ( newBuffer, BUFFER_SIZE, FALSE, FALSE, pCreatedIrp);

// для буфера в нестраничной памяти: MmBuildMdlForNonPagedPool( pNewMdl); }

// Копируем информацию о потоке инициатора вызова: pCreatedIrp -&#62 Tail.Overlay.Thread = pOriginalIrp -&#62 Tail.Overlay.Thread;

// Устанавливаем процедуру завершения обработки сформированного IRP IoSetCompletionRoutine ( pCreatedIrp, MyIoCompletionRoutine, NULL, TRUE, TRUE, TRUE );



// Передаем созданный пакет драйверу нижнего уровня IoCallDriver ( pTargetDevice, pCreatedIrp ); . . . .

Неочевидность приведенных выше манипуляций с ячейкой стека IRP пакета говорит о том, что необходимо в совершенстве владеть тонкостями формирования пакетов для тех типов запросов, которые вам захочется создавать самостоятельно.

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

NTSTATUS MyIoCompletionRoutine(IN PDEVICE_OBJECT pThisDeviceObject, IN PIRP pIrp, IN PVOID pContext ) { . . . // Очистка структуры MDL списка: IoFreeMdl( pIrp-&#62MdlAddress );

// Освобождение специального буфера: IoFreePool ( pIrp-&#62AssociatedIrp.SystemBuffer );

// Освобождение собственно IRP: IoFreeIrp ( pIrp );



return STATUS_MORE_PROCESSING_REQUIRED; }

Следует обратить внимание на то, что освобождение памяти выполняется при помощи вызова IoFreeIrp, a нe ожидаемого ExFreePool. В данном случае так можно поступать потому, что Диспетчер ввода/вывода получает из определенного поля IRP информацию о том, получен ли данный пакет из нестраничного пула или из специального зонного буфера, которым распоряжается только Диспетчер ввода/вывода.
Возможны ситуации, когда драйвер ведет собственную политику относительно выделения памяти под IRP пакеты, например, из ассоциативного списка, им же созданного. Такие IRP все равно должны быть инициализированы вызовом IoInitializeIrp, однако, применять к ним вызов IoFreeIrp нельзя, поскольку тот, скорее всего, нарушит учет памяти в драйвере.

При всей сложности самостоятельного создания IRP пакетов с нуля, в этом есть одно важное преимущество &#8212 драйвер контролирует количество создаваемых ячеек стека IRP пакета. В том числе &#8212 дополнительных, которые могут оказаться в некоторых случаях незаменимыми для хранения специфичной (для данного IRP пакета и для данного драйвера) информации в течении времени жизни этого IRP пакета.

Ниже приводятся описания прототипов использованных функций.

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

PIRP IoAllocateIrp IRQL &#60= DISPATCH_LEVEL
Параметры Формирует IRP пакет с выделением памяти (не требует последующего вызова IoInitializeIRP)
IN CCHAR StackSize Количество ячеек стека во вновь создаваемом IRP пакете
IN BOOLEAN ChargeQuota FALSE
Возвращаемое значение Адрес нового пакета IRP либо

NULL &#8212 невозможно создать новый IRP
Таблица 9.16. Описание прототипа функции IoInitializeIrp

VOID IoInitializeIrp IRQL &#60= DISPATCH_LEVEL
Параметры Формирует IRP пакет в ранее выделенной области памяти (не должна использоваться для пакетов, созданных вызовом IoAllocateIrp)
IN PIRP pIrp Указатель на область, используемую под IRP
IN USHORT PacketSize Заранее вычисленный общий размер IRP пакета (можно использовать вызов IoSizeOfIrp)
IN CCHAR StackSize Количество ячеек стека во вновь создаваемом IRP пакете
Возвращаемое значение void
<


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

VOID IoFreeIrp IRQL &#60= DISPATCH_LEVEL
Параметры Очищает и освобождает IRP пакеты, созданные вызовами IoAllocateIrp или IoBuildAsynchronousFsdRequest
IN PIRP pIrp Указатель на освобождаемый IRP пакет
Возвращаемое значение void
Как было сказано ранее, пакеты, созданные IoBuildSynchronousFsdRequest

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

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

USHORT IoSizeOfIrp IRQL &#8212 любой
Параметры Определяет размер IRP пакета, как если бы он имел StackSize ячеек стека
IN CCHAR StackSize Предполагаемое число ячеек стека IRP пакета
Возвращаемое значение Размер в байтах

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