初始化常规 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 目标。