取消 IRP 时要考虑的要点

本部分讨论有关实现 取消 例程和处理可取消 IRP 的准则。 有关处理可取消 IRP 的详细信息,请参阅 Cancel-Safe IRP 队列的控制流

所有取消例程的一般准则

每当调用驱动程序的取消例程时,I/O 管理器都持有 取消 旋转锁。 因此,每个 Cancel 例程都必须:

每次 取消 例程调用 IoReleaseCancelSpinLock 时,它都必须传递最近调用 IoAcquireCancelSpinLock 返回的 IRQL。 释放由 I/O 管理器 (获取并在) 调用 Cancel 例程时保持的旋转锁时,Cancel 例程必须传递 Irp-CancelIrql>

驱动程序在按住旋转锁时不得调用外部例程 ((如 IoCompleteRequest) ),因为可能会导致死锁。

使用 I/O 管理器定义的队列

除非驱动程序管理自己的内部 IRP 队列,否则会使用传入的 IRP 调用其 取消 例程,该 IRP 可以是以下任一项:

  • 输入目标设备对象中的 CurrentIrp

  • 与目标设备对象关联的设备队列中的条目

除非驱动程序管理自己的 IRP 内部队列,否则其 Cancel 例程应使用输入 IRP 调用 KeRemoveEntryDeviceQueue ,以测试它是否是与目标设备对象关联的设备队列中的条目。 驱动程序的 Cancel 例程 无法 调用 KeRemoveDeviceQueueKeRemoveByKeyDeviceQueue ,因为它不能假定给定的 IRP 位于设备队列中的任何特定位置。

输入 IRP 的当前状态

如果使用驱动程序已启动 I/O 处理的 IRP 调用 Cancel 例程,并且请求即将完成, 则取消 例程应释放系统取消旋转锁并返回控件。

如果输入 IRP 的当前状态为“挂起”, 则 Cancel 例程必须执行以下操作:

  1. 将输入 IRP 的 I/O 状态块设置为“ 状态 ”STATUS_CANCELLED,为 “信息”设置零。

  2. 释放它持有的任何旋转锁,包括系统取消旋转锁。

  3. 使用给定的 IRP 调用 IoCompleteRequest

将 IRP 保持在可取消状态

持有处于可取消状态的 IRP 的任何驱动程序例程都必须调用 IoMarkIrpPending ,并且必须调用 IoSetCancelRoutine 来设置 IRP 中 取消 例程的入口点。 只有这样,驱动程序例程才能调用其他支持例程,例如 IoStartPacketIoAllocateControllerExInterlockedInsert.。列出 例程。

任何随后处理可取消 IRP 的驱动程序例程都必须检查 IRP 在开始操作以满足请求之前是否已取消。 例程必须调用 IoSetCancelRoutine ,才能将其 取消 例程的入口点重置为 IRP 中的 NULL 。 只有这样,该例程才能开始对输入 IRP 进行 I/O 处理。

例程可能必须重置 IRP 中 取消 例程的入口点,如果它也传递了 IRP 以供其他驱动程序例程进一步处理,并且这些 IRP 可能保持可取消状态。

将 IRP 置于可取消状态的任何较高级别的驱动程序都必须将其 Cancel 入口点重置为 NULL ,然后才能使用 IoCallDriver 将 IRP 传递到下一个较低的驱动程序。

取消 IRP

任何更高级别的驱动程序都可以使用它已分配并传递的 IRP 调用 IoCancelIrp ,以供较低级别的驱动程序进一步处理。 但是,此类驱动程序不能假定给定的 IRP 将由较低驱动程序STATUS_CANCELLED完成。

同步

驱动程序可以 (或必须,具体取决于其设计) 在其设备扩展中维护其他状态信息,以跟踪 IRP 的可取消状态。 如果此状态由在 IRQL <= DISPATCH_LEVEL 运行的驱动程序例程共享,则应使用驱动程序分配和初始化的旋转锁保护共享数据。

驱动程序应仔细管理其获取和释放系统取消旋转锁及其自己的旋转锁。 它应在尽可能短的间隔内保留系统取消旋转锁。 在访问可取消的 IRP 之前,此类驱动程序应始终检查 IoSetCancelRoutine 的返回值,以确定取消例程是已在 (运行还是即将) 运行;如果是这样,则应让 Cancel 例程完成 IRP。

如果设备驱动程序维护有关各种驱动程序例程与其 ISR 共享的可取消 IRP 的状态信息,则这些其他例程必须与 ISR 同步对共享状态的访问。 只有驱动程序提供的 SynchCritSection 例程才能以多处理器安全方式访问与 ISR 共享的状态信息。

有关详细信息,请参阅 同步技术