WDF驱动开发-I/O请求的处理(三)

创建框架请求对象

框架请求对象表示 I/O 管理器已发送到驱动程序的 I/O 请求。 基于框架的驱动程序通过调用 框架请求对象方法来处理每个 I/O 请求。

每个 I/O 请求都包含一个 WDM I/O 请求数据包 (IRP 结构) ,但基于框架的驱动程序通常不需要访问 IRP 结构。

大多数框架请求对象由框架创建,但驱动程序也可以创建请求对象。

由框架创建的请求对象

当基于框架的驱动程序从 I/O 管理器接收 I/O (IRP) 的 I/O 请求数据包时,框架会截获 IRP 并创建框架请求对象。 框架将请求对象放入 I/O 队列,如果驱动程序已注册队列 的请求处理程序 ,则调用相应的处理程序。

下图演示了框架为读取操作创建请求对象时发生的步骤:

以下步骤对应于上图中的数字:

  1. 用户模式应用程序通过调用 Microsoft Win32 ReadFile 函数读取文件;
  2. ReadFile 函数调用在内核模式下运行的 I/O 管理器;
  3. I/O 管理器分配 IRP 结构,并将 IRP_MJ_READ 函数代码存储在 结构中;
  4. I/O 管理器调用驱动程序 x 的 DispatchRead 标准驱动程序例程,传递指向 IRP 结构的指针。 由于驱动程序 x 是基于框架的驱动程序,因此框架提供驱动程序的 DispatchRead 例程;
  5. 框架创建一个表示 IRP 结构的请求对象。 框架将请求对象添加到驱动程序的队列对象之一;
  6. 框架调用驱动程序的 EvtIoRead 请求处理程序,传递队列对象句柄和请求对象句柄;
请求驱动程序创建的对象

基于框架的驱动程序还可以创建请求对象。 例如,如果驱动程序收到一个读取或写入请求,该请求量大于驱动程序的 I/O 目标 一次可以处理的数据量,则驱动程序可能会创建请求对象。 在这种情况下,驱动程序可以将数据划分为多个较小的请求,并使用其他请求对象将这些较小的请求发送到一个或多个 I/O 目标。

若要创建请求对象,驱动程序应调用 WdfRequestCreate ,后跟初始化请求的框架对象方法,例如 WdfUsbTargetPipeFormatRequestForRead。

如果驱动程序在 WDM 调度例程中接收 WDM IRP,然后使用框架对其进行服务或转发,则该驱动程序可以调用 WdfRequestCreateFromIrp。

使用请求对象上下文

每个框架请求对象(无论是由框架创建还是由驱动程序创建)都可以包含驱动程序定义的上下文空间。 当基于框架的驱动程序初始化框架设备对象时,驱动程序可以调用 WdfDeviceInitSetRequestAttributes 来指定描述设备请求对象的上下文空间的 WDF_OBJECT_ATTRIBUTES 结构。

框架为请求对象分配上下文空间,如下所示:

  • 当框架为驱动程序创建请求对象时,它会使用驱动程序在调用 WdfDeviceInitSetRequestAttributes 时指定的大小来分配上下文空间;
  • 如果驱动程序通过调用 WdfRequestCreate 创建其他请求对象,可以通过提供WDF_OBJECT_ATTRIBUTES结构来指定上下文大小;
请求所有权

当 I/O 管理器将 I/O 请求发送到基于框架的驱动程序时,框架会截获该请求并创建框架请求对象。 框架"拥有"请求对象,因为只有框架可以访问请求并对该对象执行操作。

框架创建请求对象后,会将该对象置于驱动程序的 I/O 队列之一中。 框架继续拥有请求对象,直到它从队列中删除请求并将其传递给驱动程序。

驱动程序 收到 请求对象后,它拥有该请求。 驱动程序可以通过句柄访问请求对象,并对该对象执行操作。 虽然驱动程序拥有请求对象,但它可以 重新排队、 完成、 取消或 转发 请求,之后它不再拥有请求对象,并且无法访问它。

当请求对象的所有权在驱动程序和框架之间传递时,对象句柄的值不会更改。 例如,如果驱动程序从 I/O 队列接收请求,将其重新排队到其他队列,然后再次接收请求,则句柄的值不会更改。 同样,如果驱动程序将请求转发到 I/O 目标,然后收到 I/O 目标已完成请求的通知,则驱动程序的通知回调函数将接收驱动程序提供给 I/O 目标的相同句柄值。

将 I/O 请求重新排队

驱动程序可以重新排队从 I/O 队列获取的 I/O 请求。 驱动程序可以将 I/O 请求重新排到驱动程序为同一设备创建的另一个 I/O 队列。 此外, 总线驱动程序 可以将 I/O 请求从子设备的 I/O 队列重新排队到父设备的 I/O 队列。

将 I/O 请求重新排队到设备的不同 I/O 队列

在驱动程序的请求处理程序从驱动程序的 I/O 队列收到 I/O 请求后,驱动程序可以调用 WdfRequestForwardToIoQueue 将请求重新排队到另一个队列。

