Отмена запросов ввода/вывода

Всякий раз, когда запрос ввода/вывода удерживается драйвером в течение продолжительного отрезка времени, драйвер должен быть готов к отмене данного запроса. В случае закрытия потока диспетчер ввода/вывода пытается отменить все запросы ввода/вывода, отправленные этим потоком и еще не завершенные. Пока все такие запросы ввода/вывода не будут завершены устройством, ему не придет запрос 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. 1. удалить пакет IRP из любых очередей, в которых он присутствует;
    2. 2. установить функцию отмены IRP в NULL с помощью IoSetCancelRoutine();
    3. 3. освободить системную спин-блокировку;
    4. 4. установить в IRP поле loStatus.Information равном 0, а поле loStatus.Status равном STATUS_CANCELLED;
    5. 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);