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

框架队列对象表示 I/O 队列,这些队列是驱动程序接收的 I/O 请求的容器。 每个驱动程序可以为每个设备创建一个或多个 I/O 队列。 框架队列对象定义驱动程序可以提供的一组 事件回调函数 ,以及驱动程序可以调用的一组对象方法。

当框架收到定向到某个驱动程序设备的 I/O 请求时,框架会将该请求置于相应的 I/O 队列中。 如果驱动程序注册了一个或多个 请求处理程序,则框架可以在每次 I/O 请求可用时通知驱动程序。 或者,驱动程序可以轮询 I/O 队列中的请求。

大多数驱动程序在其 EvtDriverDeviceAdd 回调函数中创建 I/O 队列。 若要为设备创建 I/O 队列,驱动程序调用框架队列对象的 WdfIoQueueCreate 方法 (该方法) 创建框架队列对象。 驱动程序向 方法提供 WDF_IO_QUEUE_CONFIG 结构。 此结构包含有关队列的配置信息,例如队列的 调度方法和 指向 当请求 在队列中可用时框架调用的请求处理程序的指针。 结构还指示队列是否由 电源管理 ,以及驱动程序是否支持对队列的 I/O 请求使用零长度缓冲区。

如果驱动程序将 WDF_IO_QUEUE_CONFIG 结构的 DefaultQueue 成员设置为 TRUE,则队列将成为设备的默认 I/O 队列。 如果驱动程序创建默认 I/O 队列,框架会将设备的所有 I/O 请求置于此队列中,除非创建其他队列来接收某些请求。 驱动程序可以通过调用 WdfDeviceGetDefaultQueue 方法获取设备默认 I/O 队列的句柄。

如果想要对设备使用多个 I/O 队列,驱动程序可以调用 WdfIoQueueCreate 以根据需要创建任意数量的队列对象。 如果驱动程序创建多个队列,它可以调用 WdfDeviceConfigureRequestDispatching,这会指示框架将不同类型的请求定向到不同的队列。 例如,可以指定将所有读取请求传递到一个队列,并将所有写入请求传递到另一个队列。

如果驱动程序创建一组 I/O 队列并调用 WdfDeviceConfigureRequestDispatching 将驱动程序可以接收的每种类型的请求定向到特定队列,则驱动程序不需要默认队列。

如果驱动程序未为特定类型的请求提供 I/O 队列,并且驱动程序是函数驱动程序,则框架会以STATUS_INVALID_DEVICE_REQUEST完成状态值完成该类型的请求。 如果驱动程序是筛选器驱动程序,并且已调用 WdfFdoInitSetFilter,则框架会自动将这些请求转发到驱动程序堆栈中下一个较低的驱动程序。 例如,不处理读取请求的筛选器驱动程序不必提供接收读取请求的 I/O 队列。

I/O 请求的调度方法

当驱动程序调用 WdfIoQueueCreate 来创建 I/O 队列时,它会为队列指定调度方法。 框架提供三种调度方法: 顺序、 并行和 手动。 驱动程序可以为任何 I/O 队列(包括设备 的默认 I/O 队列)指定任何这些调度方法。

驱动程序通过在队列的WDF_IO_QUEUE_CONFIG结构中指定 WDF_IO_QUEUE_DISPATCH_TYPE类型的值来设置队列的 调度 方法。

顺序调度

如果驱动程序或设备一次只能处理来自队列的一个 I/O 请求,则应将设备的 I/O 队列设置为使用 顺序调度,这也称为 同步调度。 通过这种类型的调度,框架一次向驱动程序发送一个请求。 在驱动程序 完成、 取消或 重新排队 上一个请求之前,框架不会传递下一个请求。

框架将请求传递给驱动程序的请求 处理程序之一后,驱动程序将 处理请求。 如果驱动程序将请求转发到 常规 I/O 目标,它通常会调用 I/O 目标对象的同步方法之一。  驱动程序必须最终 完成 或 取消 它从 I/O 队列接收的每个请求。

