可安全取消的 IRP 队列

实现自己的 IRP 队列的驱动程序应使用 取消安全 IRP 队列 框架。 取消安全 IRP 队列将 IRP 处理拆分为两个部分:

  1. 驱动程序提供一组回调例程,用于在驱动程序的 IRP 队列上实现标准操作。 提供的操作包括插入和删除队列中的 IRP,以及锁定和解锁队列。 请参阅 实现 Cancel-Safe IRP 队列

  2. 每当驱动程序需要实际插入或移除队列中的 IRP 时,它都使用系统提供的 IoCsqXxx 例程。 这些例程处理驱动程序的所有同步和 IRP 取消逻辑。

使用取消安全 IRP 队列的驱动程序不实现 Cancel 例程来支持 IRP 取消。

框架可确保驱动程序以原子方式在其队列中插入和删除 IRP。 它还可确保正确实现 IRP 取消。 不使用框架的驱动程序在执行任何插入和删除操作之前,必须手动锁定和解锁队列。 它们还必须避免在实现 Cancel 例程时可能导致的争用条件。 (有关可能出现的争用条件的说明,请参阅 同步 IRP Cancellation。)

Windows XP 和更高版本的 Windows 附带了取消安全的 IRP 队列框架。 还必须使用 Windows 2000 和 Windows 98/Me 的驱动程序可以链接到 Windows 驱动程序工具包 (WDK) 中包含的 Csq.lib 库。 Csq.lib 库提供此框架的实现。

IoCsqXxx 例程在 Windows XP 及更高版本的 Wdm.h 和 Ntddk.h 中声明。 还必须使用 Windows 2000 和 Windows 98/Me 的驱动程序必须包含声明的 Csq.h。

可以在 WDK 的 \src\general\cancel 目录中查看有关如何使用取消安全 IRP 队列的完整演示。 有关这些队列的详细信息,另请参阅 Cancel-Safe IRP 队列的控制流 白皮书。

实现Cancel-Safe IRP 队列

若要实现取消安全的 IRP 队列,驱动程序必须提供以下例程:

指向驱动程序例程的指针存储在描述队列 的 IO_CSQ 结构中。 驱动程序为 IO_CSQ 结构分配存储。 IO_CSQ 结构保证保持固定大小,因此驱动程序可以安全地将结构嵌入其设备扩展中。

驱动程序使用 IoCsqInitializeIoCsqInitializeEx 初始化结构。 如果队列实现 CsqInsertIrp,请使用 IoCsqInitialize;如果队列实现 CsqInsertIrpEx,则使用 IoCsqInitialize

驱动程序只需在每个回调例程中提供基本功能。 例如,只有 CsqAcquireLockCsqReleaseLock 例程实现锁处理。 系统根据需要自动调用这些例程来锁定和解锁队列。

只要提供了适当的调度例程,就可以在驱动程序中实现任何类型的 IRP 队列机制。 例如,驱动程序可以将队列实现为链接列表或优先级队列。

与 CsqInsertIrp 相比,CsqInsertIrpEx 为队列提供了更灵活的接口。 驱动程序可以使用其返回值来指示操作的结果;如果它返回错误代码,则表示插入失败。 CsqInsertIrp 例程不返回值,因此没有简单的方法来指示插入失败。 此外, CsqInsertIrpEx 采用驱动程序定义的附加 InsertContext 参数,该参数可用于指定队列实现要使用的其他特定于驱动程序的信息。

驱动程序可以使用 CsqInsertIrpEx 来实现更复杂的 IRP 处理。 例如,如果没有挂起的 IRP, CsqInsertIrpEx 例程可以返回错误代码,驱动程序可以立即处理 IRP。 同样,如果 IRP 无法再排队, CsqInsertIrpEx 可能会返回错误代码来指示这一事实。

驱动程序与所有 IRP 取消处理隔离。 系统为队列中的 IRP 提供 Cancel 例程。 此例程调用 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);
}

驱动程序可以使用操作系统的任何同步基元来实现其 CsqAcquireLockCsqReleaseLock 例程。 可用的同步基元包括 旋转锁互斥对象

下面是驱动程序如何使用旋转锁实现锁定的示例。

/* 
  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 变量的指针传递给 CsqAcquireLockCsqReleaseLock。 如果驱动程序使用旋转锁为队列实现锁定,则驱动程序可以使用此变量在队列锁定时存储当前 IRQL。

驱动程序不需要使用旋转锁。 例如,驱动程序可以使用互斥锁来锁定队列。 有关驱动程序可用的同步技术的说明,请参阅 同步技术

使用 Cancel-Safe IRP 队列

在对 IRP 进行排队和取消排队时,驱动程序使用以下系统例程:

下图演示 了 IoCsqRemoveNextIrp 的控制流。

说明 iocsqremovenextirp 的控制流的示意图。

下图演示 了 IoCsqRemoveIrp 的控制流。

说明 iocsqremoveirp 的控制流的示意图。

这些例程反过来又调度到驱动程序提供的例程。

IoCsqInsertIrpEx 例程提供对 CsqInsertIrpEx 例程的扩展功能的访问权限。 它返回 CsqInsertIrpEx 返回的状态值。 调用方可以使用此值来确定 IRP 是否已成功排队。 IoCsqInsertIrpEx 还允许调用方为 CsqInsertIrpEx 的 InsertContext 参数指定值。

请注意,无论队列是具有 CsqInsertIrp 例程还是 CsqInsertIrpEx 例程,IoCsqInsertIrpIoCsqInsertIrpEx 都可以在任何取消安全队列上调用。 IoCsqInsertIrp 在任一情况下的行为都相同。 如果 向 IoCsqInsertIrpEx 传递具有 CsqInsertIrp 例程的队列,则其行为与 IoCsqInsertIrp 相同。

下图演示 了 IoCsqInsertIrp 的控制流。

说明 iocsqinsertirp 的控制流的示意图。

下图演示 了 IoCsqInsertIrpEx 的控制流。

说明 iocsqinsertirpex 的控制流的示意图。

有多种自然方法可以使用 IoCsqXxx 例程对 IRP 进行排队和取消排队。 例如,驱动程序只需按照接收 IRP 的顺序对 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);

然后,驱动程序可以使用 IO_CSQ_IRP_CONTEXT 值将同一 IRP 取消排队。

    IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

驱动程序可能还需要根据特定条件从队列中删除 IRP。 例如,驱动程序可能会将优先级与每个 IRP 相关联,以便优先级较高的 IRP 首先取消排队。 驱动程序可能会将 PeekContext 值传递给 IoCsqRemoveNextIrp,系统在请求队列中的下一个 IRP 时将该值传回驱动程序。