2. UMDF驱动对象模型:核心接口详解

UMDF(User-Mode Driver Framework)驱动对象模型是驱动开发者必须掌握的基础。该模型通过一组COM风格的接口,将WDM(Windows Driver Model)中复杂的IRP处理、设备栈遍历、I/O请求路由等操作封装为易于使用的对象。本章将深入剖析四个核心接口:IWDFDriver、IWDFDevice、IWDFIoQueue 和 IWDFIoRequest,并揭示它们之间的协作关系。

核心认知:UMDF对象模型采用“分层组合”设计。每个驱动实例对应一个IWDFDriver,每个设备对象对应一个IWDFDevice,每个I/O通道对应一个IWDFIoQueue,每个请求对应一个IWDFIoRequest。理解这四者的生命周期与交互,是排查IRP处理故障的基石。

2.1 对象模型全景图

下图展示了UMDF驱动中四个核心接口的层次关系与数据流路径。驱动入口通过IWDFDriver创建设备,设备通过IWDFDevice创建队列,队列接收并分发IWDFIoRequest,请求最终被驱动处理并完成。

UMDF 核心接口对象模型与数据流 IWDFDriver 驱动工厂对象 IWDFDevice 设备实例对象 IWDFIoQueue I/O 请求队列 IWDFIoRequest 单个 I/O 请求 CreateDevice() 创建设备 每个驱动只有一个实例 CreateIoQueue() 创建队列 支持并行/顺序/手动队列 通过回调获取请求 Complete() 完成请求

2.2 IWDFDriver:驱动工厂与入口

IWDFDriver 是UMDF驱动对象的根。它在驱动加载时由框架创建,代表整个驱动实例。驱动入口函数 DllGetClassObject 或 IDriverEntry::OnDeviceAdd 中,驱动通过此接口完成初始化。

方法 功能描述 典型调用时机
CreateDevice 创建并初始化一个新的设备对象(IWDFDevice) OnDeviceAdd 回调中
CreateWdfObject 创建框架管理的自定义上下文对象 设备初始化或请求处理时
RetrieveVersionString 获取当前UMDF框架版本信息 调试或版本兼容性检查
IsVersionAvailable 检查指定UMDF版本是否可用 驱动加载时的版本验证
故障排查要点:如果 CreateDevice 返回失败,通常意味着设备栈创建失败。常见原因包括:设备接口GUID注册冲突、父设备对象未就绪、或资源不足。此时应检查 WUDFHost.exe 进程的日志(%SystemRoot%\System32\LogFiles\Wudf\)。

2.3 IWDFDevice:设备实例与栈管理

IWDFDevice 代表一个具体的设备实例。它是驱动与设备栈交互的核心枢纽,负责创建I/O队列、管理电源策略、处理PnP事件以及暴露设备属性。

每个设备对象都关联一个设备栈(Device Stack),UMDF设备栈位于内核模式设备栈之上,通过反射器(Reflector)进行通信。

IWDFDevice 与设备栈关系 UMDF 驱动 (IWDFDevice) UMDF 反射器 (Reflector) 内核模式设备栈 CreateIoQueue() 创建队列 GetDefaultIoQueue() 获取默认队列 反射器桥接用户/内核模式 IRP 通过反射器转发到 UMDF 内核栈处理物理IRP 完成时回调 UMDF 驱动

IWDFDevice 关键方法

  • CreateIoQueue:创建指定类型的I/O队列(顺序、并行、手动)。
  • GetDefaultIoQueue:获取驱动默认的I/O队列,通常用于简单场景。
  • RetrieveDevicePropertyStore:访问设备的属性存储区,用于读取硬件配置。
  • CommitPnpState:提交PnP状态变更,通知框架设备已就绪。
常见陷阱:在 OnDeviceAdd 回调中,必须调用 CommitPnpState 才能完成设备初始化。如果遗漏此调用,设备将始终处于“未就绪”状态,导致后续I/O请求无法下发。调试时可通过 !wudfext.umdevstacks 命令检查设备状态。

2.4 IWDFIoQueue:I/O请求的调度中枢

IWDFIoQueue 是UMDF驱动中管理I/O请求的核心对象。它接收来自框架的IRP,并根据配置的调度类型将请求分发给驱动回调。UMDF支持三种队列类型:

队列类型 调度行为 适用场景 并发度
WdfIoQueueDispatchSequential 一次只处理一个请求,完成后才分发下一个 需要严格串行化的操作(如寄存器访问) 1
WdfIoQueueDispatchParallel 所有请求同时分发,驱动需自行同步 高性能、无依赖的I/O操作 N(无限制)
WdfIoQueueDispatchManual 框架不自动分发,驱动主动调用 RetrieveNextRequest 获取 自定义调度策略、请求合并、优先级控制 由驱动控制
IWDFIoQueue 三种调度模式对比 顺序队列 Req1 → Req2 → Req3 一次一个,排队处理 并行队列 Req1, Req2, Req3 同时 同时分发,需加锁保护 手动队列 驱动主动 Retrieve 完全由驱动控制调度 选择建议:无状态设备用并行,有状态设备用顺序, 需要精细控制时用手动队列

