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

取消 I/O 请求

应用程序、系统或驱动程序可以取消设备正在进行的 I/O 操作 (例如从磁盘) 读取多个块的请求。 如果取消设备的 I/O 操作,I/O 管理器将尝试取消与 I/O 操作关联的所有未处理的 I/O 请求。 设备的驱动程序可以注册,以便在 I/O 管理器尝试取消 I/O 请求时收到通知,并且驱动程序可以通过以完成状态为STATUS_CANCELLED 来完成 它们来取消其拥有的请求。

框架处理基于框架的驱动程序的某些取消工作。 如果取消设备的 I/O 操作,框架将完成以下 I/O 请求, 完成状态为与取消的操作关联的STATUS_CANCELLED:

  • 框架已放入驱动程序的默认 I/O 队列中的未传递 I/O 请求;
  • 由于驱动程序调用 了 WdfDeviceConfigureRequestDispatching,框架已转发到另一个队列的未传递 I/O 请求;

由于框架取消这些请求,因此不会将这些请求传递给驱动程序。

框架向驱动程序传递 I/O 请求后,驱动程序 拥有 该请求,并且框架无法取消该请求。 此时,只有驱动程序可以取消 I/O 请求,但框架必须通知驱动程序应取消请求。 驱动程序通过提供 EvtRequestCancel 回调函数接收此通知。

有时驱动程序从 I/O 队列接收 I/O 请求,但驱动程序会将请求重新排到同一个或其他 I/O 队列,以便以后处理,而不是处理该请求。 这种情况的示例包括:

  • 框架将 I/O 请求传递到驱动程序的请求 处理程序之一,驱动程序随后调用 WdfRequestForwardToIoQueue (或 WdfRequestForwardToParentDeviceIoQueue) 将请求放入其他队列或 WdfRequestRequeue 以将请求放回到同一队列中;
  • 框架向驱动程序的 EvtIoInCallerContext 回调函数传递 I/O 请求,驱动程序调用 WdfDeviceEnqueueRequest 将请求传递回框架,框架随后将请求置于驱动程序的 I/O 队列之一中;

在这些情况下,框架可以取消 I/O 请求,因为请求位于 I/O 队列中。 但是,如果驱动程序已为请求所在的 I/O 队列注册了 EvtIoCanceledOnQueue 回调函数,则框架会在取消关联的 I/O 操作时调用回调函数,而不是取消请求。 如果框架调用驱动程序的 EvtIoCanceledOnQueue 回调函数,则驱动程序必须 完成 请求。

总之,取消 I/O 操作时,框架始终取消从未传递到驱动程序的所有关联 I/O 请求。 如果驱动程序收到请求,然后重新排队,框架将取消请求 (如果请求位于队列) ,除非驱动程序为 I/O 队列提供 EvtIoCanceledOnQueue 回调函数。

调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx
驱动程序可以调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx 来注册 EvtRequestCancel 回调函数。 如果驱动程序已调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx,并且取消了与请求关联的 I/O 操作,框架将调用驱动程序的 EvtRequestCancel 回调函数,以便驱动程序可以取消 I/O 请求。

驱动程序应调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx (如果它将拥有相对较长的一段时间的请求)。 例如,驱动程序可能必须等待设备响应,或者可能等待较低的驱动程序完成驱动程序在收到单个请求时创建的一组请求。

如果驱动程序未调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx,或者驱动程序在调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx 后调用 WdfRequestUnmarkCancelable,则驱动程序不知道取消,因此会像平时一样处理请求。

调用 WdfRequestIsCanceled

如果驱动程序未调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx 来注册 EvtRequestCancel 回调函数,它可以调用 WdfRequestIsCanceled 来确定 I/O 管理器是否已尝试取消 I/O 请求。 如果 WdfRequestIsCanceled 返回 TRUE 并且驱动程序拥有该请求,则驱动程序应取消该请求。 如果驱动程序不拥有请求,则不应调用 WdfRequestIsCanceled。

在以下情况下,未调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx 的驱动程序可能会调用 WdfRequestIsCanceled :

  • 等待设备中断的驱动程序可能会从其 EvtInterruptDpc 回调函数调用 WdfRequestIsCanceled;
  • 轮询其设备的驱动程序可能会从其轮询线程调用 WdfRequestIsCanceled ;
  • 将 DMA 事务 分解为多个较小传输的驱动程序可能会在每次传输完成后调用 WdfRequestIsCanceled ;

如果驱动程序尚未为收到的请求调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx,则接收大型读取或写入请求的驱动程序可能会在驱动程序的 I/O 目标完成每个较小的请求后调用 WdfRequestIsCanceled。

取消请求

取消 I/O 请求可能涉及以下任何一项:

  • 停止正在进行的 I/O 操作;
  • 不将请求转发到 I/O 目标;
  • 调用 WdfRequestCancelSentRequest 以尝试取消驱动程序之前已提交到 I/O 目标的请求;

如果驱动程序取消该驱动程序从框架接收的请求对象的 I/O 请求,则驱动程序必须始终通过调用 WdfRequestComplete、 WdfRequestCompleteWithInformation 或 WdfRequestCompleteWithPriorityBoost 来完成请求,并且 状态 参数为 STATUS_CANCELLED。 如果驱动程序调用 WdfRequestCreate 来创建请求对象,驱动程序将调用 WdfObjectDelete 而不是完成 request。

同步取消和完成代码

