WDF驱动开发-I/O请求发送

初始化常规 I/O 目标

当驱动程序调用 WdfDeviceCreate 时,框架为设备初始化驱动程序的本地 I/O 目标。 若要检索设备的本地 I/O 目标的句柄,驱动程序会调用 WdfDeviceGetIoTarget。

大多数驱动程序仅将请求发送到其本地 I/O 目标。

若要初始化设备的远程 I/O 目标,驱动程序必须:

  • 调用 WdfIoTargetCreate 来创建 I/O 目标对象。
  • 调用 WdfIoTargetOpen 打开 I/O 目标,以便驱动程序可以向其发送请求。

当驱动程序调用 WdfIoTargetOpen 时,它通常通过提供表示 对象名称的 Unicode 字符串来标识远程 I/O 目标。 此名称可以标识设备、文件或设备接口。 框架将 I/O 请求发送到支持对象名称的驱动程序堆栈顶部。

极少数情况下,驱动程序可能会通过提供指向 Windows 驱动程序模型的指针来标识远程 I/O 目标, (WDM) DEVICE_OBJECT 结构。 此指针标识调用驱动程序堆栈中的另一个驱动程序。 基于框架的驱动程序很少使用此方法,因为它们很少有权访问其他驱动程序 的DEVICE_OBJECT 结构。

以下示例演示 Ndisedge 示例驱动程序如何使用上述技术创建和打开远程 I/O 目标:

status = WdfIoTargetCreate(Adapter->WdfDevice,
                        WDF_NO_OBJECT_ATTRIBUTES,
                        &Adapter->IoTarget);
    if (!NT_SUCCESS(status)) {
        DEBUGP(MP_ERROR, ("WdfIoTargetCreate failed 0x%x\n",
               status));
        return status;
    }

    WDF_IO_TARGET_OPEN_PARAMS_INIT_CREATE_BY_NAME(&openParams,
                                &fileName,
                                STANDARD_RIGHTS_ALL
                                );

    status = WdfIoTargetOpen(Adapter->IoTarget,
                        &openParams);
    if (!NT_SUCCESS(status)) {
        DEBUGP(MP_ERROR, ("WdfIoTargetOpen failed 0x%x\n", status));
        return status;
    }
将 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 请求发送到 I/O 目标队列,框架会将请求传送到相应的驱动程序;
  • 停止:I/O 目标的入口处于打开状态,但门外已关闭。 框架停止向相应的驱动程序传递请求。 若要将 I/O 请求发送到 I/O 目标,驱动程序必须在每个请求的WDF_REQUEST_SEND_OPTIONS结构中设置WDF_REQUEST_SEND_OPTION_IGNORE_TARGET_STATE或WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET;
  • 清除:I/O 目标对象的两个门都已关闭。 驱动程序无法将 I/O 请求发送到 I/O 目标,除非设置 WDF_REQUEST_SEND_OPTION_IGNORE_TARGET_STATE 或 WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET。 此外,框架还会取消 I/O 目标对象内部队列中未处理的请求。 此状态从 KMDF 版本 1.11 开始可用;
  • 对于 Query-Remove 关闭:远程 I/O 目标暂时关闭,因为其设备可能很快就会被删除;
  • 关闭:I/O 目标已关闭,无法启动或停止;
  • 删除:I/O 目标的设备已删除;

WDF_IO_TARGET_STATE枚举定义表示这些状态的值。 驱动程序可以调用 WdfIoTargetGetState 来获取 I/O 目标的状态。

本地 I/O 目标状态

框架会自动打开并启动本地 I/O 目标。

如有必要,驱动程序可以调用 WdfIoTargetStop 以暂时停止本地 I/O 目标,并调用 WdfIoTargetStart 来重启它。 例如,如果驱动程序检测到临时错误条件,则驱动程序可能会停止本地 I/O 目标,如果错误条件得到纠正,则重新启动该 I/O 目标。

在 KMDF 版本 1.11 及更高版本中,驱动程序可以调用 WdfIoTargetPurge ,以暂时阻止 I/O 请求发送到本地 I/O 目标,并取消目标队列中未处理的请求。 例如,作为文件句柄清理的一部分,驱动程序可能会清除本地 I/O 目标,以确保取消发送到驱动程序的所有请求。

如果删除了本地 I/O 目标的设备,框架会自动停止并关闭该 I/O 目标,并 取消 目标队列中的所有 I/O 请求。 框架通过调用设备对象事件回调函数通知驱动程序设备不再可用。

