在 WDF 驱动程序(KMDF 或 UMDF)中访问数据缓冲区
当 Windows 驱动程序框架 (WDF) 驱动程序收到读取、写入或设备 I/O 控制请求时,请求对象包含输入缓冲区和/或输出缓冲区。
输入缓冲区包含驱动程序所需的信息。 对于写入请求,此信息通常是函数驱动程序必须发送到设备的数据。 对于设备 I/O 控制请求,输入缓冲区可能包含指示驱动程序必须执行的操作类型的信息。
输出缓冲区从驱动程序接收信息。 对于读取请求,此信息通常是函数驱动程序从设备接收的数据。 对于设备 I/O 控制请求,输出缓冲区可能会接收由请求的 I/O 控制代码指定的状态或其他信息。
驱动程序用于访问请求的数据缓冲区的技术取决于驱动程序用于访问设备的数据缓冲区的方法。 有三种访问方法:
- 缓冲 I/O。 I/O 管理器创建与驱动程序共享的中间缓冲区。
- 直接 I/O。 I/O 管理器将缓冲区空间锁定到物理内存中,然后为驱动程序提供对缓冲区空间的直接访问。
- 既不是缓冲 I/O,也不是直接 I/O。 I/O 管理器为驱动程序提供请求缓冲区空间的虚拟地址。 I/O 管理器不会验证请求的缓冲区空间,因此驱动程序必须验证缓冲区空间是否可访问,并将缓冲区空间锁定到物理内存中。
Kernel-Mode Driver Framework (KMDF) 驱动程序可以使用这三种访问方法中的任何一种。 User-Mode Driver Framework (UMDF) 驱动程序可以对读取、写入和 IOCTL 请求使用缓冲或直接 I/O,并且可以 转换指定 METHOD_NEITHER 方法的请求。
指定缓冲区访问方法
对于读取和写入请求,驱动程序堆栈中的所有驱动程序都必须使用相同的方法来访问设备的缓冲区,但最高级别驱动程序除外,该驱动程序可以使用“两者”方法,无论较低级别的驱动程序使用哪种方法。
从版本 1.13 开始,KMDF 驱动程序通过为每个设备调用 WdfDeviceInitSetIoTypeEx 来指定设备的所有读取和写入请求的访问方法。 例如,如果驱动程序为其设备之一指定缓冲 I/O 方法,则 I/O 管理器在向该设备的驱动程序传递读取和写入请求时使用缓冲 I/O 方法。
对于设备 I/O 控制请求,IOCTL) (I/O 控制代码包含指定缓冲区访问方法的位。 因此,KMDF 驱动程序无需执行任何操作即可为 IOCTL 选择缓冲方法。 有关 IOCTL 的详细信息,请参阅 定义 I/O 控制代码。 与读取和写入请求不同,设备的所有 IOCTL 不必指定相同的访问方法。
UMDF 驱动程序指定框架用于读取和写入请求以及设备 I/O 控制请求的访问方法的 首选项 。 UMDF 驱动程序提供的值只是首选项,不保证框架会使用。 有关详细信息,请参阅 管理 UMDF 驱动程序中的缓冲区访问方法。
UMDF 驱动程序通过为每个设备调用 WdfDeviceInitSetIoTypeEx 来指定设备的所有读取、写入和 IOCTL 请求的访问方法。 例如,如果驱动程序为其设备之一指定缓冲 I/O 方法,则框架在向该设备的驱动程序传递读取、写入和 IOCTL 请求时使用缓冲 I/O 方法。
请注意 KMDF 和 UMDF 之间 IOCTL 的缓冲区访问技术的差异。 KMDF 驱动程序不为 IOCTL 指定缓冲区访问方法,而 UMDF 驱动程序指定 IOCTL 的缓冲区访问方法。
如果 WDF 驱动程序通过使用对 I/O 目标使用的 I/O 方法不正确的技术描述 I/O 请求的缓冲区,则框架会更正缓冲区说明。 例如,如果驱动程序使用 MDL 来描述它传递给 WdfIoTargetSendReadhronously 的缓冲区,并且 I/O 目标使用缓冲的 I/O (这要求使用虚拟地址而不是 MDL) 指定缓冲区,则框架会将缓冲区说明从 MDL 转换为虚拟地址和长度。 但是,如果驱动程序以正确的格式指定缓冲区,则效率会更高。
有关框架内存对象、旁观列表、MDL 和本地缓冲区的信息,请参阅 使用内存缓冲区。
有关何时删除内存缓冲区的信息,请参阅 内存缓冲区生命周期。
访问缓冲 I/O 的数据缓冲区
如果驱动程序使用缓冲 I/O,则其行为会根据数据请求的类型以及它使用的是 KMDF 还是 UMDF 而更改。
当 KMDF 驱动程序使用缓冲 I/O 时,I/O 管理器会创建一个中间缓冲区,驱动程序可以针对每种请求类型访问该缓冲区。 下面是发生的具体情况:
- 写入请求。 I/O 管理器在调用驱动程序堆栈之前从调用应用的输入缓冲区传输输入信息。 然后,KMDF 驱动程序从中间缓冲区读取输入信息并将其写入设备。
- 读取请求。 KMDF 驱动程序从设备读取信息并将其存储在中间缓冲区中。 然后,I/O 管理器将输出数据从中间缓冲区复制到应用的输出缓冲区。
- 设备 I/O 控制请求。 KMDF 驱动程序向或从中间缓冲区读取或写入该请求的数据。
当 UMDF 驱动程序使用缓冲 I/O 时,驱动程序主机进程会创建一个或两个中间缓冲区,具体取决于请求的类型。 下面是发生的具体情况:
- 写入请求。 框架创建一个缓冲区,从调用应用的输入缓冲区传输输入信息,然后调用驱动程序堆栈。 UMDF 驱动程序从中间缓冲区读取输入信息并将其写入设备。
- 读取请求。 UMDF 驱动程序从设备读取信息,并将其存储在框架创建的缓冲区中。 驱动程序主机进程将输出数据从中间缓冲区复制到应用的输出缓冲区。
- 设备 I/O 控制请求。 框架创建两个缓冲区,对应于驱动程序可以访问的 IOCTL 的输入和输出缓冲区。 框架将输入信息从 IOCTL 复制到新的中间缓冲区,并使其可供驱动程序使用。 框架不会复制输出缓冲区的内容,因此驱动程序不应尝试从中读取 (否则最终会) 读取垃圾数据。 驱动程序写入输出缓冲区的任何数据将复制回原始 IOCTL 缓冲区,并在成功完成 I/O 请求后返回到应用。 请注意,驱动程序写入输入缓冲区的任何数据将被丢弃,不会返回到调用应用。
若要检索表示缓冲区的框架内存对象的句柄,KMDF 和 UMDF 驱动程序调用 WdfRequestRetrieveInputMemory 或 WdfRequestRetrieveOutputMemory,具体取决于这是读取请求还是写入请求。 然后,驱动程序可以通过调用 WdfMemoryGetBuffer 来检索指向缓冲区的指针。 若要读取和写入缓冲区,驱动程序会调用 WdfMemoryCopyFromBuffer 或 WdfMemoryCopyToBuffer。
若要检索缓冲区的虚拟地址和长度,驱动程序会调用 WdfRequestRetrieveInputBuffer 或 WdfRequestRetrieveOutputBuffer。
若要为缓冲区分配和生成内存描述符列表 (MDL) ,KMDF 驱动程序调用 WdfRequestRetrieveInputWdmMdl 或 WdfRequestRetrieveOutputWdmMdl。
访问直接 I/O 的数据缓冲区
如果驱动程序使用直接 I/O,则 I/O 管理器验证 I/O 请求的发起方通常 (指定的用户模式应用程序) 缓冲区空间的可访问性,将缓冲区空间锁定到物理内存中,然后为驱动程序提供对缓冲区空间的直接访问权限。
如果驱动程序已指定了直接 I/O 的首选项,并且已满足直接 I/O 的所有 UMDF 要求 (请参阅 管理 UMDF 驱动程序) 中的缓冲区访问方法 ,框架会将其从 I/O 管理器接收的内存缓冲区直接映射到驱动程序的主机进程地址空间,从而为驱动程序提供对缓冲区空间的直接访问。
若要检索表示缓冲区空间的框架内存对象的句柄,驱动程序会调用 WdfRequestRetrieveInputMemory 或 WdfRequestRetrieveOutputMemory。 然后,驱动程序可以通过调用 WdfMemoryGetBuffer 来检索指向缓冲区的指针。 若要读取和写入缓冲区,驱动程序会调用 WdfMemoryCopyFromBuffer 或 WdfMemoryCopyToBuffer。
若要检索缓冲区空间的虚拟地址和长度,驱动程序会调用 WdfRequestRetrieveInputBuffer 或 WdfRequestRetrieveOutputBuffer。
如果设备的驱动程序使用直接 I/O,则 I/O 管理器使用 MDL 描述缓冲区。 为了检索指向缓冲区 MDL 的指针,KMDF 驱动程序调用 WdfRequestRetrieveInputWdmMdl 或 WdfRequestRetrieveOutputWdmMdl。 UMDF 驱动程序无法访问 MDL。
访问非缓冲 I/O 和直接 I/O 的数据缓冲区
如果驱动程序使用称为 非缓冲 I/O 或直接 I/O 方法 的缓冲区访问方法 (或“既不”方法,则) ,则 I/O 管理器只是为驱动程序提供为请求的缓冲区空间指定的 I/O 请求发起方虚拟地址。 I/O 管理器不会验证 I/O 请求的缓冲区空间,因此驱动程序必须验证缓冲区空间是否可访问,并将缓冲区空间锁定到物理内存中。
只能在 I/O 请求发起者的进程上下文中访问 I/O 管理器提供的虚拟地址。 只有驱动程序堆栈中的最高级别驱动程序保证在发起方的进程上下文中执行。
若要获取对 I/O 请求缓冲区空间的访问权限,最高级别的驱动程序必须提供 EvtIoInCallerContext 回调函数。 每次收到驱动程序的 I/O 请求时,框架都会调用此回调函数。
如果请求的缓冲区访问方法“两者都不是”,则 KMDF 驱动程序必须为每个缓冲区执行以下操作:
调用 WdfRequestRetrieveUnsafeUserInputBuffer 或 WdfRequestRetrieveUnsafeUserOutputBuffer 以获取缓冲区的虚拟地址。
调用 WdfRequestProbeAndLockUserBufferForRead 或 WdfRequestProbeAndLockUserBufferForWrite 来探测和锁定缓冲区,并获取缓冲区的框架内存对象的句柄。
将内存对象句柄保存在请求的 上下文空间中。
调用 WdfDeviceEnqueueRequest,它将请求返回到框架。
框架随后将请求添加到驱动程序的 I/O 队列之一。 如果驱动程序提供了 请求处理程序,框架最终将调用相应的请求处理程序。
请求处理程序可以从请求的上下文空间检索请求的内存对象句柄。 驱动程序可以将句柄传递给 WdfMemoryGetBuffer 以获取缓冲区的地址。
有时,最高级别驱动程序必须使用上述步骤来访问用户模式缓冲区,即使驱动程序未使用“两者”访问方法也是如此。 例如,假设驱动程序使用缓冲 I/O。 使用缓冲访问方法的 I/O 控制代码可能会传递包含指向用户模式缓冲区的嵌入指针的结构。 在这种情况下,驱动程序必须提供 EvtIoInCallerContext 回调函数,该函数从 结构中提取指针,然后使用前面的步骤 2 到 4。
UMDF 既不支持缓冲缓冲区也不支持直接 I/O 类型缓冲区,因此 UMDF 驱动程序永远不需要直接处理此类缓冲区。
但是,如果框架从 I/O 管理器接收此类缓冲区进行读取或写入,则会将其作为缓冲 I/O 或直接 I/O 提供给 UMDF 驱动程序,具体取决于驱动程序选择的访问方法。 如果框架收到指定“既不”缓冲区方法的 IOCTL,则它可以根据 INF 指令的存在,选择性地将 IOCTL 请求的缓冲区访问方法转换为缓冲 I/O 或直接 I/O。 有关详细信息 ,请参阅管理 UMDF 驱动程序中的缓冲区访问方法 。