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

Удаление IRP пакетов


Как бывает и в реальной жизни, кто-то, инициировавший IRP запрос, может передумать и инициализировать снятие запроса "с повестки". Пользовательское приложение может запросить уничтожение пакета после длительного ожидания. Приложение может вовсе прекратить работу, бросив все на попечение операционной системы. Наконец, приложение может попытаться завершить свою асинхронную операцию Win32 API вызовом CancelIo.

Таблица 9.19. Прототип функции IoCancelIrp



BOOLEAN IoCancelIrp IRQL &#60= DISPATCH_LEVEL
Параметры Помечает пакет IRP как требующий удаления и вызывает процедуры удаления, если таковые определены
IN PIRP pIrp Указатель на удаляемый IRP пакет
Возвращаемое значение TRUE &#8212 если пакет удален

FALSE &#8212 в случае неудачи

В режиме ядра для удаления запроса выполняется вызов IoCancelIrp

(таблица 9.19). Операционная система также вызывает IoCancelIrp

для всех IRP пакетов, относящихся к потоку, выполнение которого прекращается.

Предположим, некий код режима ядра направил пакет (в данном случае &#8212 синхронный) другому драйверу. Как он может выполнить удаление отправленного пакета, например, в результате превышения времени ожидания? Пример ниже иллюстрирует этот случай.

// формирует синхронный пакет: PIRP pIrp= IoBuildSynchronousFsdRequest(. . ., &event, &iosb); // Подключаем процедуру завершения: IoSetCompletionRoutine( pIrp, MyCompletionRoutine, (VOID*)&event, TRUE, TRUE, TRUE ); NTSTATUS status = IoCallDriver(. . .); if( status == STATUS_PENDING ) { // Некоторое время ожидаем естественного завершения LARGE_INTEGER waitDelay; waitDelay.QuadPart = - 10000; // относительное время if( KeWaitForSingleObject( &event, KernelMode, FALSE, &waitDelay) == STATUS_TIMEOUT ) { IoCancelIrp(pIrp); KeWaitForSingleObject( &event, KernelMode, FALSE, NULL); } } // Синхронные IRP пакеты - их удаляет Диспетчер ввода/вывода: IoCompleteRequest(pIrp, IO_NO_INCREMENT); . . .

// Процедура завершения NTSTATUS MyCompletionRoutine( PDEVICE_OBJECT pThisDevice, PIRP pIrp, PVOID pContext ) { if (pIrp-&#62PendingReturned) KeSetEvent((PKEVENT) pContext, IO_NO_INCREMENT, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; }


Процедура IoCancelIrp устанавливает флаг (cancel bit) в IRP пакете и выполняет вызов процедуры CancelRoutine, если таковая имеется в IRP пакете.

Таблица 9.20. Прототип предоставляемой драйвером функции CancelRoutine

VOID CancelRoutine IRQL == DISPATCH_LEVEL
Параметры Выполняет действия, сопутствующие удалению пакета IRP
IN PDEVICE_OBJECT pDevObj Указатель на объект устройства, которое (точнее &#8212 драйвер) и зарегистрировало ранее эту функцию в IRP пакете вызовом IoSetCancelRoutine

(см. ниже)
IN PIRP pIrp Указатель на удаляемый IRP пакет
Возвращаемое значение void
Таблица 9.21. Прототип функции IoSetCancelRoutine

PDRIVER_CANCEL IoSetCancelRoutine IRQL &#60= DISPATCH_LEVEL
Параметры Устанавливает (переустанавливает) определяемую драйвером функцию CancelRoutine для данного IRP пакета
IN PIRP pIrp Указатель на IRP пакет, которому будет соответствовать устанавливаемая функция CancelRoutine
IN PDRIVER_CANCEL CancelRoutine Указатель на функцию, которая соответствует прототипу, описанному в таблице 9.20, или NULL (если следует отменить функцию CancelRoutine для данного пакета IRP)
Возвращаемое значение Указатель на ранее установленную для данного IRP пакета функцию CancelRoutine. Соответственно, если таковой не было, то возвращается NULL. Значение NULL возвращается также, если пакет находится в обработке и не может быть удален
В отличие от процедур завершения, которые могут быть указаны для каждого драйвера (в соответствующих ячейках стека IRP), место для процедуры CancelRoutine в IRP пакете единственное, что указывает на ее необычный статус &#8212 она вызывается один раз для удаляемого пакета. Соответственно, эта процедура и должна принадлежать тому драйверу, который наиболее компетентно может распорядиться судьбой IRP пакета. Правда, этот драйвер может регистрировать разные свои функции для удаления разнотипных IRP пакетов (чтения, записи и т.п.).

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


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

Код вызова IoCancelIrp устроен примерно следующим образом &#8212 по крайней мере, так уверяет Уолтер Оней:

BOOLEAN IoCancelIrp(PIRP pIrp) { IoAcquireCancelSpinLock(&pIrp-&#62CancelIrql); pIrp-&#62Cancel=TRUE; PDRIVER_CANCEL CancelRoutine = IoSetCancelRoutine(pIrp, NULL); if( CancelRoutine != NULL) { PIO_STACK_LOCATION currentCell = IoGetCurrentIrpStackLocation(pIrp); (*CancelRoutine)(currentCell-&#62DeviceObject, pIrp); return TRUE; } else { IoReleaseCancelSpinLock(pIrp-&#62CancelIrql); return FALSE; } }

Для ограничения доступа к удаляемому пакету, код IoCancelIrp, прежде всего, запрашивает объект спин-блокировки вызовом IoAcquireCancelSpinLock

(в переменной pIrp-&#62CancelIrql сохраняется значение текущего уровня IRQL для использования при последующем вызове IoReleaseCancelSpinLock). В случае, если за IRP пакетом закреплена процедура CancelRoutine, то она вызывается (теперь на нее возложена задача освобождения спин-блокировки). Если же такой процедуры нет, то вызов IoCancelIrp завершает работу, освобождая спин-блокировку.

Процедура CancelRoutine, получив управление, может оказаться в одной из описанных ниже ситуаций.

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

Irp-&#62IoStatus.Status = STATUS_IO_TIMEOUT; Irp-&#62IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT);

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


Лучшим приемом будет &# 8212 дождаться естественного развития событий, когда пакет вернется, вероятнее всего, с каким-нибудь кодом ошибки.

Выяснить, обрабатывается ли рассматриваемый пакет именно сейчас, можно при помощи следующего кода, поскольку, если задействован механизм System Queuing и какой-либо пропущенный через него IRP пакет в настоящий момент обрабатывается, то именно адрес этого IRP пакета "лежит" в поле pDeviceObject-&#62CurrentIrp (иначе там будет NULL):

VOID MyCancelRoutine ( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) { if( pIrp == pDeviceObject-&#62CurrentIrp ) { . . .

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

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

VOID StartIo ( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIRp) { KIRQL CancelIrql; IoAcquireCancelSpinLock(&CancelIrql); If(pIrp-&#62Cancel) { IoReleaseCancelSpinLock(CancelIrql); return; } // Удаляем процедуру обработки удаления, делая пакет // "not cancelable" - неуничтожаемым IoSetCancelRoutine(pIrp, NULL); IoReleaseCancelSpinLock(CancelIrql); . . . }

В случае, если удаляемый IRP пакет пока находится в системной очереди (которая называется еще "управляемая StartIo"), a перед его размещением

там (то есть вместе с вызовом IoMarkIrpPending) была зарегистрирована процедура MyCancelRoutine для этого IRP пакета, то действия по удалению такого пакета (не текущего &#8212 не находящегося в обработке) могут выглядеть следующим образом:

VOID MyCancelRoutine( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) { if( pIrp == pDeviceObject-&#62CurrentIrp ) { // Текущий IRP IoReleaseCancelSpinLock(pIrp-&#62CancelIrql); // Вряд ли можно сделать что-то еще... } else { // Удаляем из системной очереди: KeRemoveEntryDeviceQueue( &pDeviceObject-&#62DeviceQueue, &pIrp-&#62Tail.Overlay.DeviceQueueEntry); // Только теперь можно освободить спин-блокировку: IoReleaseCancelSpinLock(pIrp-&#62CancelIrql);



pIrp-&#62IoStatus.Status=STATUS_CANCELLED; pIrp-&#62IoStatus.Information = 0; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); } return; }

Приведенный ниже пример выполняет удаление пакета из очереди, поддерживаемой собственно драйвером (так называемой "Device-Managed Queue").

VOID MyOtherCancelRoutine ( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp ) { KIRQL oldIRQL; PMYDEVICE_EXTENSION pDevExt = (PMYDEVICE_EXTENSION)pDeviceObject-&#62DeviceExtension; IoSetCancelRoutine(pIrp, NULL); // Освобождаем спин-блокировку, установленную еще IoCancelIrp IoReleaseCancelSpinLock(pIrp-&#62CancelIrp);

// Удаляем IRP из очереди под защитой спин-блокировки KeAcquireSpinLock(&pDevExt-&#62QueueLock, &oldIRQL); RemoveEntryList (&pIrp-&#62Tail.Overlay.ListEntry); KeReleaseSpinLock(&pDevExt-&#62QueueLock, oldIRQL); // pIrp-&#62IoStatus.Status = STATUS_CANCELLED; pIrp-&#62IoStatus.Information = 0; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return; }

Предполагается, что объект спин-блокировки, используемый для синхронизации доступа к очереди пакетов, pDevExt-&#62QueueLock был заранее создан и сохранен в структуре расширения данного устройства.

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

в конце процедуры CancelRoutine (обработки удаления IRP пакета в данном драйвере) запускаются вызовы процедур завершения вышестоящих драйверов &#8212 если такие драйвера имеются и если соответствующие процедуры были зарегистрированы для данного IRP пакета на случай его удаления (см. описание вызова IoSetCompletionRoutine

в таблице 9.8).


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