远程 I/O 目标状态

驱动程序必须调用 WdfIoTargetOpen 才能打开远程 I/O 目标。 当驱动程序打开远程 I/O 目标时,框架会自动启动 I/O 目标。

如有必要,驱动程序可以调用 WdfIoTargetStop 以暂时停止远程 I/O 目标,并调用 WdfIoTargetStart 来重启它。

在 KMDF 版本 1.11 及更高版本中,驱动程序可以调用 WdfIoTargetPurge ,以暂时阻止将 I/O 请求发送到远程 I/O 目标,并取消目标队列中未处理的请求。

如果删除远程 I/O 目标的设备,框架会自动停止并关闭 I/O 目标,并取消目标队列中的所有 I/O 请求,除非驱动程序注册以下事件回调函数:

  • EvtIoTargetQueryRemove通知驱动程序远程 I/O 目标的设备可能已删除。 如果希望驱动程序允许删除设备,驱动程序必须调用 WdfIoTargetCloseForQueryRemove ;
  • EvtIoTargetRemoveComplete通知驱动程序远程 I/O 目标的设备已删除。 此回调函数必须调用 WdfIoTargetClose;
  • EvtIoTargetRemoveCanceled通知驱动程序删除远程 I/O 目标的设备的尝试已取消。 此回调函数必须调用 WdfIoTargetOpen,驱动程序通常调用 WDF_IO_TARGET_OPEN_PARAMS_INIT_REOPEN 来初始化其WDF_IO_TARGET_OPEN_PARAMS_INIT函数;

如果驱动程序已完成使用远程 I/O 目标,并且不再使用该目标,并且目标没有仍在挂起的子请求对象,则驱动程序可以在不首先调用 WdfIoTargetClose 的情况下调用 WdfObjectDelete。 如果目标具有任何仍在挂起的子请求对象,则驱动程序必须先调用 WdfIoTargetClose ,然后才能安全地调用 WdfObjectDelete。

获取有关常规 I/O 目标的信息

若要获取有关 I/O 目标的信息,驱动程序可以调用 I/O 目标对象定义的以下方法:

  • WdfIoTargetGetDevice:返回与本地或远程 I/O 目标关联的框架设备对象;
  • WdfIoTargetQueryTargetProperty 或 WdfIoTargetAllocAndQueryTargetProperty:检索与本地或远程 I/O 目标设备关联的设备属性;
  • WdfIoTargetGetState:返回本地或远程 I/O 目标的状态信息;
  • WdfIoTargetWdmGetTargetDeviceObject:返回指向与本地或远程 I/O 目标关联的 Windows 驱动程序模型 (WDM) 设备对象的指针;
  • WdfIoTargetWdmGetTargetPhysicalDevice:返回指向 WDM 物理设备对象的指针, 表示远程 I/O 目标设备的 PDO;
  • WdfIoTargetWdmGetTargetFileObject:返回指向与远程 I/O 目标关联的 WDM 文件对象的指针;
  • WdfIoTargetWdmGetTargetFileHandle:返回与远程 I/O 目标关联的文件的句柄;
同步发送 I/O 请求

下表列出了驱动程序可以调用的 I/O 目标对象方法,以同步方式将 I/O 请求发送到 I/O 目标:

也可以通过调用 WdfRequestSend 以同步方式发送请求,但必须首先按照 异步发送 I/O 请求中所述的规则设置请求的格式。

与 异步发送 I/O 请求相比,以同步方式向 I/O 目标发送 I/O 请求更易于编程。 但是,应使用以下准则来帮助确定同步 I/O 是否适用于驱动程序:

  • 如果驱动程序不发送许多 I/O 请求,并且系统或设备性能没有因为驱动程序等待每个 I/O 请求完成而降低,则可以使用同步 I/O;
  • 如果驱动程序必须在短时间内处理许多 I/O 请求,则可能无法允许驱动程序在发送下一个请求之前等待每个请求完成。 否则,驱动程序可能会丢失数据或降低其设备的性能 ,并且可能整个系统。 在这种情况下,异步 I/O 可能是更好的选择;
  • 同步 I/O 对于处理必须启动和完成且没有其他并发活动的操作很有用。 此类操作可能包括重置 USB 管道或读取设备寄存器;
  • 大多数情况下,驱动程序应在调用同步发送 I/O 请求的对象方法时指定超时值。 如果驱动程序未指定超时值,并且设备或较低级别的驱动程序无法响应,驱动程序可能会停止。 因此,用户可能会遇到无响应的应用程序。 此外,如果你的驱动程序未释放工作项,其他驱动程序可能无法获取系统资源(例如 工作项);
  • 如果堆栈中高于和低于 的驱动程序需要操作同步进行,则驱动程序应使用同步 I/O。 因此,应了解驱动程序堆栈中可能存在的其他驱动程序的要求;