例如,如果希望驱动程序在处理请求之前将资源分配给请求,驱动程序的 EvtIoDefault 回调函数可以接收所有请求,将资源信息存储在每个请求的上下文内存中,然后调用 WdfRequestForwardToIoQueue 将每个请求重新排队到其他队列。

如果驱动程序调用 WdfRequestForwardToIoQueue 来重新排队驱动程序从使用顺序 调度方法的 I/O 队列中获取的 I/O 请求,则框架会将下一个 I/O 请求从顺序队列传递到驱动程序,而无需等待重新排队的请求完成。

如果驱动程序使用手动调度方法,它可以调用 WdfRequestRequeue 方法,将 I/O 请求返回到驱动程序从中获取它的 I/O 队列的头。 调用 WdfRequestRequeue 后,驱动程序对 WdfIoQueueRetrieveNextRequest 的下一次调用将检索重新排队的请求。

将 I/O 请求重新排队到父设备的 I/O 队列

父设备的函数驱动程序可以充当 总线驱动程序 ,该驱动程序 枚举 父设备的子设备,并为子 设备创建物理设备对象 (PDO) 。 此类驱动程序有时可以接收父设备必须处理的子设备的 I/O 请求。

例如,协议总线 ((如 USB) )通常控制分配给每个已连接设备的硬件资源。 因此,父总线的函数驱动程序通常处理每个子设备的 I/O 操作。 当 I/O 管理器将 I/O 请求发送到其中一个子 设备的设备堆栈 时,总线的函数驱动程序会在子设备的某个 I/O 队列中接收 I/O 请求,因为该驱动程序创建了子设备的 PDO。 在驱动程序可以在父总线设备的上下文中处理 I/O 请求之前,它必须将子设备的 I/O 队列中的 I/O 请求重新排队到属于父设备的 I/O 队列。

但是,驱动程序无法调用 WdfRequestForwardToIoQueue 将请求从子队列移动到父队列。 由于 I/O 管理器为父设备和子设备创建单独的设备堆栈,因此必须先将基础 WDM 设备对象从表示子设备的对象更改为表示父设备的对象。

在 KMDF 版本 1.9 之前,驱动程序只能通过创建远程 I/O 目标、增加子设备的设备堆栈大小以及指定正确的 WDM 设备对象,将 I /O 请求从子设备发送到其父设备。

从 KMDF 版本 1.9 开始,驱动程序可以在创建子设备之前调用 WdfPdoInitAllowForwardingRequestToParent ,然后调用 WdfRequestForwardToParentDeviceIoQueue 以将请求从子级 I/O 队列重新排队到父队列。 如果驱动程序使用WdfPdoInitAllowForwardingRequestToParent 和 WdfRequestForwardToParentDeviceIoQueue,框架将增加子级的设备堆栈大小,并将正确的 WDM 设备对象分配给 I/O 请求。

完成 I/O 请求

每个基于框架的驱动程序最终都必须完成它从框架收到的每个 I/O 请求。 驱动程序通过调用请求对象的 WdfRequestComplete、 WdfRequestCompleteWithInformation 或 WdfRequestCompleteWithPriorityBoost 方法完成请求。

何时完成请求

驱动程序在确定以下情况之一为 true 时,必须完成请求:

  • 请求的 I/O 操作已成功完成;
  • 请求的 I/O 操作已启动,但在完成之前失败;
  • 请求的 I/O 操作不受支持,或在收到操作时无效,无法启动;
  • 请求的 I/O 操作已取消;

如果驱动程序通过在设备上创建 I/O 活动来为 I/O 请求提供服务,驱动程序通常会从其 EvtInterruptDpc 或 EvtDpcFunc 回调函数调用 WdfRequestComplete。

如果驱动程序收到不受支持的请求或无效请求,它通常从接收请求的请求处理程序调用 WdfRequestComplete。

如果 I/O 操作被取消,驱动程序通常会从其 EvtRequestCancel 回调函数调用 WdfRequestComplete。

如果驱动程序将 I/O 请求 转发 到 I/O 目标,驱动程序在 I/O 目标完成请求后完成请求,如下所示:

  • 如果驱动程序将 I/O 请求 同步 转发到 I/O 目标,则驱动程序对 I/O 目标的调用仅在较低级别的驱动程序完成请求 后返回,除非发生错误。 I/O 目标返回后,驱动程序必须调用 WdfRequestComplete;
  • 如果驱动程序 以异步方式转发 I/O 请求,你将希望在较低级别的驱动程序完成请求时收到通知。 如果驱动程序注册 CompletionRoutine 回调函数,框架会在 I/O 目标完成请求后调用此回调函数。 CompletionRoutine 回调函数通常调用 WdfRequestComplete;

若要注册 CompletionRoutine 回调函数,驱动程序必须先调用 WdfRequestSetCompletionRoutine ,然后才能将 I/O 请求转发到 I/O 目标。

