可安全取消的 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 队列白皮书的控制Flow

实现 Cancel-Safe IRP 队列

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

  • 将 IRP 插入队列中的以下任一例程: CsqInsertIrpCsqInsertIrpExCsqInsertIrpExCsqInsertIrp 的扩展版本;队列是使用一个或另一个实现的。

  • 一个 CsqRemoveIrp 例程,用于从队列中删除指定的 IRP。

  • 一个 CsqPeekNextIrp 例程,该例程返回指向队列中指定 IRP 后面的下一个 IRP 的指针。 这是系统通过 IoCsqRemoveNextIrp 接收的 PeekContext 值的位置。 驱动程序可以以任何方式解释该值。

  • 以下两个例程都允许系统锁定和解锁 IRP 队列: CsqAcquireLockCsqReleaseLock

  • 完成已取消的 IRP 的 CsqCompleteCanceledIrp 例程。

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

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

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

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

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

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

驱动程序与所有 IRP 取消处理隔离。 系统为队列中的 IRP 提供 Cancel 例程。 此例程调用 CsqRemoveIrp 从队列中删除 IRP, CsqCompleteCanceledIrp 完成 IRP 取消。

下图说明了 IRP 取消的控制流。

diagram illustrating the flow of control for irp cancellation.

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 的控制流。

diagram illustrating the flow of control for iocsqremovenextirp.

下图说明了 IoCsqRemoveIrp 的控制流。

diagram illustrating the flow of control for iocsqremoveirp.

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

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

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

下图说明了 IoCsqInsertIrp 的控制流。

diagram illustrating the flow of control for iocsqinsertirp.

下图说明了 IoCsqInsertIrpEx 的控制流。

diagram illustrating the flow of control for 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 时,系统会将该值传回驱动程序。