如果驱动程序调用 WdfRequestMarkCancelable 或 WdfRequestMarkCancelableEx 以使 I/O 请求可取消,则可能存在同步问题。 例如,驱动程序和设备可能通过 EvtInterruptIsr 和 EvtInterruptDpc 回调函数异步执行设备 I/O 操作, EvtInterruptDpc 和 EvtRequestCancel 回调函数可能包含对 WdfRequestComplete 的调用。

驱动程序必须仅调用 WdfRequestComplete 一次,才能完成或取消请求。 但是如果 EvtInterruptDpc 和 EvtRequestCancel 回调函数彼此不同步,框架可以在另一个正在执行时调用其中一个。

如果驱动程序使用框架的 自动同步,则避免此问题很容易,因为自动同步可确保一次调用一个回调函数。

如果驱动程序不使用框架的自动同步,则可以使用 框架锁 来同步取消和完成代码。

无论驱动程序是使用框架的自动同步还是提供自己的同步,驱动程序的 EvtRequestCancel 回调函数都必须调用 WdfRequestComplete 来取消请求。 驱动程序的 EvtInterruptDpc 回调函数应调用 WdfRequestUnmarkCancelable ,如下所示:

Status = WdfRequestUnmarkCancelable(Request);
if( Status != STATUS_CANCELLED ) {
    WdfRequestComplete(Request, RequestStatus);
    }

如果驱动程序已调用 WdfRequestComplete 来取消请求,则此代码可确保驱动程序不会调用 WdfRequestComplete 来完成请求。

将取消已发送请求的操作同步

当驱动程序尝试取消它已转发到 I/O 目标的 I/O 请求时,驱动程序必须确保将有效的请求句柄传递给 WdfRequestCancelSentRequest 方法。 如果 I/O 目标完成请求,则请求句柄将变为无效,因为驱动程序的 CompletionRoutine 回调函数将调用 WdfRequestComplete (尝试) 删除请求对象。

若要避免此问题,驱动程序可以通过创建请求对象的 集合 等方式跟踪已发送到 I/O 目标的请求。 驱动程序可以调用 WdfSpinLockAcquire 来同步对集合的访问。

调用驱动程序的 CompletionRoutine 回调函数时,它会获取锁,从集合中删除已完成请求的句柄,并调用 WdfSpinLockRelease 以释放锁。

在尝试取消驱动程序已转发到 I/O 目标的请求之前,驱动程序可以:

  • 调用 WdfSpinLockAcquire 以获取旋转锁;
  • 在集合中查找请求对象的句柄,以确保驱动程序的完成例程尚未完成请求,并从集合中删除了句柄;
  • 调用 WdfObjectReference 以递增请求对象的引用计数,以便无法删除该对象;
  • 调用 WdfSpinLockRelease 以释放旋转锁;
  • 调用 WdfRequestCancelSentRequest;
  • 调用 WdfObjectDereference 以递减对象的引用计数;

此序列可确保如果 I/O 目标在驱动程序调用 WdfRequestCancelSentRequest 之前完成请求,则即使驱动程序的 CompletionRoutine 回调函数调用 WdfRequestComplete,由于递增的引用计数,请求的句柄仍有效。

转发 I/O 请求

当驱动程序收到无法处理的 I/O 请求时,它通常会执行以下操作之一:

  • 它将收到的请求转发给另一个驱动程序;
  • 它会创建其他请求并将其发送到另一个驱动程序;

基于框架的驱动程序 使用 I/O 目标转发请求,这些目标表示系统上的其他驱动程序。 驱动程序可以使用以下任一技术将请求转发到 I/O 目标:

  • 驱动程序可以通过调用 WdfDeviceGetIoTarget、 WdfRequestFormatRequestUsingCurrentType 和 WdfRequestSend,将 I/O 请求转发到下一个较低的驱动程序。注意仅当驱动程序收到请求时,此方法才有用,该请求不需要在转发之前对其进行修改;
  • 驱动程序可以调用 WdfFdoInitSetFilter 将自身注册为filter驱动程序。如果filter驱动程序未为特定类型的 I/O 请求提供 I/O 队列,框架会自动将该类型的请求转发到下一个较低的驱动程序;
  • 通常,功能驱动程序会检查每个 I/O 请求的内容。 如果功能驱动程序无法处理请求,它可能会修改请求并将其转发到 I/O 目标。 或者,它可能会创建一个或多个新请求并将其发送到 I/O 目标。框架的 I/O 目标对象定义了几种用于将 I/O 请求发送到其他驱动程序的方法。 例如,驱动程序可以调用 WdfIoTargetFormatRequestForRead,后跟 WdfRequestSend,以将读取请求发送到 I/O 目标。 极少数情况下,驱动程序编写器可能需要在将请求发送到 I/O 目标之前指定请求的基础 WDM I/O 堆栈位置 的内容。 对于这些情况,驱动程序可以在调用 WdfRequestSend 之前调用 WdfRequestWdmFormatUsingStackLocation;

有时,驱动程序必须将相同的请求发送到多个 I/O 目标,通常是因为驱动程序必须向其所有设备发送单个命令。 在向 I/O 目标发送请求之前,驱动程序可以调用 WdfRequestChangeTarget 来验证 I/O 目标是否可访问。

驱动程序最终必须完成它转发到 I/O 目标的每个请求,除非它在调用 WdfRequestSend 时设置WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET标志。

请注意,当驱动程序转发请求时,框架不会从字面上将框架请求对象从发送驱动程序传输到接收驱动程序。 相反,框架会在接收请求的驱动程序中创建一个新的请求对象。 只有请求的基础 I/O 请求数据包 (IRP) 才会从一个驱动程序传输到另一个驱动程序。

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值