保证向前推进 I/O 操作

某些驱动程序(例如系统分页设备的存储驱动程序)必须至少执行一些受支持的 I/O 操作,而不会失败,以避免丢失关键系统数据。 驱动程序故障的一个潜在原因是内存不足的情况。 如果框架或驱动程序无法分配足够的内存来处理 I/O 请求,则其中一个或另一个可能需要通过错误状态值 完成 I/O 请求来使 I/O 请求失败。

在版本 1.9 之前的 KMDF 版本中,如果框架无法为 I/O 请求数据包分配框架请求对象, (I/O 管理器已发送到驱动程序的 I/O) ,框架始终会失败 I/O 请求。 为了使驱动程序能够在内存不足的情况下处理 I/O 请求,框架版本 1.9 及更高版本为 I/O 队列提供了 有保证的向前进度 功能。

此功能使框架和驱动程序能够分别为请求对象集和与请求相关的驱动程序上下文缓冲区预先分配内存。 仅当系统内存量较低时,框架和驱动程序才使用此预分配的内存。

保证向前进度的功能

通过使用框架保证的 I/O 队列向前进度,驱动程序可以:

  • 要求框架预先分配一组请求对象,以在内存不足的情况下用于特定的 I/O 队列。

  • 提供一个回调函数,用于预分配特定于请求的资源,驱动程序在内存不足的情况下从框架接收预分配的请求对象时可以使用这些资源。

  • 提供另一个回调函数,用于在 检测到内存不足的情况时为 I/O 请求分配特定于驱动程序的资源。 如果此回调函数的分配由于内存不足而失败,它可以指示框架是否应使用其预分配的请求对象之一。

  • 指定哪些 I/O 请求需要使用预分配的请求对象。 选项包括为所有 IRP 使用预分配的对象、仅在分页 I/O 操作正在进行时使用它们,或让其他驱动程序回调函数检查每个 IRP 以确定是否使用预分配的对象。

如果驱动程序对其一个或多个 I/O 队列实现了有保证的向前进度,则驱动程序将能够更好地在内存不足的情况下成功 处理 I/O 请求 。 你可以为设备的默认 I/O 队列以及驱动程序通过调用 WdfDeviceConfigureRequestDispatching 配置的任何 I/O 队列实现有保证的向前进度。

只有当驱动程序和驱动程序的 I/O 目标 都实现有保证的前进进度时,框架的有保证向前进度功能才适用于驱动程序。 换句话说,如果驱动程序为设备实现有保证的向前进度,则设备驱动程序堆栈中的所有较低级别驱动程序也必须实现有保证的向前进度。

为 I/O 队列启用有保证的向前进度

若要为 I/O 队列启用有保证的向前进度,驱动程序会初始化 WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 结构,然后调用 WdfIoQueueAssignForwardProgressPolicy 方法。 如果驱动程序调用 WdfDeviceConfigureRequestDispatching 来配置 I/O 队列,则必须在调用 WdfIoQueueAssignForwardProgressPolicy 之前执行此操作。

当驱动程序调用 WdfIoQueueAssignForwardProgressPolicy 时,它可以指定以下三个事件回调函数,所有这些函数都是可选的:

EvtIoAllocateResourcesForReservedRequest
驱动程序的 EvtIoAllocateResourcesForReservedRequest 回调函数为框架在内存不足的情况下保留的请求对象分配和存储特定于请求的资源。

框架每次创建保留请求对象时都会调用此回调函数。 驱动程序应为一个 I/O 请求分配特定于请求的资源,通常使用保留的请求对象的 上下文空间

EvtIoAllocateRequestResources
驱动程序的 EvtIoAllocateRequestResources 回调函数分配特定于请求的资源以供立即使用。 在框架收到 IRP 并为 IRP 创建请求对象后,将立即调用它。

如果回调函数分配资源的尝试失败,回调函数将返回错误状态值。 然后,框架删除新创建的请求对象,并使用其保留的请求对象之一。 反过来,驱动程序 的请求处理程序 使用其 EvtIoAllocateRequestResources 回调函数之前分配的特定于请求的资源。

EvtIoWdmIrpForForwardProgress
驱动程序的 EvtIoWdmIrpForForwardProgress 回调函数检查 IRP,并告知框架是使用 IRP 的保留请求对象,还是通过错误状态值完成 I/O 请求来使该请求失败。

仅当框架无法创建新的请求对象,并且你通过在驱动程序的 WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY结构中 设置标志来指示 (,) 希望驱动程序在内存不足的情况下检查 IRP 时,框架才会调用此回调函数。 换句话说,驱动程序可以评估每个 IRP,并确定它是否是即使在内存不足的情况下也必须处理的 IRP。

当驱动程序调用 WdfIoQueueAssignForwardProgressPolicy 时,它还指定你希望框架针对内存不足的情况预先分配的保留请求对象数。 可以选择适合你的设备和驱动程序的请求对象数。 为防止性能降低,驱动程序通常应指定一个数字,该数字近似于驱动程序和设备可以并行处理的 I/O 请求数。

但是,如果驱动程序调用 WdfIoQueueAssignForwardProgressPolicy 及其 EvtIoAllocateResourcesForReservedRequest 回调函数预先分配了过多的保留请求对象或过多特定于请求的资源内存,则驱动程序实际上可能会导致尝试处理的内存不足的情况。 应测试驱动程序和设备的性能,并包括低内存模拟,以确定要选择的最佳数字。

WdfIoQueueAssignForwardProgressPolicy 返回之前,框架会创建并保留驱动程序指定的请求对象数。 每次保留请求对象时,框架都会立即调用驱动程序的 EvtIoAllocateResourcesForReservedRequest 回调函数,以便在框架实际使用保留请求对象的情况下,驱动程序可以分配和保存特定于请求的资源。

