очереди IRP Cancel-Safe

Драйверы, реализующие собственные очереди IRP, должны использовать безопасную для отмены платформу очередей IRP . Очередь отмены безопасного IRP разделяет обработку IRP на две части:

  1. Драйвер предоставляет набор процедур обратного вызова, реализующих стандартные операции в очереди IRP драйвера. Предоставляемые операции включают вставку и удаление irP из очереди, а также блокировку и разблокировку очереди. См. раздел Реализация очереди IRP Cancel-Safe.

  2. Всякий раз, когда драйверу требуется фактически вставить или удалить IRP из очереди, он использует предоставляемые системой процедуры IoCsqXxx . Эти подпрограммы обрабатывают всю логику синхронизации и отмены IRP для драйвера.

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

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

Безопасная для отмены платформа очередей IRP входит в состав Windows XP и более поздних версий Windows. Драйверы, которые также должны работать с Windows 2000 и Windows 98/Me, могут ссылаться на библиотеку Csq.lib, которая входит в комплект драйверов Windows (WDK). Библиотека Csq.lib предоставляет реализацию этой платформы.

Подпрограммы IoCsqXxx объявляются в Windows XP и более поздних версиях Wdm.h и Ntddk.h. Драйверы, которые также должны работать с Windows 2000 и Windows 98/Me, должны содержать csq.h для объявлений.

Полную демонстрацию использования очередей IRP с безопасной отменой можно просмотреть в каталоге \src\general\cancel в WDK. Дополнительные сведения об этих очередях см. в техническом документе Поток управления для Cancel-Safe очереди IRP .

Реализация очереди IRP Cancel-Safe

Чтобы реализовать очередь IRP с безопасной отменой, драйверы должны предоставить следующие подпрограммы:

  • Любая из следующих процедур для вставки IRP в очередь: CsqInsertIrp или CsqInsertIrpEx. CsqInsertIrpEx — это расширенная версия CsqInsertIrp; очередь реализуется с помощью одного или другого.

  • Подпрограмма CsqRemoveIrp , которая удаляет указанный IRP из очереди.

  • Подпрограмма CsqPeekNextIrp , которая возвращает указатель на следующий IRP после указанного IRP в очереди. Здесь система передает значение PeekContext , полученное от IoCsqRemoveNextIrp. Драйвер может интерпретировать это значение любым способом.

  • Обе следующие процедуры позволяют системе блокировать и разблокировать очередь IRP: CsqAcquireLock и CsqReleaseLock.

  • Подпрограмма CsqCompleteCanceledIrp, которая завершает отмененное IRP.

Указатели на подпрограммы драйвера хранятся в структуре IO_CSQ , описывающей очередь. Драйвер выделяет хранилище для структуры IO_CSQ . Структура IO_CSQ гарантированно останется фиксированной, поэтому драйвер может безопасно внедрить структуру в расширение устройства.

Драйвер использует ioCsqInitialize или IoCsqInitializeEx для инициализации структуры. Используйте IoCsqInitialize , если очередь реализует CsqInsertIrp, или IoCsqInitializeEx , если очередь реализует CsqInsertIrpEx.

Драйверы должны предоставлять только необходимые функциональные возможности в каждой процедуре обратного вызова. Например, только подпрограммы CsqAcquireLock и CsqReleaseLock реализуют обработку блокировки. Система автоматически вызывает эти подпрограммы для блокировки и разблокировки очереди при необходимости.

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

CsqInsertIrpEx предоставляет более гибкий интерфейс для очереди, чем CsqInsertIrp. Драйвер может использовать возвращаемое значение, чтобы указать результат операции; Если возвращается код ошибки, вставка завершилась сбоем. Подпрограмма CsqInsertIrp не возвращает значение, поэтому простого способа указать, что вставка завершилась сбоем, не существует. Кроме того, CsqInsertIrpEx принимает дополнительный определяемый драйвером параметр InsertContext , который можно использовать для указания дополнительных сведений о драйвере, которые будут использоваться реализацией очереди.

Драйверы могут использовать CsqInsertIrpEx для реализации более сложной обработки IRP. Например, если ожидающие irP отсутствуют, подпрограмма CsqInsertIrpEx может вернуть код ошибки, а драйвер может немедленно обработать IRP. Аналогичным образом, если поставщики irP больше не могут быть поставлены в очередь, CsqInsertIrpEx может вернуть код ошибки, указывающий на этот факт.

Драйвер изолирован от всех операций отмены IRP. Система предоставляет подпрограмму отмены для IRP в очереди. Эта подпрограмма вызывает CsqRemoveIrp , чтобы удалить IRP из очереди, и CsqCompleteCanceledIrp для завершения отмены IRP.

На следующей схеме показан поток управления для отмены IRP.

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

