注册 IoCompletion 例程

为了注册 IoCompletion 例程,调度例程调用 IoSetCompletionRoutine,提供 IoCompletion 例程的地址以及随后将使用 IoCallDriver 传递给较低级驱动程序的 IRP。

调用 IoSetCompletionRoutine 时,调度例程指定 I/O 管理器应调用指定的 IoCompletion 例程的情况。 如果较低级别的驱动程序成功完成 IRP (InvokeOnSuccess) 、以错误状态值 (InvokeOnError) 完成 IRP,或者取消 IRP (InvokeOnCancel) ,则可以选择调用 IoCompletion 例程。

IoCompletion 例程的目的是监视较低级别的驱动程序对 IRP 执行的操作,并在必要时执行其他完成处理。 具体而言,驱动程序的 IoCompletion 例程的最常见用途如下:

  • 释放驱动程序使用 IoAllocateIrpIoBuildAsynchronousFsdRequest 分配的 IRP

    使用这些支持例程之一分配 IRP 的任何更高级别的驱动程序都必须为该 IRP 提供 IoCompletion 例程。 IoCompletion 例程必须调用 IoFreeIrp 来释放驱动程序分配的 IRP。

  • 重复使用传入的 IRP 以请求较低的驱动程序完成一些操作,例如部分传输,直到 IoCompletion 例程满足并完成原始请求

  • 重试低级驱动程序已完成但出现错误的请求

    与中间驱动程序相比,最高级别驱动程序(如文件系统)更有可能具有尝试重试请求的 IoCompletion 例程,但可能位于紧密耦合的端口驱动程序之上的类驱动程序除外。 但是,任何中间驱动程序都使用 IoCompletion 例程重试请求。

虽然最高级别或中间驱动程序的 DispatchReadWrite 例程最有可能处理需要 IoCompletion 例程的 IRP,但任何驱动程序中将 IRP 传递给较低级别的驱动程序中的任何调度例程都可以注册 IoCompletion 例程。

对于驱动程序分配的 IRP 和重用的 IRP,调度例程必须使用以下布尔参数调用 IoSetCompletionRoutine

  • InvokeOnSuccess 设置为 TRUE

  • InvokeOnError 设置为 TRUE

  • 如果链中的任何较低驱动程序可能处理可取消的 IRP,则 InvokeOnCancel 设置为 TRUE

    通常,无论是否使用STATUS_CANCELLED返回 IRP, InvokeOnCancel 都设置为 TRUE,以确保 IoCompletion 例程释放每个驱动程序分配的 IRP 或检查每次重用 IRP 的完成状态。

使用 IoAllocateIrpIoBuildAsynchronousFsdRequest为较低级别的驱动程序分配 IRP 的调度例程必须为每个驱动程序分配的 IRP 设置 IoCompletion 例程。

  • 调度例程必须设置有关原始 IRP 及其分配的 IRP () 的状态,以便 IoCompletion 例程使用。 IoCompletion 例程至少需要访问原始 IRP 以及分配了多少个额外 IRP。

  • 调度例程应调用 IoSetCompletionRoutine ,并为其分配的 IRP () 的所有 InvokeOnXxx 参数设置为 TRUE

对一系列操作重用 IRP 或重试 I/O 操作的调度例程必须为将重复使用或重试的每个 IRP 调用 IoSetCompletionRoutine

  • 调度例程必须保存原始 IRP 的状态信息,以供 IoCompletion 例程后续使用。

    例如, DispatchReadWrite 例程必须先保存 IoCompletion 例程的输入 IRP 的相关传输参数,然后才能为该 IRP 中下一个较低级别的驱动程序设置部分传输。 如果 DispatchReadWrite 例程修改 IoCompletion 例程需要确定何时满足原始请求的任何参数,则保存参数尤其重要。

  • 如果 IoCompletion 例程可以重试请求,则调度例程必须为其 IoCompletion 例程在完成原始 IRP 并出错之前应尝试的重试次数设置驱动程序确定的上限。

  • 如果要重用 IRP,调度例程应调用 IoSetCompletionRoutine ,并将所有 InvokeOnXxx 参数都设置为 TRUE

  • 对于异步请求,任何中间驱动程序的调度例程必须为原始 IRP 调用 IoMarkIrpPending 。 然后将 IRP 发送到较低的驱动程序后,它必须返回STATUS_PENDING。