实现 IoCompletion 例程

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

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

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

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

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

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

      通常,在释放或完成 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

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

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

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

      对于每次重试, IoCompletion 例程通常会递减调度例程设置的重试计数。 通常,当有限次数的重试失败时,IoCompletion 例程必须调用 IoCompleteRequest 来标记IRP为失败。

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

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

    • 如果 IoCompletion 例程无法以 STATUS_SUCCESS 完成原始 IRP,则必须保持 I/O 状态块为较低驱动程序返回的状态,以便在重试或重新使用操作中使用,从而导致 IoCompletion 例程无法完成 IRP。

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

    • 如果 IoCompletion 例程必须因错误 STATUS_XXX 导致原始 IRP 失败,则它可以 记录错误。 但是,基础设备驱动程序负责记录发生的任何设备 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,则调用 IoCompleteRequest 并将 PriorityBoost 设置为 IO_NO_INCREMENT。 无需增加运行时优先级,因为驱动程序可以假定原始请求者没有等待其 I/O操作完成。

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

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

调用 IoCompleteRequest 的效果

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

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

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