Базовая реализация CsqCompleteCanceledIrp выглядит следующим образом.

VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {
  Irp->IoStatus.Status = STATUS_CANCELLED;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

Драйверы могут использовать любой из примитивов синхронизации операционной системы для реализации подпрограмм CsqAcquireLock и CsqReleaseLock . Доступные примитивы синхронизации включают спиновые блокировки и объекты мьютексов.

Ниже приведен пример того, как драйвер может реализовать блокировку с помощью спиновых блокировок.

/* 
  The driver has previously initialized the SpinLock variable with
  KeInitializeSpinLock.
 */

VOID CsqAcquireLock(PIO_CSQ IoCsq, PKIRQL PIrql)
{
    KeAcquireSpinLock(SpinLock, PIrql);
}

VOID CsqReleaseLock(PIO_CSQ IoCsq, KIRQL Irql)
{
    KeReleaseSpinLock(SpinLock, Irql);
}

Система передает указатель на переменную IRQL в csqAcquireLock и CsqReleaseLock. Если драйвер использует спиновую блокировку для реализации блокировки очереди, драйвер может использовать эту переменную для хранения текущего IRQL при блокировке очереди.

Драйверы не обязаны использовать спин-блокировки. Например, драйвер может использовать мьютекс для блокировки очереди. Описание методов синхронизации, доступных для драйверов, см. в разделе Методы синхронизации.

Использование очереди IRP Cancel-Safe

Драйверы используют следующие системные процедуры при постановке в очередь и выходе из очереди irPs:

  • Одно из следующих средств для вставки IRP в очередь: IoCsqInsertIrp или IoCsqInsertIrpEx.

  • IoCsqRemoveNextIrp для удаления следующего IRP в очереди. При необходимости драйвер может указать значение ключа.

На следующей схеме показан поток управления для IoCsqRemoveNextIrp.

схема, иллюстрирующая поток управления для iocsqremovenextirp.

  • IoCsqRemoveIrp для удаления указанного IRP из очереди.

На следующей схеме показан поток управления для IoCsqRemoveIrp.

схема, иллюстрирующая поток управления для iocsqremoveirp.

Эти подпрограммы, в свою очередь, передаются в подпрограммы, предоставляемые водителем.

Подпрограмма IoCsqInsertIrpEx предоставляет доступ к расширенным функциям подпрограммы CsqInsertIrpEx . Он возвращает значение состояния, возвращенное csqInsertIrpEx. Вызывающий объект может использовать это значение, чтобы определить, был ли IRP успешно поставлен в очередь. IoCsqInsertIrpEx также позволяет вызывающей объекту указать значение параметра InsertContext объекта CsqInsertIrpEx.

Обратите внимание, что ioCsqInsertIrp и IoCsqInsertIrpEx можно вызывать в любой очереди с безопасной отменой, независимо от того, есть ли в очереди подпрограмма CsqInsertIrp или подпрограмма CsqInsertIrpEx . IoCsqInsertIrp ведет себя одинаково в любом случае. Если IoCsqInsertIrpEx передается очереди, которая содержит подпрограмму CsqInsertIrp , она ведет себя так же, как IoCsqInsertIrp.

На следующей схеме показан поток управления для IoCsqInsertIrp.

схема, иллюстрирующая поток управления для iocsqinsertirp.

На следующей схеме показан поток управления для IoCsqInsertIrpEx.

схема, иллюстрирующая поток управления для iocsqinsertirpex.

Существует несколько естественных способов использования процедур IoCsqXxx для постановки в очередь и вывода из очереди irPs. Например, драйвер может просто ставить irP в очередь для обработки в том порядке, в котором они получены. Драйвер может ставить IRP в очередь следующим образом:

    status = IoCsqInsertIrpEx(IoCsq, Irp, NULL, NULL);

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

    IoCsqRemoveNextIrp(IoCsq, NULL);

Кроме того, драйвер может ставить в очередь и вынимать из очереди определенные irP. Подпрограммы используют непрозрачную структуру IO_CSQ_IRP_CONTEXT для идентификации конкретных irP в очереди. Драйвер помещает IRP в очередь следующим образом:

    IO_CSQ_IRP_CONTEXT ParticularIrpInQueue;
    IoCsqInsertIrp(IoCsq, Irp, &ParticularIrpInQueue);

Затем драйвер может высвоить одно и то же IRP с помощью значения IO_CSQ_IRP_CONTEXT .

    IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

Драйверу также может потребоваться удалить IRP из очереди на основе определенного критерия. Например, драйвер может связать приоритет с каждым IRP, чтобы irP с более высоким приоритетом сначала вывели из очереди. Драйвер может передать значение PeekContextв IoCsqRemoveNextIrp, которое система передает обратно драйверу при запросе следующего IRP в очереди.