如果在 I/O 目标完成异步转发的 I/O 请求时不需要通知驱动程序,则驱动程序不必注册 CompletionRoutine 回调函数。 相反,驱动程序可以在调用 WdfRequestSend 时设置WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET标志。 在这种情况下,驱动程序不调用 WdfRequestComplete。

驱动程序无法完成它通过调用 WdfRequestCreate 或 WdfRequestCreateFromIrp 创建的 I/O 请求。 相反,驱动程序必须调用 WdfObjectDelete 来删除请求对象,通常是在 I/O 目标完成请求之后。

例如,驱动程序可能会收到读取或写入请求,该请求的数据量大于驱动程序的 I/O 目标一次可以处理的数据量。 驱动程序必须将数据划分为多个较小的请求,并将这些较小的请求发送到一个或多个 I/O 目标。 处理这种情况的方法包括:

  • 调用 WdfRequestCreate 以创建表示较小请求的单个附加请求对象:驱动程序可以同步将此请求发送到 I/O 目标。 较小请求的 CompletionRoutine 回调函数可以调用 WdfRequestReuse ,以便驱动程序可以重用请求并再次将其发送到 I/O 目标。 在 I/O 目标完成最后一个较小的请求后, CompletionRoutine 回调函数可以调用 WdfObjectDelete 以删除驱动程序创建的请求对象,驱动程序可以调用 WdfRequestComplete 来完成原始请求;
  • 调用 WdfRequestCreate 以创建表示较小请求的其他几个请求对象:驱动程序的 I/O 目标可以异步处理这些多个较小的请求。 驱动程序可以为每个较小的请求注册 CompletionRoutine 回调函数。 每次调用 CompletionRoutine 回调函数时,它都可以调用 WdfObjectDelete 来删除驱动程序创建的请求对象。 I/O 目标完成所有较小的请求后,驱动程序可以调用 WdfRequestComplete 来完成原始请求;
提供完成信息

驱动程序完成请求后,可以选择提供其他驱动程序可以访问的一些附加信息。 例如,驱动程序可以提供为读取或写入请求传输的字节数。 若要提供此信息,驱动程序可以执行以下任一操作:

  • 在调用 WdfRequestComplete 之前调用 WdfRequestSetInformation;
  • 调用 WdfRequestCompleteWithInformation;
获取完成信息

若要获取有关另一个驱动程序已完成的 I/O 请求的信息,驱动程序可以:

  • 调用 WdfRequestGetStatus 以获取较低级别驱动程序在调用 WdfRequestComplete 时指定的完成状态值;
  • 调用 WdfRequestGetCompletionParams 以获取包含有关已完成请求的其他信息的 WDF_REQUEST_COMPLETION_PARAMS 结构,例如表示请求缓冲区的内存对象的句柄或特定于总线的信息。驱动程序只能在调用 WdfRequestSend 以同步或异步方式将 I/O 请求发送到 I/O 目标之后调用 WdfRequestGetCompletionParams。 驱动程序调用将 I/O 请求仅 同步发送到 I/O 目标的方法之一(例如 WdfIoTargetSendReadSynchronously) 后,不得调用 WdfRequestGetCompletionParams;
  • 调用 WdfRequestGetInformation 以获取较低级别驱动程序在调用 WdfRequestSetInformation 或 WdfRequestCompleteWithInformation 时指定的附加 I/O 完成信息,如果驱动程序堆栈中的驱动程序提供此类信息;

如果驱动程序以同步方式发送 I/O 请求,则它通常在同步调用返回后调用 WdfRequestGetStatus、 WdfRequestGetCompletionParams 和 WdfRequestGetInformation 。 如果驱动程序异步发送 I/O 请求,它通常从 CompletionRoutine 回调函数中调用这些方法。

相关推荐
编程小白20261 小时前
从 C++ 基础到效率翻倍:Qt 开发环境搭建与Windows 神级快捷键指南
开发语言·c++·windows·qt·学习
凯子坚持 c3 小时前
CANN 性能剖析实战:从原始事件到交互式火焰图
windows·microsoft
开开心心就好4 小时前
发票合并打印工具,多页布局设置实时预览
linux·运维·服务器·windows·pdf·harmonyos·1024程序员节
獨枭4 小时前
PyCharm 跑通 SAM 全流程实战
windows
仙剑魔尊重楼4 小时前
音乐制作电子软件FL Studio2025.2.4.5242中文版新功能介绍
windows·音频·录屏·音乐·fl studio
PHP小志5 小时前
Windows 服务器怎么修改密码和用户名?账户被系统锁定如何解锁
windows
专注VB编程开发20年6 小时前
vb.net datatable新增数据时改用数组缓存
java·linux·windows
仙剑魔尊重楼6 小时前
专业音乐制作软件fl Studio 2025.2.4.5242中文版新功能
windows·音乐·fl studio
rjc_lihui8 小时前
Windows 运程共享linux系统的方法
windows
失忆爆表症8 小时前
01_项目搭建指南:从零开始的 Windows 开发环境配置
windows·postgresql·fastapi·milvus