使用缓冲 I/O
为交互式或慢速设备提供服务的驱动程序,或者通常一次传输相对较少的数据的驱动程序应使用 缓冲 I/O 传输方法。 将缓冲 I/O 用于小型交互式传输可改善整体物理内存使用率,因为内存管理器不需要像对请求直接 I/O 的驱动程序那样,为每个传输锁定完整的物理页。 通常,视频、键盘、鼠标、串行和并行驱动程序请求缓冲 I/O。
I/O 管理器确定 I/O 操作使用缓冲 I/O,如下所示:
对于IRP_MJ_READ和IRP_MJ_WRITE请求,DO_BUFFERED_IO在 DEVICE_OBJECT 结构的 Flags 成员中设置。 有关详细信息,请参阅 初始化设备对象。
对于 IRP_MJ_DEVICE_CONTROL 和 IRP_MJ_INTERNAL_DEVICE_CONTROL 请求,IOCTL 代码的值包含METHOD_BUFFERED作为 IOCTL 值中的 TransferType 值。 有关详细信息,请参阅 定义 I/O 控制代码。
下图说明了 I/O 管理器如何为使用缓冲 I/O 的传输操作设置 IRP_MJ_READ 请求。
该图概述了驱动程序如何在 IRP 中使用 SystemBuffer 指针传输读取请求的数据时,驱动程序已对设备对象的 Flags 进行了 ORed,DO_BUFFERED_IO:
用户空间虚拟地址的某些范围表示当前线程的缓冲区,该缓冲区的内容可能存储在上图) 中基于页面的物理地址范围内, (深色底纹。
I/O 管理器为当前线程的读取请求提供服务,线程为此请求传递表示缓冲区的用户空间虚拟地址范围。
I/O 管理器检查用户提供的缓冲区的可访问性,并调用 ExAllocatePoolWithTag 以 (SystemBuffer 创建非分页系统空间缓冲区,) 用户提供的缓冲区的大小。
I/O 管理器在发送到驱动程序的 IRP 中提供对新分配 的 SystemBuffer 的访问权限。
如果图中显示了写入请求,则 I/O 管理器会在将 IRP 发送到驱动程序之前,将数据从用户缓冲区复制到系统缓冲区。
对于上图中显示的读取请求,驱动程序将数据从设备读取到系统空间缓冲区。 此缓冲区的内存是非分页的,驱动程序可以安全地访问缓冲区,而无需首先锁定它。 满足读取请求后,驱动程序会使用 IRP 调用 IoCompleteRequest 。
当原始线程再次处于活动状态时,I/O 管理器将读取数据从系统缓冲区复制到用户缓冲区中。 它还调用 ExFreePool 来释放系统缓冲区。
I/O 管理器为驱动程序创建系统空间缓冲区后,可以交换请求用户模式线程,并且其物理内存可由另一个线程(可能由属于另一个进程的线程)重复使用。 但是,在驱动程序使用 IRP 调用 IoCompleteRequest 之前,IRP 中提供的系统空间虚拟地址范围仍然有效。
一次传输大量数据的驱动程序(尤其是执行多页传输的驱动程序)不应尝试使用缓冲 I/O。 当系统运行时,非分页池可能会变得碎片化,因此 I/O 管理器无法分配大型连续系统空间缓冲区,以在 IRP 中为此类驱动程序发送。
通常,驱动程序对某些类型的 IRP(例如 IRP_MJ_DEVICE_CONTROL 请求)使用缓冲 I/O,即使它也使用 直接 I/O。 使用直接 I/O 的驱动程序通常只对 IRP_MJ_READ 和 IRP_MJ_WRITE 请求执行此操作,并且可能针对驱动程序定义的 IRP_MJ_INTERNAL_DEVICE_CONTROL 请求(需要大型数据传输)。
每个 IRP_MJ_DEVICE_CONTROL 和 IRP_MJ_INTERNAL_DEVICE_CONTROL 请求都包含 I/O 控制代码。 如果 I/O 控制代码指示必须使用缓冲 I/O 支持 IRP,则 I/O 管理器使用单个系统缓冲区来表示用户应用程序的输入和输出缓冲区。 支持此类 I/O 控制代码的驱动程序必须读取 ((如果缓冲区有任何) )的输入数据,然后通过覆盖输入数据 (提供输出数据(如果有任何) )。 有关详细信息,请参阅 定义 I/O 控制代码。