Удаление IRP пакетов
Как бывает и в реальной жизни, кто-то, инициировавший IRP запрос, может передумать и инициализировать снятие запроса "с повестки". Пользовательское приложение может запросить уничтожение пакета после длительного ожидания. Приложение может вовсе прекратить работу, бросив все на попечение операционной системы. Наконец, приложение может попытаться завершить свою асинхронную операцию Win32 API вызовом CancelIo.
Таблица 9.19. Прототип функции IoCancelIrp
BOOLEAN IoCancelIrp | IRQL <= DISPATCH_LEVEL |
Параметры | Помечает пакет IRP как требующий удаления и вызывает процедуры удаления, если таковые определены |
IN PIRP pIrp | Указатель на удаляемый IRP пакет |
Возвращаемое значение | TRUE — если пакет удален FALSE — в случае неудачи |
В режиме ядра для удаления запроса выполняется вызов IoCancelIrp
(таблица 9.19). Операционная система также вызывает IoCancelIrp
для всех IRP пакетов, относящихся к потоку, выполнение которого прекращается.
Предположим, некий код режима ядра направил пакет (в данном случае — синхронный) другому драйверу. Как он может выполнить удаление отправленного пакета, например, в результате превышения времени ожидания? Пример ниже иллюстрирует этот случай.
// формирует синхронный пакет: 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->PendingReturned) 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 | Указатель на объект устройства, которое (точнее — драйвер) и зарегистрировало ранее эту функцию в IRP пакете вызовом IoSetCancelRoutine (см. ниже) |
IN PIRP pIrp | Указатель на удаляемый IRP пакет |
Возвращаемое значение | void |
PDRIVER_CANCEL IoSetCancelRoutine | IRQL <= DISPATCH_LEVEL |
Параметры | Устанавливает (переустанавливает) определяемую драйвером функцию CancelRoutine для данного IRP пакета |
IN PIRP pIrp | Указатель на IRP пакет, которому будет соответствовать устанавливаемая функция CancelRoutine |
IN PDRIVER_CANCEL CancelRoutine | Указатель на функцию, которая соответствует прототипу, описанному в таблице 9.20, или NULL (если следует отменить функцию CancelRoutine для данного пакета IRP) |
Возвращаемое значение | Указатель на ранее установленную для данного IRP пакета функцию CancelRoutine. Соответственно, если таковой не было, то возвращается NULL. Значение NULL возвращается также, если пакет находится в обработке и не может быть удален |
Драйвер должен участвовать в схеме удаления IRP пакетов, если он реализует собственную очередь пакетов или участвует в использовании системной очереди (то есть зарегистрировал процедуру StartIo).
При этом подразумевается, что процедуре удаления подвергаются прежде всего пакеты в состоянии ожидания, то есть не выполненные сразу, а помещенные по этой причине в очередь. Пакеты IRP, которые переданы на обработку нижним драйверам и "застряли" там — это худшее, что можно придумать в момент удаления.
Код вызова IoCancelIrp устроен примерно следующим образом — по крайней мере, так уверяет Уолтер Оней:
BOOLEAN IoCancelIrp(PIRP pIrp) { IoAcquireCancelSpinLock(&pIrp->CancelIrql); pIrp->Cancel=TRUE; PDRIVER_CANCEL CancelRoutine = IoSetCancelRoutine(pIrp, NULL); if( CancelRoutine != NULL) { PIO_STACK_LOCATION currentCell = IoGetCurrentIrpStackLocation(pIrp); (*CancelRoutine)(currentCell->DeviceObject, pIrp); return TRUE; } else { IoReleaseCancelSpinLock(pIrp->CancelIrql); return FALSE; } }
Для ограничения доступа к удаляемому пакету, код IoCancelIrp, прежде всего, запрашивает объект спин-блокировки вызовом IoAcquireCancelSpinLock
(в переменной pIrp->CancelIrql сохраняется значение текущего уровня IRQL для использования при последующем вызове IoReleaseCancelSpinLock). В случае, если за IRP пакетом закреплена процедура CancelRoutine, то она вызывается (теперь на нее возложена задача освобождения спин-блокировки). Если же такой процедуры нет, то вызов IoCancelIrp завершает работу, освобождая спин-блокировку.
Процедура CancelRoutine, получив управление, может оказаться в одной из описанных ниже ситуаций.
Во-первых, рассматриваемый IRP пакет в настоящий момент обрабатывается. Если такой пакет все еще находится в одной из рабочих процедур драйвера, то он имеет право на несколько секунд жизни, после чего драйвер должен принять решение о его дальнейшей судьбе, скорее всего, завершить с ошибкой, например:
Irp->IoStatus.Status = STATUS_IO_TIMEOUT; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT);
В том случае, если пакет "застрял" в нижних слоях драйверов, то это вина нижних драйверов, которые не смогли организовать обработку данной нештатной ситуации.
Лучшим приемом будет &# 8212 дождаться естественного развития событий, когда пакет вернется, вероятнее всего, с каким-нибудь кодом ошибки.
Выяснить, обрабатывается ли рассматриваемый пакет именно сейчас, можно при помощи следующего кода, поскольку, если задействован механизм System Queuing и какой-либо пропущенный через него IRP пакет в настоящий момент обрабатывается, то именно адрес этого IRP пакета "лежит" в поле pDeviceObject->CurrentIrp (иначе там будет NULL):
VOID MyCancelRoutine ( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) { if( pIrp == pDeviceObject->CurrentIrp ) { . . .
Конкретная реализация действий по удалению текущего пакета (если она возможна) остается задачей разработчика драйвера.
Существенно проще становится ситуация, когда пакет, предназначенный для уничтожения, только что поступил в процедуру StartIo либо еще находится в очереди отложенных (pending) пакетов.
VOID StartIo ( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIRp) { KIRQL CancelIrql; IoAcquireCancelSpinLock(&CancelIrql); If(pIrp->Cancel) { IoReleaseCancelSpinLock(CancelIrql); return; } // Удаляем процедуру обработки удаления, делая пакет // "not cancelable" - неуничтожаемым IoSetCancelRoutine(pIrp, NULL); IoReleaseCancelSpinLock(CancelIrql); . . . }
В случае, если удаляемый IRP пакет пока находится в системной очереди (которая называется еще "управляемая StartIo"), a перед его размещением
там (то есть вместе с вызовом IoMarkIrpPending) была зарегистрирована процедура MyCancelRoutine для этого IRP пакета, то действия по удалению такого пакета (не текущего — не находящегося в обработке) могут выглядеть следующим образом:
VOID MyCancelRoutine( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) { if( pIrp == pDeviceObject->CurrentIrp ) { // Текущий IRP IoReleaseCancelSpinLock(pIrp->CancelIrql); // Вряд ли можно сделать что-то еще... } else { // Удаляем из системной очереди: KeRemoveEntryDeviceQueue( &pDeviceObject->DeviceQueue, &pIrp->Tail.Overlay.DeviceQueueEntry); // Только теперь можно освободить спин-блокировку: IoReleaseCancelSpinLock(pIrp->CancelIrql);
pIrp->IoStatus.Status=STATUS_CANCELLED; pIrp->IoStatus.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->DeviceExtension; IoSetCancelRoutine(pIrp, NULL); // Освобождаем спин-блокировку, установленную еще IoCancelIrp IoReleaseCancelSpinLock(pIrp->CancelIrp);
// Удаляем IRP из очереди под защитой спин-блокировки KeAcquireSpinLock(&pDevExt->QueueLock, &oldIRQL); RemoveEntryList (&pIrp->Tail.Overlay.ListEntry); KeReleaseSpinLock(&pDevExt->QueueLock, oldIRQL); // pIrp->IoStatus.Status = STATUS_CANCELLED; pIrp->IoStatus.Information = 0; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return; }
Предполагается, что объект спин-блокировки, используемый для синхронизации доступа к очереди пакетов, pDevExt->QueueLock был заранее создан и сохранен в структуре расширения данного устройства.
![]() |
Вопросы создания и поддержки очередей IRP пакетов, ведомых собственно драйвером, в данной книге не рассматриваются. Хотя этот прием достаточно широко распространен и присутствует в примерах пакета DDK. |
в конце процедуры CancelRoutine (обработки удаления IRP пакета в данном драйвере) запускаются вызовы процедур завершения вышестоящих драйверов — если такие драйвера имеются и если соответствующие процедуры были зарегистрированы для данного IRP пакета на случай его удаления (см. описание вызова IoSetCompletionRoutine
в таблице 9.8).