当某个驱动程序 的请求处理程序 从 I/O 队列接收 I/O 请求时,它可以调用 WdfRequestIsReserved 方法,以确定请求对象是否是框架针对内存不足情况预先分配的请求对象。 如果此方法返回 TRUE,则驱动程序应使用其 EvtIoAllocateResourcesForReservedRequest 回调函数保留的资源。

如果框架使用其保留请求对象之一,则会在驱动程序完成请求后将对象返回到其保留对象集。 框架保存请求对象以及驱动程序通过调用 WdfDeviceInitSetRequestAttributesWdfObjectAllocateContext 创建的任何上下文空间,以便在出现另一个内存不足的情况时重复使用。

框架和驱动程序支持如何保证向前推进

以下是驱动程序和框架为支持 I/O 队列的有保证向前进度而执行的步骤:

  1. 驱动程序调用 WdfIoQueueAssignForwardProgressPolicy

    作为响应,框架分配并存储驱动程序指定的请求对象数。 如果驱动程序以前调用 了 WdfDeviceInitSetRequestAttributes,则每个分配包括 WdfDeviceInitSetRequestAttributes 指定的上下文空间。

    此外,如果驱动程序提供了 EvtIoAllocateResourcesForReservedRequest 回调函数,则框架会在每次分配和存储请求对象时调用回调函数。

  2. 框架接收 I/O 请求数据包 (IRP) I/O 管理器发送到驱动程序。

    框架尝试为 IRP 分配请求对象。 如果驱动程序为请求类型创建的 I/O 队列支持保证向前进度,则下一步取决于分配是成功还是失败:

    • 请求对象分配成功。

      如果驱动程序提供了 EvtIoAllocateRequestResources 回调函数,框架将调用它。 如果回调函数返回STATUS_SUCCESS,框架会将请求添加到 I/O 队列。 如果回调函数返回错误状态值,框架将删除它刚刚创建的请求对象,并使用其预分配的请求对象之一。 当驱动程序的请求处理程序收到请求对象时,它会确定请求对象是否已预先分配,因此是否应使用驱动程序的预分配资源。

      如果驱动程序 提供 EvtIoAllocateRequestResources 回调函数,框架会将请求添加到 I/O 队列,就像驱动程序未启用有保证的向前进度一样。

    • 请求对象分配失败。

      框架接下来执行的操作取决于驱动程序为 WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 结构的 ForwardProgressReservedPolicy 成员提供的值。 此成员通知框架何时使用保留请求:始终,仅当 I/O 请求是分页 I/O 操作时,或仅当 EvtIoWdmIrpForwardProgress 回调函数指示应使用保留请求时。

    在所有情况下,驱动程序的请求处理程序都可以调用 WdfRequestIsReserved 来确定框架是否使用了保留的请求对象。 如果是这样,驱动程序应使用其 EvtIoAllocateResourcesForReservedRequest 回调函数分配的请求资源。

保证向前进度方案

你正在为可能包含系统分页文件的存储设备编写驱动程序。 从分页文件读取操作和写入操作成功非常重要。

你决定为读取和写入操作创建单独的 I/O 队列,并为这两个 I/O 队列启用有保证的向前进度。 你决定为所有其他请求类型创建第三个 I/O 队列,但不启用有保证的向前进度。

驱动程序堆栈和设备能够并行处理四个写入操作,因此,在调用 WdfIoQueueAssignForwardForwardProgressPolicy 之前,请将 WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY 结构的 TotalForwardProgressRequests 成员设置为 4。

你决定仅当驱动程序的设备是分页设备时才保证向前进度很重要,因此驱动程序将WDF_IO_QUEUE_FORWARD_PROGRESS_POLICY结构的 ForwardProgressReservedPolicy 成员设置为 WdfIoForwardProgressReservedPolicyPagingIO

由于驱动程序需要每个读取请求和每个写入请求的框架内存对象,因此你决定驱动程序应预先分配一些内存对象,以便在内存不足的情况下用于调用 WdfIoTargetFormatRequestForReadWdfIoTargetFormatRequestForWrite

因此,驱动程序为读取队列提供 EvtIoAllocateResourcesForReservedRequest 回调函数,为写入队列提供另一个回调函数。 每次框架调用其中一个回调函数时,回调函数都会调用 WdfMemoryCreate 并保存返回的对象句柄,以应对内存不足的情况。 因为回调函数接收预分配的请求对象的句柄,所以它可以将内存对象父级给请求对象。 (DMA 设备的驱动程序也可能预先分配 框架 DMA 对象。)

读取和写入队列 的请求处理程序 必须确定每个接收的请求对象是否为框架为内存不足的情况保留的对象。 请求处理程序可以调用 WdfRequestIsReserved,也可以将请求对象句柄与 EvtIoAllocateResourcesForReservedRequest 回调函数之前收到的句柄进行比较。

该驱动程序还为读取队列提供 EvtIoAllocateRequestResources 回调函数,并为写入队列提供另一个回调函数。 框架在收到来自 I/O 管理器的读取或写入请求并成功创建请求对象时调用其中一个回调函数。 其中每个回调函数调用 WdfMemoryCreate 为请求分配内存对象。 如果分配失败,回调函数将返回错误状态值,以通知框架刚刚出现内存不足的情况。 框架检测错误返回值,删除刚刚创建的请求对象,并使用其预分配的对象之一。

此驱动程序不提供 EvtIoWdmIrpForForwardProgress 回调函数,因为它不需要在框架将其添加到 I/O 队列之前检查单个读取或写入 IRP。

请记住,当驱动程序为设备实现有保证的向前进度时,设备驱动程序堆栈中的所有较低级别驱动程序也必须实现有保证的向前进度。