已为顺序调度设置 I/O 队列的驱动程序可以调用 WdfIoQueueRetrieveNextRequest 或 WdfIoQueueRetrieveRequestByFileObject ,以便在最后一个接收的请求完成或取消之前从队列获取另一个请求。 你可能希望在函数驱动程序中执行此操作,以便驱动程序可以启动下一个硬件操作,而驱动程序的 EvtInterruptDpc 回调函数仍在处理来自上一个硬件操作的数据。

如果创建多个 I/O 队列并将其全部设置为按顺序调度,框架会按顺序调度来自每个队列的请求,但队列并行运行。 如果驱动程序或设备一次只能处理一个任何类型的请求,则必须使用具有 EvtIoDefault 回调函数的单个 I/O 队列。

并行调度

如果驱动程序和设备可以同时处理多个 I/O 请求,则可以将设备的 I/O 队列设置为使用 并行调度 ,以便驱动程序可以异步处理请求。 此调度方法也称为 异步调度。

如果驱动程序将 I/O 队列设置为使用并行调度,框架会将 I/O 请求在队列中可用后立即将其传送给驱动程序。 结果是驱动程序可能必须同时处理多个请求。

每当其中一个驱动程序 的请求处理程序 收到请求时,驱动程序都必须 处理该请求 ,然后 完成 请求。 如果驱动程序将请求转发到 常规 I/O 目标,它通常会调用 I/O 目标对象的异步方法之一。 驱动程序必须最终 完成 或 取消 它从 I/O 队列接收的每个请求。

使用并行调度的驱动程序可以调用 WdfIoQueueStop 或 WdfIoQueueStopSynchronously 来暂时停止队列,然后调用 WdfIoQueueStart 以重启队列。

手动调度

如果希望驱动程序完全控制 I/O 请求的传递,则可以将设备的 I/O 队列设置为使用 手动调度,这意味着框架不会向驱动程序传递请求,除非驱动程序明确请求。

若要从手动队列获取请求,驱动程序可以在轮询队列的循环中调用 WdfIoQueueRetrieveNextRequest 或 WdfIoQueueRetrieveRequestByFileObject 。 或者,驱动程序可以调用 WdfIoQueueReadyNotify 来注册一个回调函数,当队列中有一个或多个请求可用时框架将调用该回调函数。 框架调用回调函数后,驱动程序可以在循环中调用 WdfIoQueueRetrieveNextRequest 或 WdfIoQueueRetrieveRequestByFileObject 以检索请求。

驱动程序从队列获取请求后,必须 处理该请求。 驱动程序必须最终 完成 或 取消 每个请求。

请求处理程序

如果驱动程序为 I/O 队列指定了顺序调度方法或并行 调度方法 ,则框架在每次准备好向驱动程序传递队列请求之一时都会调用驱动程序提供的回调函数。

对于每个 I/O 队列,驱动程序可以提供以下一个或多个回调函数,这些回调函数称为 请求处理程序:

  • EvtIoRead:当队列中提供读取请求时,框架会调用 I/O 队列的 EvtIoRead 回调函数;
  • EvtIoWrite:当写入请求在队列中可用时,框架会调用 I/O 队列的 EvtIoWrite 回调函数;
  • EvtIoDeviceControl:当设备 I/O 控制请求在队列中可用时,框架会调用 I/O 队列的 EvtIoDeviceControl 回调函数;
  • EvtIoInternalDeviceControl:当队列中提供内部设备 I/O 控制请求时,框架会调用 I/O 队列的 EvtIoInternalDeviceControl 回调函数;
  • EvtIoDefault:如果驱动程序未提供关联的特定于请求类型的回调函数,则当任何请求可用时,框架会调用 I/O 队列的 EvtIoDefault 回调函数;

驱动程序在调用 WdfIoQueueCreate 为设备创建 I/O 队列时注册回调函数。

其中每个回调函数都接收两个输入参数:一个是框架传递给驱动程序的 I/O 请求句柄,一个是持有该请求的 I/O 队列的句柄。 回调函数可以通过调用 WdfIoQueueGetDevice 来确定目标设备。

