4. UMDF IRP处理流程:IRP到达驱动栈、框架拦截、WDFREQUEST创建、回调分发

UMDF驱动模型的核心优势在于其框架拦截(Framework Interception)机制。当IRP(I/O请求包)从应用层或内核层下发时,并不会直接到达驱动开发者编写的回调函数,而是先被UMDF框架层截获,经过一系列标准化处理后,转换为框架内部的WDFREQUEST对象,再通过预定义的回调分发机制通知驱动。理解这一流程是排查设备栈遍历故障的基础。

核心要点: UMDF驱动不直接处理IRP,而是处理WDFREQUEST。框架负责IRP到WDFREQUEST的转换、队列管理和同步控制。

4.1 IRP到达驱动栈的完整路径

一个典型的IRP从创建到被UMDF驱动处理,需要经过以下层次:

  1. 应用层发起I/O请求(如ReadFile、WriteFile、DeviceIoControl)。
  2. 内核I/O管理器创建IRP,并将其发送到设备对象栈。
  3. 内核模式驱动栈(如ACPI、PCI驱动)处理硬件相关操作,然后将IRP转发至UMDF的反射器(Reflector)。
  4. 反射器(WudfRd.sys)将内核IRP封装,通过ALPC(高级本地过程调用)传递给用户模式的UMDF驱动宿主进程(WudfHost.exe)。
  5. UMDF框架在宿主进程中接收该请求,创建WDFREQUEST对象,并开始回调分发。
IRP到达UMDF驱动栈的完整路径 应用层 (App.exe) 内核I/O管理器 内核驱动栈 (ACPI/PCI) 反射器 (WudfRd.sys) ALPC 跨进程 WudfHost.exe (框架)

4.2 框架拦截与WDFREQUEST创建

当IRP通过ALPC到达UMDF框架后,框架执行以下关键步骤:

步骤 框架动作 说明
1 接收ALPC消息 框架从ALPC端口读取内核传递过来的IRP封装数据。
2 创建WDFREQUEST对象 框架在堆上分配WDFREQUEST结构体,包含IRP的MajorFunction、参数、I/O缓冲区信息。
3 关联设备对象 将WDFREQUEST与目标WDFDEVICE对象绑定,确定请求的目标设备栈。
4 设置请求上下文 框架自动填充请求类型、超时、优先级等元数据。
5 入队或直接分发 根据队列配置,将WDFREQUEST放入I/O队列或立即调用回调。
调试提示: 在WinDbg中使用 !wdfrequest 扩展命令可以查看当前WDFREQUEST对象的详细信息,包括其关联的IRP MajorFunction和内部状态。

4.3 回调分发机制

WDFREQUEST创建完成后,框架根据请求类型和驱动注册的回调函数进行分发。UMDF驱动通过事件回调函数(Event Callback)来处理请求,而不是直接处理IRP。

4.3.1 主要回调函数类型

回调函数 对应的IRP主功能码 典型用途
IQueueCallbackRead IRP_MJ_READ 处理从设备读取数据的请求
IQueueCallbackWrite IRP_MJ_WRITE 处理向设备写入数据的请求
IQueueCallbackDeviceIoControl IRP_MJ_DEVICE_CONTROL 处理自定义控制码(IOCTL)
IQueueCallbackDefaultIoHandler 其他未匹配的IRP 处理框架未明确拦截的请求

4.3.2 分发流程

  1. 框架匹配队列: 根据WDFREQUEST的MajorFunction,框架查找驱动注册的I/O队列(WDFQUEUE)。
  2. 调用回调: 框架调用队列对应的回调函数,传入WDFREQUEST和WDFQUEUE句柄。
  3. 驱动处理: 驱动在回调中执行实际逻辑(如访问硬件、操作缓冲区)。
  4. 请求完成: 驱动调用 WdfRequestComplete 通知框架请求已完成,框架将结果返回给内核。
UMDF回调分发机制 WDFREQUEST 对象 框架分发器 IQueueCallbackRead IQueueCallbackWrite IQueueCallbackDeviceIoControl WdfRequestComplete

4.4 关键代码示例:注册回调与处理请求

以下代码展示了在UMDF驱动中如何配置I/O队列并注册回调函数:

// 创建默认I/O队列并注册回调
HRESULT CMyDevice::CreateQueueAndCallbacks()
{
    HRESULT hr = S_OK;
    WDF_IO_QUEUE_CONFIG queueConfig;

    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
        &queueConfig,
        WdfIoQueueDispatchParallel  // 并行分发模式
    );

    // 注册读、写、IOCTL回调
    queueConfig.EvtIoRead = OnRead;
    queueConfig.EvtIoWrite = OnWrite;
    queueConfig.EvtIoDeviceControl = OnDeviceIoControl;

    // 创建队列
    hr = m_FxDevice->CreateIoQueue(
        &queueConfig,
        WDF_NO_OBJECT_ATTRIBUTES,
        &m_DefaultQueue
    );

    return hr;
}

// 读请求处理回调
void CMyDevice::OnRead(
    IWDFIoQueue* pQueue,
    IWDFIoRequest* pRequest
)
{
    HRESULT hr = S_OK;
    IWDFMemory* pInputMemory = nullptr;
    SIZE_T bytesToRead = 0;

    // 获取请求的输入缓冲区
    pRequest->GetInputMemory(&pInputMemory);
    bytesToRead = pRequest->GetInputBufferSize();

    // 执行设备读取操作(伪代码)
    hr = ReadFromHardware(pInputMemory, bytesToRead);

    // 完成请求
    pRequest->Complete(hr);
}
常见故障点: 如果驱动未注册 EvtIoDefault 回调,而框架收到无法匹配的IRP(如IRP_MJ_CREATE),框架会默认完成该请求并返回失败状态。这可能导致应用层CreateFile调用失败。排查时应检查所有可能的IRP主功能码是否都有对应的回调处理。

4.5 流程总结与故障排查要点

阶段 关键对象/组件 排查要点
IRP到达 IRP、反射器、ALPC 检查反射器是否加载(!wdfldr),ALPC端口是否正常
框架拦截 UMDF框架、WDFREQUEST 使用 !wdfrequest 查看请求状态,确认是否成功创建
回调分发 WDFQUEUE、回调函数 确认队列配置正确,回调函数指针非空,分发模式匹配
请求完成 WdfRequestComplete 检查是否每个请求都调用了Complete,避免请求泄漏
核心原则: UMDF驱动开发者应始终从WDFREQUEST的角度思考I/O处理,而不是IRP。框架的拦截机制虽然增加了抽象层,但也提供了队列管理、同步、电源管理等强大功能,正确理解这一流程是高效调试的基础。