以下示例演示如何 (IOCTL) 请求发送同步 I/O 控件:

NTSTATUS                status;
    WDF_MEMORY_DESCRIPTOR   inputDesc, outputDesc;
    PWDF_MEMORY_DESCRIPTOR  pInputDesc = NULL, pOutputDesc = NULL;
    ULONG_PTR               bytesReturned;

    UNREFERENCED_PARAMETER(FileObject);

    if (InputBuffer) {
        WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDesc,
                                    InputBuffer,
                                    InputBufferLength);
        pInputDesc = &inputDesc;
    }

    if (OutputBuffer) {
        WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&outputDesc,
                                    OutputBuffer,
                                    OutputBufferLength);
        pOutputDesc = &outputDesc;
    }

    status = WdfIoTargetSendIoctlSynchronously(
                        IoTarget,
                        WDF_NO_HANDLE, // Request
                        IoctlControlCode,
                        pInputDesc,
                        pOutputDesc,
                        NULL, // PWDF_REQUEST_SEND_OPTIONS
                        &bytesReturned);
    if (!NT_SUCCESS(status)) {
         DEBUGP(MP_ERROR,
        ("WdfIoTargetSendIoctlSynchronously failed 0x%x\n",
          status));
    }

    *BytesReadOrWritten = (ULONG)bytesReturned;
异步发送 I/O 请求

必须先设置请求的格式,然后才能将 I/O 请求异步发送到 I/O 目标。 下表列出了驱动程序可以调用以格式化 I/O 请求的 I/O 目标对象方法:

若要异步发送 I/O 请求,驱动程序必须:

  • 设置请求的格式:使用上表中列出的方法之一设置请求的格式;
  • 注册 CompletionRoutine 回调函数:如果以异步方式发送请求,通常希望框架在另一个驱动程序完成每个请求时通知驱动程序。 驱动程序应定义 CompletionRoutine 回调函数,并通过调用 WdfRequestSetCompletionRoutine 对其进行注册;
  • 发送请求:在驱动程序格式化请求并注册 CompletionRoutine 回调函数后,驱动程序必须调用 WdfRequestSend。 使用此方法可以同步或异步发送请求,具体取决于 RequestOptions 参数中设置的标志;

调用 WdfRequestSend 以发送 I/O 请求的驱动程序稍后可以尝试取消该请求。

某些驱动程序可能会通过为每个请求多次调用 WdfRequestSend 来向多个设备发送单个 I/O 请求,从而发送到多个 I/O 目标。 这些驱动程序必须在每次调用 WdfRequestSend 之前调用 WdfRequestChangeTarget,然后再调用第一个驱动程序,以验证请求是否可以发送到下一个 I/O 目标。

相关推荐
FU.l2 小时前
Linux-笔记 全志平台休眠功能初探
linux·驱动开发·笔记
小袁搬码2 小时前
wget之Win11中安装及使用
windows·shell
我代码抄都抄不明白4 小时前
【无标题】蓝屏事件 139
c语言·windows·microsoft·visual studio
系统之家装机大师8 小时前
老机福音!最精简最快的Win7系统:免费下载!
windows·微软·电脑
Arran阿蓝10 小时前
8.javaSE基础进阶_泛型generics(无解通配符?+上下界统配符super&extends)
java·jvm·windows·intellij-idea
danielli10 小时前
C# Modbus设备信息加载的实现方式(1)
java·windows·c#
mrathena10 小时前
Windows 11 安装 安卓子系统 (WSA)
android·windows
qq_4543847111 小时前
JDK 为什么需要配置环境变量
java·开发语言·windows
m0_6442226111 小时前
HarmonyOS开发实战:UDP通讯示例规范
网络·驱动开发·嵌入式硬件·udp·harmonyos·鸿蒙·harmonyos next
人才程序员12 小时前
CMake日志与变量操作
linux·运维·服务器·c++·windows·microsoft·c