了解 Windows 文件系统文件 I/O

下载 EaseFilter Filter Driver SDK 安装文件
下载 EaseFilter Filter Driver SDK 压缩文件
了解 EaseFilter Filter Driver SDK 编程
定义:文件 I/O

输入/输出操作,例如打开、关闭、读取、写入和附加,所有这些操作都处理标准磁盘或磁带文件。 该术语用于指代与低级系统 I/O 不同的常规文件操作,例如处理虚拟内存页或操作系统目录。 尽管后者也可以打开、关闭、读取和写入,但它们通常是隐藏在用户视图之外的。

Windows I/O 模型概述

每个操作系统都有一个隐式或显式的 I/O 模型来处理进出外围设备的数据流。 Microsoft Windows I/O 模型的一项功能是支持异步 I/O。 此外,I/O模型还具有以下一般特征:
I/O 管理器为所有内核模式驱动程序(包括最低级驱动程序、中间驱动程序和文件系统驱动程序)提供一致的接口。 所有对驱动程序的 I/O 请求都作为 I/O 请求数据包 (IRP) 发送。
I/O 操作是分层的。 I/O 管理器导出 I/O 系统服务,用户模式受保护的子系统调用这些服务来代表其应用程序和/或最终用户执行 I/O 操作。 I/O 管理器拦截这些调用,设置一个或多个 IRP,并通过可能的分层驱动程序将它们路由到物理设备。
I/O 管理器定义了驱动程序可以支持的一组标准例程,其中一些是必需的,另一些是可选的。 鉴于外围设备之间的差异以及总线、功能、过滤器和文件系统驱动程序所需的不同功能,所有驱动程序都遵循相对一致的实现模型。
与操作系统本身一样,驱动程序也是基于对象的。 驱动程序、其设备和系统硬件都表示为对象。 I/O 管理器和其他操作系统组件导出内核模式支持例程,驱动程序可以调用这些例程来通过操作适当的对象来完成工作。
除了使用 IRP 传送传统的 I/O 请求之外,I/O 管理器还与 PnP 和电源管理器一起发送包含 PnP 和电源请求的 IRP。

I/O 请求 - 概述

下图显示了当子系统代表应用程序打开表示数据文件的文件对象时所发生的情况的概述。

  1. 子系统调用 I/O 系统服务来打开命名文件。WINDOWS-FILE-SYSTEM-FILE-I-O.html
  2. I/O 管理器调用对象管理器来查找指定文件并帮助它解析文件对象的任何符号链接。 它还调用安全引用监视器来检查子系统是否具有打开该文件对象的正确访问权限。WINDOWS-FILE-SYSTEM-FILE-I-O.html
  3. 如果卷尚未安装,I/O 管理器会暂时挂起打开请求并调用一个或多个文件系统,直到其中一个将文件对象识别为已存储在文件系统使用的大容量存储设备之一上的内容。 。 当文件系统安装了卷后,I/O 管理器将恢复请求。WINDOWS-FILE-SYSTEM-FILE-I-O.html
  4. I/O 管理器为打开请求分配内存并初始化 IRP。 对于驱动程序来说,打开相当于“创建”请求。WINDOWS-FILE-SYSTEM-FILE-I-O.html
  5. I/O 管理器调用文件系统驱动程序,并将 IRP 传递给它。 文件系统驱动程序访问 IRP 中的 I/O 堆栈位置以确定必须执行什么操作、检查参数、确定所请求的文件是否在缓存中,如果不在缓存中,则设置下一层驱动程序的 I/O IRP.WINDOWS-FILE-SYSTEM-FILE-I-O.html 中的堆栈位置
  6. 两个驱动程序都处理 IRP 并完成请求的 I/O 操作,调用由 I/O 管理器和其他系统组件(上图中未显示)提供的内核模式支持例程。WINDOWS-FILE-SYSTEM-FILE-I-O .html
  7. 驱动程序将 IRP 返回到 I/O 管理器,并在 IRP 中设置 I/O 状态块,以指示请求的操作是否成功或失败的原因。WINDOWS-FILE-SYSTEM-FILE-I-O.html
  8. I/O管理器从IRP中获取I/O状态,因此它可以通过受保护的子系统将状态信息返回给原始调用者。WINDOWS-FILE-SYSTEM-FILE-I-O.html
  9. I/O 管理器释放已完成的 IRP.WINDOWS-FILE-SYSTEM-FILE-I-O.html
  10. 如果打开操作成功,I/O 管理器将文件对象的句柄返回给子系统。 如果出现错误,它会向子系统返回适当的状态。WINDOWS-FILE-SYSTEM-FILE-I-O.html

