Создание 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->StackSize; USHORT irpSize = IoSizeOfIrp(nOfRequiredStackLocs); PIO_STACK_LOCATION pTagDevIrpStackLocation;
PIRP pCreatedIrp = (PIRP) ExAllocatePool( NonPagedPool, irpSize ); IoInitializeIrp( pCreatedIrp, irpSize, nOfRequiredStackLocs);
// Получаем указатель на ячейку стека IRP, которая после вызова // IoCallDriver будет ассоциирована с нижним драйвером: pTagDevIrpStackLocation = IoGetNextIrpStackLocation( pCreatedIrp );
// Подразумевая операцию чтения, устанавливаем поля ячейки: pTagDevIrpStackLocation->MajorFunction = IRP_MJ_READ; pTagDevIrpStackLocation->Parameters.Read.Length = BUFFER_SIZE; pTagDevIrpStackLocation->Parameters.Read.ByteOffset.QuadPart = 0i64;
// B запросе IRP_MJ_READ список MDL не может использоваться. // Передаем собственный буфер в качестве системного, // требующегося при данном типе запросов: PVOID newBuffer = ExAllocatePool ( NonPagedPool, BUFFER_SIZE ); pCreatedIrp -> AssociatedIrp.SystemBuffer = newBuffer;
// Если вызываемое устройство имеет свойство (флаг) DO_DIRECT_IO: if( pTargetDevice->Flags & DO_DIRECT_IO ) { // Описание IoAllocateMdl см. в таблице 7.19. Поскольку третий // параметр равен FALSE, указатель на созданный MDL список будет // сразу занесен в поле pCreatedIrp-> MdlAddress PMDL pNewMdl = IoAllocateMdl ( newBuffer, BUFFER_SIZE, FALSE, FALSE, pCreatedIrp);
// для буфера в нестраничной памяти: MmBuildMdlForNonPagedPool( pNewMdl); }
// Копируем информацию о потоке инициатора вызова: pCreatedIrp -> Tail.Overlay.Thread = pOriginalIrp -> 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->MdlAddress );
// Освобождение специального буфера: IoFreePool ( pIrp->AssociatedIrp.SystemBuffer );
// Освобождение собственно IRP: IoFreeIrp ( pIrp );
return STATUS_MORE_PROCESSING_REQUIRED; }
![]() |
Следует обратить внимание на то, что освобождение памяти выполняется при помощи вызова IoFreeIrp, a нe ожидаемого ExFreePool. В данном случае так можно поступать потому, что Диспетчер ввода/вывода получает из определенного поля IRP информацию о том, получен ли данный пакет из нестраничного пула или из специального зонного буфера, которым распоряжается только Диспетчер ввода/вывода. |
При всей сложности самостоятельного создания IRP пакетов с нуля, в этом есть одно важное преимущество — драйвер контролирует количество создаваемых ячеек стека IRP пакета. В том числе — дополнительных, которые могут оказаться в некоторых случаях незаменимыми для хранения специфичной (для данного IRP пакета и для данного драйвера) информации в течении времени жизни этого IRP пакета.
Ниже приводятся описания прототипов использованных функций.
Таблица 9.15. Описание прототипа функции IoAllocateIrp
PIRP IoAllocateIrp | IRQL <= DISPATCH_LEVEL |
Параметры | Формирует IRP пакет с выделением памяти (не требует последующего вызова IoInitializeIRP) |
IN CCHAR StackSize | Количество ячеек стека во вновь создаваемом IRP пакете |
IN BOOLEAN ChargeQuota | FALSE |
Возвращаемое значение |
Адрес нового пакета IRP либо NULL — невозможно создать новый IRP |
VOID IoInitializeIrp | IRQL <= DISPATCH_LEVEL |
Параметры | Формирует IRP пакет в ранее выделенной области памяти (не должна использоваться для пакетов, созданных вызовом IoAllocateIrp) |
IN PIRP pIrp | Указатель на область, используемую под IRP |
IN USHORT PacketSize | Заранее вычисленный общий размер IRP пакета (можно использовать вызов IoSizeOfIrp) |
IN CCHAR StackSize | Количество ячеек стека во вновь создаваемом IRP пакете |
Возвращаемое значение | void |
Таблица 9.17. Описание прототипа функции IoFreeIrp
VOID IoFreeIrp | IRQL <= DISPATCH_LEVEL |
Параметры | Очищает и освобождает IRP пакеты, созданные вызовами IoAllocateIrp или IoBuildAsynchronousFsdRequest |
IN PIRP pIrp | Указатель на освобождаемый IRP пакет |
Возвращаемое значение | void |
или IoBuildDeviceIoControlRequest, освобождаются самим Диспетчером ввода/вывода, когда драйвер завершает обработку такого пакета вызовом IoCompleteRequest. Освобождения пакетов, сделанных нестандартными способами (например, с помощью ExAllocatePool) выполняет сам драйвер.
Таблица 9.18. Описание прототипа функции IoSizeOfIrp
USHORT IoSizeOfIrp | IRQL — любой |
Параметры | Определяет размер IRP пакета, как если бы он имел StackSize ячеек стека |
IN CCHAR StackSize | Предполагаемое число ячеек стека IRP пакета |
Возвращаемое значение | Размер в байтах |