示例 I/O 请求 - 详细信息

演示打开文件对象的图显示了具有两个 I/O 堆栈位置的 IRP,但 IRP 可以具有任意数量的 I/O 堆栈位置,具体取决于将处理给定请求的分层驱动程序的数量。

下图更详细地说明了 “打开文件对象” 图中的驱动程序如何使用 I/O 支持例程 (IoXxx 例程) 处理读取或写入请求的 IRP。

说明在分层驱动程序中处理 irps 的示意图。

  1. I/O 管理器使用为子系统的读/写请求分配的 IRP 调用文件系统驱动程序 (FSD) 。 FSD 访问其在 IRP 中的 I/O 堆栈位置,以确定它应执行的操作。

  2. FSD 可以通过调用 I/O 支持例程 (IoAllocateIrp) 一次或多次来分配其他 IRP,将原始请求拆分为较小的请求 (多个设备驱动程序) 。 对于较低级别的驱动程序 () ,附加的 I/O 堆栈位置为零填充的 FSD () 。 FSD 可以自行决定重用原始 IRP,而不是按上图所示分配其他 IRP,方法是在原始 IRP 中设置下一个较低驱动程序的 I/O 堆栈位置,并将其传递给较低的驱动程序。

  3. 对于每个驱动程序分配的 IRP,上图中的 FSD 调用 I/O 支持例程来注册 FSD 提供的完成例程;在完成例程中,FSD 可以确定较低级别的驱动程序是否满足请求,并在低级驱动程序完成请求后释放每个驱动程序分配的 IRP。 I/O 管理器将调用 FSD 提供的完成例程,无论每个驱动程序分配的 IRP 是成功完成、已完成但状态为错误还是已取消。 较高级别的驱动程序负责释放它分配的任何 IRP,并代表自己为较低级别的驱动程序设置。 I/O 管理器在所有驱动程序完成 IRP 后释放其分配的 IRP。

    接下来,FSD 调用 I/O 支持例程 (IoGetNextIrpStackLocation) 来访问下一个较低级别的驱动程序的 I/O 堆栈位置,以便为下一个较低级别的驱动程序设置请求。 (在上图中,下一个较低级别的驱动程序恰好是最低级别的驱动程序。) FSD 然后调用 I/O 支持例程, (IoCallDriver) 将该 IRP 传递给下一个较低级别的驱动程序。

  4. 使用 IRP 调用它时,最低级别的驱动程序会检查其 I/O 堆栈位置,以确定 (IRP_MJ_XXX 函数代码指示的操作) 应在目标设备上执行。 目标设备由设备对象在其指定的 I/O 堆栈位置表示,并与 IRP 一起传递给驱动程序。 最低级别驱动程序可以假定 I/O 管理器已将 IRP 路由到为 IRP_MJ_XXX 操作定义的入口点,该入口点 (IRP_MJ_READ或IRP_MJ_WRITE) ,而更高级别的驱动程序已检查请求的其他参数的有效性。

    如果没有更高级别的驱动程序,则最低级别的驱动程序将检查IRP_MJ_XXX 操作的输入参数是否有效。 如果存在,驱动程序通常会调用 I/O 支持例程,以告知 I/O 管理器设备操作在 IRP 上挂起,并将 IRP 排队或将其传递给另一个驱动程序提供的例程,该例程访问目标设备 (此处、物理或逻辑设备:磁盘或磁盘上的分区) 。

  5. I/O 管理器确定驱动程序是否已忙于处理目标设备的另一个 IRP,如果 IRP 为它排队,并返回。 否则,I/O 管理器会将 IRP 路由到驱动程序提供的例程,该例程在其设备上启动 I/O 操作。 (在此阶段,上图中的驱动程序和 I/O 管理器返回 control.)

  6. 当设备中断时,驱动程序的中断服务例程 (ISR) 只能执行停止设备中断并保存有关操作的必要上下文所需的工作。 然后,ISR 使用 IRP 调用 I/O 支持例程 (IoRequestDpc) ,将驱动程序提供的 DPC 排入队列 (延迟过程调用) 例程,以低于 ISR 的硬件优先级完成请求的操作。

  7. 当驱动程序的 DPC 获得控制权时,它将使用在 ISR 调用 IoRequestDpc) 中传递的上下文 (来完成 I/O 操作。 如果有任何) ,DPC 调用支持例程以取消下一个 IRP (排队,并将该 IRP 传递给驱动程序提供的、在设备上启动 I/O 操作的例程, (请参阅步骤 5) 。 然后,DPC 在 IRP 的 I/O 状态块中设置有关刚刚完成的操作的状态,并使用 IoCompleteRequest 将其返回到 I/O 管理器。

  8. I/O 管理器将 IRP 中最低级别驱动程序的 I/O 堆栈位置归零,并调用文件系统的已注册完成例程, (请参阅步骤 3) 与 FSD 分配的 IRP。 此完成例程检查 I/O 状态块,以确定是重试请求,还是更新有关原始请求维护的任何内部状态,并释放其驱动程序分配的 IRP。 文件系统可以收集它发送给较低级别驱动程序的所有驱动程序分配的 IRP 的状态信息,以便可以设置 I/O 状态并完成原始 IRP。 文件系统完成原始 IRP 后,I/O 管理器会将 和 NTSTATUS 值返回给原始请求者, (子系统的本机函数) I/O 操作。

与分层驱动程序 中的处理 IRP 图中显示的文件系统驱动程序一样,添加到现有驱动程序链中的任何新驱动程序都可以执行以下所有操作:

  • 将自己的完成例程设置为 IRP。 IoCompletion 例程检查 I/O 状态块,以确定低级驱动程序是否已成功完成 IRP、取消 IRP 和/或完成 IRP 并出现错误。 完成例程还可以在完成 IRP 之前更新驱动程序可能已保存的任何特定于 IRP 的状态,释放驱动程序可能已分配的任何特定于操作的资源,等等。 此外,完成例程可以通过通知 I/O 管理器 IRP) 需要进行更多处理来推迟 IRP 完成 (,并且可以在允许 IRP 完成之前将另一个请求发送到下一个较低级别的驱动程序。

  • 在分配的 IRP 中设置下一个较低级别的驱动程序的 I/O 堆栈位置,并将请求发送到下一个较低级别的驱动程序。

  • 通过在每个 IRP 中设置下一个较低驱动程序的 I/O 堆栈位置并调用 IoCallDriver,将任何传入请求传递到较低的驱动程序。 (请注意,对于具有主要函数代码 IRP_MJ_POWER的 IRP,驱动程序必须使用 PoCallDriver.)

每个驱动程序创建的设备对象都表示一个物理、逻辑或虚拟设备,特定驱动程序为其执行 I/O 请求。 有关创建和设置设备对象的详细信息,请参阅 设备对象和设备堆栈

正如 分层驱动程序中的处理 IRP 图所示,大多数驱动程序通过驱动程序提供的一组系统定义 标准例程分阶段处理每个 IRP,但链中不同级别的驱动程序必须具有不同的标准例程。 例如,只有最低级别的驱动程序处理来自物理设备的中断,因此只有最低级别的驱动程序才具有完成中断驱动的 I/O 操作的 ISR 和 DPC。 另一方面,由于此类驱动程序在收到来自其设备的中断时知道 I/O 已完成,因此它不需要完成例程。 只有更高级别的驱动程序才会有一个或多个完成例程,如此图中的 FSD。