将直接 I/O 与 PIO 配合使用

使用编程 I/O (PIO) 而不是 DMA 的驱动程序必须将用户空间缓冲区双重映射到系统空间地址范围。 下图说明了 I/O 管理器如何为使用直接 I/O 的 PIO 传输操作设置 IRP_MJ_READ 请求。

说明使用 pio 的设备直接 i/o 的示意图。

该图显示了使用 PIO 的设备如何处理同一任务。

  1. 某些范围的用户空间虚拟地址表示当前线程的缓冲区,并且该缓冲区的内容实际上可能存储在一些物理上不连续的页面上。 如果缓冲区长度为非零,则 I/O 管理器将创建一个 MDL 来描述此缓冲区。

  2. I/O 管理器为当前线程的读取请求提供服务,线程为此传递表示缓冲区的一系列用户空间虚拟地址。

  3. I/O 管理器或 FSD 检查用户提供的缓冲区的辅助功能。 如果 I/O 管理器已创建 MDL,它将使用 MDL 调用 MmProbeAndLockPages ,它指定用户缓冲区的虚拟地址范围。 MmProbeAndLockPages 还会填充 MDL 中相应的物理地址范围。

  4. I/O 管理器提供指向 IRP 中请求传输操作的 MDL (MdlAddress) 的指针。 在 I/O 管理器或文件系统在驱动程序完成 IRP 后调用 MmUnlockPages 之前,MDL 中所述的物理页面将保持锁定状态并分配给缓冲区。 但是,在将 IRP 发送到设备驱动程序或可能分层到设备驱动程序的任何中间驱动程序之前,此类 MDL 中的虚拟地址可能会变得不可见 (和无效的) 。

  5. 如果驱动程序需要系统 (虚拟) 地址,则驱动程序使用 IRP 的 MdlAddress 指针调用 MmGetSystemAddressForMdlSafe,以将 MDL 中的用户空间虚拟地址双重映射到系统空间地址范围。 在上图中,AliasBuff 表示描述双映射地址的 MDL。

  6. 驱动程序使用系统空间虚拟地址范围(从双映射的 MDL (AliasBuff) )将数据读入内存。

当驱动程序通过调用 IoCompleteRequest 完成 IRP 时,如果驱动程序名为 MmGetSystemAddressForMdlSafe,则 I/O 管理器或文件系统将释放 MDL 的双映射系统空间范围。 I/O 管理器或文件系统解锁 MDL 中所述的页面,并代表驱动程序释放 MDL 和 IRP。 为了提高性能,驱动程序应避免将 MDL 物理地址双重映射到系统空间,如步骤 3 中所述,除非它们必须使用虚拟地址。 这样做会不必要地使用系统页表条目,并且可能会降低驱动程序性能和可伸缩性。 此外,如果系统用完页表条目,系统可能会崩溃,因为大多数较旧的驱动程序无法处理这种情况。

当前用户线程的缓冲区和线程本身保证仅在该线程为当前线程时驻留在物理内存中。 对于上图中显示的线程,在运行另一个进程的线程时,其用户缓冲区的内容可以分页到辅助存储。 运行另一个进程的线程时,请求线程缓冲区的系统物理内存可能会被覆盖,除非内存管理器已锁定并保留包含原始线程缓冲区的相应物理页。

但是,当另一个线程是当前线程时,原始线程的缓冲区虚拟地址不可见,即使内存管理器保留缓冲区的物理页也是如此。 因此,驱动程序无法使用 MmGetMdlVirtualAddress 返回的 虚拟地址来访问内存。 此例程的调用方必须将其结果连同 IRP 的 MdlAddress 指针) 一起传递给 MapTransfer (,以便使用基于数据包的系统或总线主 DMA 传输数据。