子系统成功打开代表数据文件、设备或卷的文件对象后,子系统在后续设备 I/O 操作(通常是读、写或设备 I/O)请求中使用返回的句柄来标识该文件对象。 /O 控制请求)。 为了发出这样的请求,子系统调用 I/O 系统服务。 I/O 管理器将这些请求作为 IRP 发送到适当的驱动程序。

I/O 请求 - 详细信息

图示打开文件对象的图显示了具有两个 I/O 堆栈位置的 IRP,但 IRP 可以具有任意数量的 I/O 堆栈位置,具体取决于有多少个分层驱动程序将处理给定的请求。
下图更详细地说明了“打开文件对象”图中的驱动程序如何使用 I/O 支持例程(IoXxx 例程)来处理读或写请求的 IRP。

  1. I/O 管理器使用为子系统的读/写请求分配的 IRP 来调用文件系统驱动程序 (FSD)。 FSD 访问 IRP 中的 I/O 堆栈位置以确定它应该执行什么操作。
  2. FSD 可以通过调用 I/O 支持例程 (IoAllocateIrp) 一次或多次来分配额外的 IRP,将原始请求分解为更小的请求(可能针对多个设备驱动程序)。 附加的 IRP 将返回到 FSD,并为较低级别的驱动程序提供零填充的 I/O 堆栈位置。 FSD 可以自行决定重用原始 IRP,而不是如上图所示分配额外的 IRP,方法是在原始 IRP 中设置下一个较低层驱动程序的 I/O 堆栈位置并将其传递给较低层驱动程序。
  3. 对于每个驱动程序分配的 IRP,上图中的 FSD 调用 I/O 支持例程来注册 FSD 提供的完成例程; 在完成例程中,FSD 可以确定较低层驱动程序是否满足该请求,并在较低层驱动程序完成后释放每个驱动程序分配的 IRP。 I/O 管理器将调用 FSD 提供的完成例程,无论每个驱动程序分配的 IRP 是否成功完成、以错误状态完成或取消。 较高级别的驱动程序负责释放它为较低级别的驱动程序代表其自己分配和设置的所有 IRP。 I/O 管理器在所有驱动程序完成后释放其分配的 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 管理器都返回控制权。)
  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 堆栈位置清零,并使用 FSD 分配的 IRP 调用文件系统的注册完成例程(请参阅步骤 3)。 此完成例程检查 I/O 状态块以确定是否重试该请求或更新有关原始请求的任何内部状态并释放其驱动程序分配的 IRP。 文件系统可以收集它发送给较低级别驱动程序的所有驱动程序分配的 IRP 的状态信息,以便它可以设置 I/O 状态并完成原始 IRP。 当文件系统完成原始 IRP 时,I/O 管理器将 NTSTATUS 值返回给 I/O 操作的原始请求者(子系统的本机函数)。

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

  1. 将其自己的完成例程设置到 IRP 中。 IoCompletion 例程检查 I/O 状态块以确定较低的驱动程序是否成功完成 IRP、取消 IRP 和/或完成时出现错误。 在完成 IRP 之前,完成例程还可以更新驱动程序可能已保存的任何特定于 IRP 的状态,释放驱动程序可能已分配的任何特定于操作的资源,等等。 此外,完成例程可以推迟 IRP 完成(通过通知 I/O 管理器该 IRP 需要更多处理),并且可以在允许 IRP 完成之前向下一个较低级别的驱动程序发送另一个请求。
  2. 在其分配的 IRP 中设置下一个较低级别驱动程序的 I/O 堆栈位置,并将请求发送到下一个较低级别驱动程序。
  3. 通过在每个 IRP 中设置下一个较低层驱动程序的 I/O 堆栈位置并调用 IoCallDriver,将任何传入请求传递到较低层驱动程序。 (请注意,对于主功能代码为 IRP_MJ_POWER 的 IRP,驱动程序必须使用 PoCallDriver。)

每个驱动程序创建的设备对象都代表一个物理、逻辑或虚拟设备,特定驱动程序会对其执行 I/O 请求。有关创建和设置设备对象的详细信息,请参阅 "设备对象和设备栈"。"分层驱动程序中的 IRP 处理 "图还显示,大多数驱动程序都会通过驱动程序提供的一组系统定义的标准例程分阶段处理每个 IRP,但链中不同级别的驱动程序必然有不同的标准例程。例如,只有最底层的驱动程序才会处理来自物理设备的中断,因此只有最底层的驱动程序才会有 ISR 和 DPC 来完成中断驱动的 I/O 操作。另一方面,由于这样的驱动程序在收到来自设备的中断时就知道 I/O 已完成,因此不需要完成例程。只有更高级别的驱动程序才会有一个或多个完成例程,如图中的 FSD。

京公网安备 号    |    备案号:京ICP备09015132号-1024