实现 IoCompletion 例程

输入时, IoCompletion 例程接收 Context 指针。 当调度例程调用 IoSetCompletionRoutine 时,它可以提供 上下文 指针。 此指针可以引用 IoCompletion 例程处理 IRP 所需的驱动程序确定的任何上下文信息。 请注意,上下文区域不可分页,因为 IoCompletion 例程可以在 IRQL = DISPATCH_LEVEL调用。

请考虑 IoCompletion 例程的以下实现准则:

  • IoCompletion 例程可以检查 IRP 的 I/O 状态块来确定 I/O 操作的结果。

  • 如果输入 IRP 是由调度例程使用 IoAllocateIrpIoBuildAsynchronousFsdRequest 分配的, IoCompletion 例程必须调用 IoFreeIrp 来释放该 IRP,最好是在完成原始 IRP 之前。

    • IoCompletion 例程必须释放为驱动程序分配的 IRP 分配的调度例程的任何每 IRP 资源,最好是在释放相应的 IRP 之前。

      例如,如果调度例程使用 IoAllocateMdl 分配 MDL ,并为它分配的部分传输 IRP 调用 IoBuildPartialMdl ,则 IoCompletion 例程必须使用 IoFreeMdl 释放 MDL。 如果它分配资源以维护有关原始 IRP 的状态,则必须释放这些资源,最好是在它使用原始 IRP 调用 IoCompleteRequest 之前,并且肯定在它返回控制权之前。

      通常,在释放或完成 IRP 之前, IoCompletion 例程应释放调度例程分配的任何按 IRP 资源。 否则,驱动程序必须在其 IoCompletion 例程从完成原始请求返回控制权之前,保持有关要释放的资源的状态。

    • 如果 IoCompletion 例程无法通过STATUS_SUCCESS完成原始 IRP,则必须将原始 IRP 中的 I/O 状态块设置为驱动程序分配的 IRP 中返回的值,该值导致 IoCompletion 例程使原始请求失败。

    • 如果 IoCompletion 例程将使用 STATUS_PENDING 完成原始请求,则必须先使用原始 IRP 调用 IoMarkIrpPending ,然后才能调用 IoCompleteRequest

    • 如果 IoCompletion 例程必须使原始 IRP 失败并出现错误 STATUS_XXX,则它可以 记录错误。 但是,基础设备驱动程序负责记录发生的任何设备 I/O 错误,因此 IoCompletion 例程通常不会记录错误。

    • IoCompletion 例程已处理并释放驱动程序分配的 IRP 时,该例程必须返回具有STATUS_MORE_PROCESSING_REQUIRED的控制。

      IoCompletion 例程返回STATUS_MORE_PROCESSING_REQUIRED会阻止 I/O 管理器对驱动程序分配和释放的 IRP 的完成处理。 第二次调用 IoCompleteRequest 会导致 I/O 管理器继续调用 IRP 的完成例程,从紧邻返回STATUS_MORE_PROCESSING_REQUIRED的例程上方的完成例程开始。

  • 如果 IoCompletion 例程重复使用传入的 IRP 以向较低版本的驱动程序发送一个或多个请求,或者如果例程重试失败的操作,则它应更新 IoCompletion 例程维护的关于 IRP 的每次重用或重试的任何上下文。 然后,它可以再次设置下一个较低驱动程序的 I/O 堆栈位置,使用自己的入口点调用 IoSetCompletionRoutine ,并为 IRP 调用 IoCallDriver

    • 每次重用或重试 IRP 时, IoCompletion 例程不应调用 IoMarkIrpPending

      调度例程已将原始 IRP 标记为挂起。 在链中的所有驱动程序使用 IoCompleteRequest 完成原始 IRP 之前,它仍处于挂起状态。

    • 在重试请求之前, IoCompletion 例程应在保存返回的错误信息后重置 I/O 状态块,将STATUS_SUCCESS设置为 “状态” ,将零设置为 “信息”。

      对于每次重试, IoCompletion 例程通常会递减调度例程设置的重试计数。 通常, IoCompletion 例程必须调用 IoCompleteRequest 才能在有限次数的重试失败时使 IRP 失败。

    • IoCompletion 例程在调用 IoSetCompletionRoutineIoCallDriver 并重复使用或重试的 IRP 后,必须返回STATUS_MORE_PROCESSING_REQUIRED。

      IoCompletion 例程返回STATUS_MORE_PROCESSING_REQUIRED会阻止 I/O 管理器对重用或重试的 IRP 的完成处理。

    • 如果 IoCompletion 例程无法使用 STATUS_SUCCESS 完成原始 IRP,则必须保留由较低级驱动程序返回的 I/O 状态块,以便执行导致 IoCompletion 例程使 IRP 失败的重用或重试操作。

    • 如果 IoCompletion 例程将使用 STATUS_PENDING 完成原始请求,则必须先使用原始 IRP 调用 IoMarkIrpPending ,然后才能调用 IoCompleteRequest

    • 如果 IoCompletion 例程必须使原始 IRP 失败并出现错误 STATUS_XXX,则它可以 记录错误。 但是,基础设备驱动程序负责记录发生的任何设备 I/O 错误,因此 IoCompletion 例程通常不会记录错误。

  • 在 IRP 中设置 IoCompletion 例程,然后将 IRP 向下传递到较低驱动程序的任何驱动程序都应检查 IoCompletion 例程中的 IRP-PendingReturned> 标志。 如果设置了 标志, IoCompletion 例程必须使用 IRP 调用 IoMarkIrpPending 。 但请注意,传递 IRP 然后等待事件的驱动程序不应将 IRP 标记为挂起。 相反,其 IoCompletion 例程应发出事件信号并返回STATUS_MORE_PROCESSING_REQUIRED。

  • IoCompletion 例程必须释放调度例程分配用于处理原始 IRP 的任何资源,最好是在 IoCompletion 例程使用原始 IRP 调用 IoCompleteRequest 之前,并且肯定在 IoCompletion 例程从完成原始 IRP 返回控制权之前。

如果任何较高级别的驱动程序在原始 IRP 中设置了其 IoCompletion 例程,则在调用所有较低级别驱动程序的 IoCompletion 例程之前,不会调用该驱动程序的 IoCompletion 例程。

在对 IoCompleteRequest 的调用中提供优先级提升

如果最低级别的设备驱动程序可以在其调度例程中完成 IRP,则会调用 PriorityBoost 为 IO_NO_INCREMENT 的 IoCompleteRequest。 无需增加运行时优先级,因为驱动程序可以假定原始请求者没有等待其 I/O 操作完成。

否则,最低级别驱动程序会提供系统定义的特定于设备类型的值,该值可提升请求者的运行时优先级,以补偿请求者在其设备 I/O 请求上等待的时间。 有关提升值,请参阅 Wdm.h 或 Ntddk.h。

较高级别的驱动程序在调用 IoCompleteRequest 时应用与其各自的基础设备驱动程序相同的 PriorityBoost

调用 IoCompleteRequest 的效果

当驱动程序调用 IoCompleteRequest 时,I/O 管理器在调用下一个已设置要为 IRP 调用的 IoCompletion 例程的更高级别的驱动程序(如果有)之前,用零填充该驱动程序的 I/O 堆栈位置。

较高级别的驱动程序的 IoCompletion 例程只能检查 IRP 的 I/O 状态块,以确定所有较低级别的驱动程序如何处理请求。

IoCompleteRequest 的调用方不得尝试访问刚刚完成的 IRP。 这种尝试是导致系统崩溃的编程错误。