队列回调接口

驱动通过实现 IQueueCallbackRead、IQueueCallbackWrite、IQueueCallbackDeviceIoControl 等接口来接收队列分发的请求。例如:

// 实现读请求回调
class MyIoQueue : public IQueueCallbackRead {
public:
    void OnRead(
        IWDFIoQueue* pQueue,
        IWDFIoRequest* pRequest,
        SIZE_T BytesToRead
    ) override {
        // 处理读请求
        // ...
        pRequest->CompleteWithInformation(S_OK, BytesRead);
    }
};

2.5 IWDFIoRequest:请求的完整生命周期

IWDFIoRequest 代表一个从应用程序或内核下发的I/O请求。它封装了IRP的所有关键信息,包括操作码、输入输出缓冲区、完成状态等。理解其生命周期是调试IRP处理故障的核心。

请求生命周期状态机

IWDFIoRequest 生命周期状态机 已创建 已分发 处理中 已完成 关键规则:每个请求必须且只能完成一次。重复 Complete() 会导致系统崩溃。 如果请求被转发到其他队列或驱动,必须调用 Forward() 并设置完成回调。

IWDFIoRequest 核心方法

方法 用途 注意事项
Complete 完成请求并返回状态 只能调用一次,调用后请求对象失效
CompleteWithInformation 完成请求并附带传输字节数 用于读写操作,传递实际传输大小
Forward 将请求转发到另一个队列或驱动 必须设置完成回调以接收最终状态
GetDeviceIoControlParameters 获取IOCTL码、输入输出缓冲区大小 仅对DeviceIoControl请求有效
GetInputMemory / GetOutputMemory 获取请求的输入/输出缓冲区 返回 IWDFMemory 对象,需检查有效性
Cancel 取消请求 需配合队列的取消回调使用
调试技巧:使用 !wudfext.umirp 命令可以查看当前未完成的UMDF请求。如果发现请求长时间处于“处理中”状态,通常意味着驱动忘记调用 Complete 或 Forward。此时应检查回调函数中是否存在提前返回的分支路径。

2.6 四接口协作流程示例

以下代码片段展示了从驱动入口到请求完成的完整协作流程:

// 1. OnDeviceAdd 中创建设备和队列
HRESULT MyDriver::OnDeviceAdd(
    IWDFDriver* pDriver,
    IWDFDeviceInitialize* pDeviceInit
) {
    IWDFDevice* pDevice = nullptr;
    HRESULT hr = pDriver->CreateDevice(pDeviceInit, nullptr, &pDevice);
    if (FAILED(hr)) return hr;

    // 2. 创建并行队列
    IWDFIoQueue* pQueue = nullptr;
    hr = pDevice->CreateIoQueue(
        nullptr,                     // 默认回调接口
        WdfIoQueueDispatchParallel,  // 并行调度
        true,                        // 默认队列
        false,                       // 不自动转发
        &pQueue
    );
    if (FAILED(hr)) return hr;

    // 3. 设置读请求回调
    pQueue->SetReadCallback(new MyReadCallback());
    pDevice->CommitPnpState();
    return S_OK;
}

// 4. 读请求回调中处理请求
void MyReadCallback::OnRead(
    IWDFIoQueue* pQueue,
    IWDFIoRequest* pRequest,
    SIZE_T BytesToRead
) {
    IWDFMemory* pOutputMemory = nullptr;
    pRequest->GetOutputMemory(&pOutputMemory);
    if (pOutputMemory) {
        // 模拟读取数据
        pOutputMemory->CopyFromBuffer(0, "Hello", 5);
        pRequest->CompleteWithInformation(S_OK, 5);
    } else {
        pRequest->Complete(E_FAIL);
    }
}
内存泄漏风险:每次调用 GetInputMemory 或 GetOutputMemory 获取的 IWDFMemory 对象,在不再使用时必须调用 Release 释放。否则会导致UMDF宿主进程(WUDFHost.exe)内存持续增长,最终触发资源耗尽故障。

2.7 常见故障模式与排查对照

故障现象 可能原因 涉及接口 排查命令/方法
设备无法启动 CreateDevice 失败或未调用 CommitPnpState IWDFDriver, IWDFDevice !wudfext.umdevstacks
I/O请求无响应 队列未设置回调或回调未注册 IWDFIoQueue !wudfext.umirp
系统蓝屏(0x9F) 请求未完成,导致IRP超时 IWDFIoRequest !analyze -v, !wudfext.umirp
内存泄漏 IWDFMemory 未 Release IWDFIoRequest !wudfext.wudfheapdump
请求重复完成 Complete 被调用多次 IWDFIoRequest 启用UMDF验证器(Verifier)