框架在任意线程上下文中调用驱动程序的请求处理程序。 在任意线程上下文中执行时,驱动程序不应等待较长时间。 在某些情况下,驱动程序可能会使用内核调度程序对象作为同步机制。

删除 I/O 队列

基于框架的驱动程序必须仅删除它们创建的某些 I/O 队列。 如果驱动程序创建 默认 I/O 队列 或它通过调用 WdfDeviceConfigureRequestDispatching 配置的 I/O 队列,框架将删除驱动程序的队列对象。

例如,如果希望每个设备的 I/O 队列只要每个设备都插入系统,驱动程序就会在其 EvtDriverDeviceAdd 回调函数中创建其 I/O 队列。 驱动程序可能会创建一个默认队列,该队列接收除读取请求之外的所有请求,以及仅接收读取请求的单独队列。

驱动程序无法删除这些 I/O 队列。 相反,框架在删除队列所属的设备对象时删除队列对象。 

但是,如果驱动程序在其 EvtDriverDeviceAdd 回调函数之外创建临时 I/O 队列,则必须调用 WdfObjectDelete 以在使用完这些队列后删除这些队列。 例如,提供 EvtDeviceFileCreate 回调函数的驱动程序可能会创建 I/O 队列来处理与特定框架文件对象关联的 I/O 请求。 在这种情况下,驱动程序的 EvtFileCleanup 回调函数必须调用 WdfIoQueuePurge 来清除队列,然后调用 WdfObjectDelete 将其删除。

注意 框架不允许驱动程序删除其默认 I/O 队列,也不允许驱动程序通过调用 WdfDeviceConfigureRequestDispatching来接收特定类型的所有 I/O 请求 。 如果驱动程序调用 WdfObjectDelete 以删除表示其中一个队列的队列对象, 则 WdfObjectDelete 返回而不删除该对象。 WdfObjectDelete 不提供返回状态,因此仅当 使用框架的验证程序时,框架才会报告错误。

管理 I/O 队列
启动 I/O 队列

当驱动程序调用 WdfIoQueueCreate 来创建 I/O 队列时,框架会自动使队列能够接收 I/O 请求并将其传递给驱动程序。

驱动程序通常从 EvtDriverDeviceAdd 回调函数中调用 WdfIoQueueCreate。 在驱动程序的 EvtDriverDeviceAdd 回调函数返回后,框架可以开始向驱动程序传递 I/O 请求。

如果驱动程序使用 电源管理的 I/O 队列,则框架无法开始向驱动程序传递请求,直到设备进入其工作状态,并且框架已调用驱动程序的 EvtDeviceD0Entry 回调函数。

停止和重启 I/O 队列

驱动程序可以调用 WdfIoQueueStop 或 WdfIoQueueStopSynchronously ,以暂时阻止框架从 I/O 队列传递 I/O 请求。 若要恢复 I/O 请求的传递,驱动程序会调用 WdfIoQueueStart。

如果驱动程序使用电源管理的 I/O 队列,则当设备离开其工作 (D0) 状态时,框架会自动停止设备的队列,并在设备状态返回到 D0 时重启队列。

将请求添加到 I/O 队列

当系统向驱动程序发送读取、写入或设备 I/O 控制请求时,框架会将该请求置于 I/O 队列中。 驱动程序可以通过调用 WdfDeviceConfigureRequestDispatching 来控制框架存储在每个队列中的请求类型。

驱动程序还可以通过调用 WdfRequestForwardToIoQueue 来重新排队从框架收到的请求。

从 I/O 队列获取请求

如果驱动程序为 I/O 队列指定顺序或并行 调度方法 ,它将在 请求处理程序中接收请求。

如果驱动程序指定手动或顺序调度方法,它可以通过调用 WdfIoQueueRetrieveNextRequest 或 WdfIoQueueRetrieveRequestByFileObject 来获取请求。

搜索 I/O 请求

