Отмена запросов ввода/вывода
Всякий раз, когда запрос ввода/вывода удерживается драйвером
в течение продолжительного отрезка времени, драйвер должен быть готов
к отмене данного запроса. В случае закрытия потока диспетчер ввода/вывода
пытается отменить все запросы ввода/вывода, отправленные этим потоком
и еще не завершенные. Пока все такие запросы ввода/вывода не будут завершены
устройством, ему не придет запрос IRP_MJ_ CLOSE, и, следовательно, не
освободится объект-файл, а впоследствии - и само устройство (драйвер никогда
не получит запрос DriverUnload).
Для обеспечения отмены запроса ввода/вывода в пакете IRP, представляющем
такой запрос, должен быть указан адрес диспетчерской точки входа драйвера,
собственно отменяющей запрос. Для этого служит функция IoSetCancelRoutine().
PDRIVER_CANCEL loSetCancelRoutine(IN
PIRP Irp,
PDRIVER_CANCEL CancelRoutine) ;
Где функция CancelRoutine() имеет такой же прототип, как и все диспетчерские функции.
VOID CancelRoutine(IN PDEVICE_OBJECT
DeviceObject,
IN PIRP Irp) ;
Она вызывается на уровне IRQL DISPATCH_LEVEL в случайном контексте потока, однако перед ее вызовом происходит захват специальной системной спин-блокировки. До тех пор, пока системная спин-блокировка не будет освобождена, функция CancelRoutine() работает на уровне IRQL DISPATCH_LEVEL. Уровень IRQL, на который нужно перейти после освобождения блокировки указывается при вызове IoReleaseCancelSpinLock() (см. ниже). Принцип реализации функции следующий:
- Если указанный пакет IRP не может быть отменен или не принадлежит драйверу, то надо освободить системную спин-блокировку и завершить работу функции.
- В противном случае:
- 1. удалить пакет IRP из любых очередей, в которых он присутствует;
- 2. установить функцию отмены IRP в NULL с помощью IoSetCancelRoutine();
- 3. освободить системную спин-блокировку;
- 4. установить в IRP поле loStatus.Information равном 0, а поле loStatus.Status равном STATUS_CANCELLED;
- 5. завершить IRP с помощью loCompleteRequest().
Системная спин-блокировка отмены IRP освобождается с помощью loRelease CancelSpinLock():
VOID IoReleaseCancelSpinLock(IN KIRQL Irgl);
Где: Irql - уровень IRQL, на который система должна вернуться после освобождения спин-блокировки. Это значение хранится в IRP в поле Cancellrql.
Отмена IRP и Системная Очередь
Пример функции отмены IRP драйвера, использующего системную очередь, показан в следующем листинге. Необходимо отметить, что для удаления IRP из системной очереди используется функция KeRemoveEntryDeviceQueue() так, как это показано в листинге.
VOID Cancel(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
// Обрабатывается ли отменяемый запрос в данный момент?
if (Irp == DeviceOb]ect->Current!rp)
{
// Да. Освободить системную спин-блокировку и указать
// диспетчеру ввода/вывода начать обработку следующего
// пакета. Отмена IRP - в конце функции
loReleaseCancelSpinLock(Irp->CancelIrql);
loStartNextPacket(DeviceOb]ect, TRUE); }
else {
// Нет. Отменяемый IRP находится в очереди. // Удалить его из очереди
KeRemoveEntryDeviceQueue(SDeviceOb]ect->DeviceQueue, &Irp->Tail.Overlay.DeviceQueueEntry);
loReleaseCancelSpinLock(Irp->CancelIrql); }
// Отменить IRP
Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information
= 0; loCompleteRequest(Irp, IO_NO_INCREMENT); return; }
Отмена IRP и очереди, управляемые драйвером
VOID Cancel(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{ '
PIRP irpToCancel;
PDEVICE_EXT devExt;
KIRQL oldlrql;
// обнулить указатель на функцию отмены loSetCancelRoutine(Irp, NULL);
// Освободить системную спин-блокировку // как можно быстрее
loReleaseCancelSpinLock(Irp->CancelIrql); devExt = DeviceObject->DeviceExtension;
. // Захватить спин-блокировку доступа к очереди, // удалить IRP и освободить
// спин-блокировку KeAcquireSpinLock(&devExt->QueueLock, soldlrql);
RemoveEntryList(&Irp->Tail.Overlay.ListEntry); KeReleaseSpinLock(&devExt->QueueLock,
oldlrql) ;
// Отменить IRP
Irp->IoStatus Status = STATUS_CANCELLED; Irp->IoStatus.Information
= 0; loCompleteRequest(Irp, IO_NO_INCREMENT);