如果驱动程序为 I/O 队列指定手动 调度方法 ,则可以使用以下步骤搜索队列中的特定请求:

  • 调用 WdfIoQueueFindRequest 以查找与驱动程序指定的条件匹配的请求;
  • 调用 WdfIoQueueRetrieveFoundRequest 以检索 WdfIoQueueFindRequest 所在的请求;
清除或清空 I/O 队列

清除 I/O 队列意味着停止将 I/O 请求插入队列,并取消队列中已有的任何请求。

清空 I/O 队列意味着停止将 I/O 请求插入队列,同时允许将队列中已有的任何请求传递到驱动程序。

驱动程序通常仅在队列不受电源管理的情况下清除或清空其队列。 对于电源管理的 I/O 队列,驱动程序可以提供 EvtIoStop 和 EvtIoResume 回调函数。

如果某些驱动程序的队列不受电源管理,则当队列的关联设备或 I/O 通道不可用时,可能需要清除或清空队列。 通常,除非每个请求很有可能包含非常重要的信息,否则将清除队列,而不是清空队列。 例如,网络设备的驱动程序可能会清除其队列,而存储设备的驱动程序可能会清空其队列。

如果希望驱动程序清除或清空 I/O 队列,驱动程序可以调用以下队列对象方法之一:

  • WdfIoQueuePurge 或 WdfIoQueuePurgeSynchronously,用于停止将 I/O 请求排队到 I/O 队列并取消未处理的请求;
  • WdfIoQueueDrain 或 WdfIoQueueDrainSynchronously,用于停止将 I/O 请求排队到 I/O 队列,同时允许传递和处理已排队的请求;

调用 WdfIoQueueDrain 和 WdfIoQueueDrainSynchronously 时要小心。 由于清空操作会等待请求完成,因此,如果确定队列的挂起请求将及时完成,则只应清空队列。 如果不知道 I/O 请求需要多长时间才能完成,并且取消未完成的请求是可以接受的,请考虑清除队列。

将请求从一个 I/O 队列移动到另一个 I/O 队列

在驱动程序收到 I/O 请求后,你可能希望驱动程序将请求重新排队到不同的 I/O 队列中。 为此,驱动程序调用 WdfRequestForwardToIoQueue 或 WdfRequestForwardToParentDeviceIoQueue,这将请求添加到指定队列的尾部。 最终,框架将使用指定队列的调度方法再次将请求传送到驱动程序。

在 I/O 请求排队之前截获该请求

驱动程序可以在框架将请求放入 I/O 队列之前截获 I/O 请求。 若要截获 I/O 请求,驱动程序必须调用 WdfDeviceInitSetIoInCallerContextCallback 来注册 EvtIoInCallerContext 回调函数。

框架将 EvtIoInCallerContext 回调函数与设备相关联。 因此,框架每次收到系统要发送到设备的请求时,都会调用 EvtIoInCallerContext 回调函数。

通常,当 EvtIoInCallerContext 回调函数收到请求时,它会对该请求执行一些初步处理。 接下来,回调函数调用 WdfDeviceEnqueueRequest,这将请求返回给框架。 然后,框架可以将请求置于适当的 I/O 队列中,就像未调用 EvtIoInCallerContext 回调函数时一样。

驱动程序可能提供 EvtIoInCallerContext 回调函数的主要原因是驱动程序必须处理支持 I/O 方法的 I/O 操作,该方法既 不调用缓冲,也不直接 I/O。 对于此 I/O 方法,驱动程序必须访问 I/O 请求发起者的进程上下文中收到的缓冲区。

获取 I/O 队列属性

若要获取框架队列对象的属性,驱动程序可以调用以下方法:

  • WdfIoQueueGetDevice,用于获取队列对象所属的设备对象的句柄;
  • WdfIoQueueGetState,用于获取有关队列 的状态信息 ;
使用通过电源管理的 I/O 队列

驱动程序创建 I/O 队列时,可以指定队列是否由 电源管理。 当 I/O 请求在电源管理的队列中可用时,框架仅当设备处于其工作 (D0) 状态时,才会将请求传送到驱动程序。 在框架从电源管理的队列传递到驱动程序的所有 I/O 请求都已完成、取消或推迟之前,框架不允许设备离开其工作状态。

Power-Managed队列的回调函数

如果驱动程序使用电源管理的 I/O 队列,它可以提供两个附加的回调函数:

  • EvtIoStop:EvtIoStop 回调函数停止处理指定的 I/O 请求。 当设备离开其工作 (D0) 状态或删除时,框架会针对驱动程序尚未完成的每个 I/O 请求(包括驱动程序拥有的请求和已转发到 I/O 目标的请求)调用 I/O 队列的 EvtIoStop 回调函数一次;
  • EvtIoResume:EvtIoResume 回调函数继续处理以前停止的 I/O 请求。 当框架在设备返回到其工作状态后,从队列继续向驱动程序传递 I/O 请求时,会调用 I/O 队列的 EvtIoResume 回调函数;

每次框架调用驱动程序的 EvtIoStop 回调函数时,该函数通常会 完成 或 取消 I/O 请求,或调用 WdfRequestStopAcknowledge 将请求的所有权返回到框架。

虽然这样做是可选的,但通常应该为电源管理的队列提供 EvtIoStop 回调函数。 通过提供 EvtIoStop,驱动程序可以帮助缩短设备(可能是系统)进入低功耗状态之前经过的时间。

如果不为电源管理的队列提供 EvtIoStop ,框架将等到从电源托管队列传递到驱动程序的所有请求完成之后,再将设备 (或系统) 移动到较低的电源状态或删除设备。 这种不作为可能会阻止系统进入其休眠状态或另一个低系统电源状态。 在极端情况下,它可能会导致系统崩溃,并出现 bug 检查代码 9F。

如果驱动程序未将请求转发到 I/O 目标,并且未在不确定的时间内保留请求,则可以安全地省略电源托管队列的 EvtIoStop 。

正在等待 Dispatcher 对象

通常,驱动程序应仅使用调度程序对象作为非比特线程上下文中的同步机制。

由于 请求处理程序 在任意线程上下文中运行,因此电源托管队列的请求处理程序不得等待内核调度程序对象设置。 这样做可能会导致死锁。

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 队列中的所有 I/O 请求都已取消。

驱动程序调用 WdfIoQueueCreate 后,框架可以将新的 I/O 队列设置为就绪状态。 但是,仅当设备处于工作状态 (D0) 状态时, 电源管理的 I/O 队列 才会进入就绪状态。

驱动程序可以通过以下方式更改 I/O 队列的状态:

  • 调用 WdfIoQueueStop 或 WdfIoQueueStopSynchronously 将队列置于停止状态;
  • 调用 WdfIoQueueDrain 或 WdfIoQueueDrainSynchronously 以将队列置于其排空状态;
  • 调用 WdfIoQueuePurge 或 WdfIoQueuePurgeSynchronously 将队列置于其清除状态;
  • 调用 WdfIoQueueStart 以将队列返回到其就绪状态;

若要获取 I/O 队列的当前状态,驱动程序可以调用 WdfIoQueueGetState。

相关推荐

  1. WDF驱动开发-I/O请求处理()

    2024-06-12 06:30:04       8 阅读
  2. WDF驱动开发-I/O请求处理(四)

    2024-06-12 06:30:04       7 阅读
  3. WDF驱动开发-电源策略()

    2024-06-12 06:30:04       7 阅读
  4. windows驱动开发-I/O请求()

    2024-06-12 06:30:04       15 阅读
  5. WDF驱动开发-PNP和电源管理()

    2024-06-12 06:30:04       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-12 06:30:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-12 06:30:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-12 06:30:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-12 06:30:04       18 阅读

热门阅读

  1. 海外盲盒小程序背后的技术支撑与实现

    2024-06-12 06:30:04       5 阅读
  2. CAPL如何在底层模拟TCP Server端建立TCP连接

    2024-06-12 06:30:04       11 阅读
  3. 低代码开发应用:国企数字化转型的思考与探索

    2024-06-12 06:30:04       7 阅读