3. UMDF驱动基本结构:DriverEntry、DeviceAdd、IO目标、设备接口、驱动卸载

UMDF(User-Mode Driver Framework)驱动的基本结构由一组固定的回调函数和对象模型组成。理解这些核心组件是开发稳定、高效驱动程序的基础。本章将详细剖析UMDF驱动的生命周期,从入口点 DriverEntry 到卸载过程,并重点介绍设备初始化、I/O目标以及设备接口的注册机制。

3.1 驱动入口点:DriverEntry

DriverEntry 是UMDF驱动的第一个执行函数,类似于应用程序的 main 函数。当驱动被加载到用户模式宿主进程(WudfHost.exe)中时,框架会调用此函数。其主要职责是初始化驱动对象并注册事件回调。

关键职责:
  • 创建并初始化 IWDFDriver 对象。
  • 设置驱动注册表路径和配置信息。
  • 注册 IDriverEntry 接口,特别是 OnDeviceAdd 回调。
  • 执行任何一次性的全局初始化(如日志系统、全局锁)。

以下是一个典型的 DriverEntry 实现示例:

HRESULT CMyDriver::OnDriverInitialize(
    __in IWDFDriver *pDriver,
    __in IWDFDeviceInitialize *pDeviceInit
    )
{
    UNREFERENCED_PARAMETER(pDeviceInit);
    
    // 保存驱动对象指针
    m_pDriver = pDriver;
    
    // 注册设备添加回调
    pDriver->SetDriverEntry(this);
    
    return S_OK;
}

// 标准DriverEntry入口
HRESULT STDAPICALLTYPE DllGetClassObject(
    __in REFCLSID rclsid,
    __in REFIID riid,
    __out LPVOID *ppv
    )
{
    // COM类工厂实现
    return CMyDriver::CreateInstance(rclsid, riid, ppv);
}
注意:UMDF驱动本质上是COM对象,因此 DriverEntry 实际上是通过COM类工厂机制实现的。驱动必须导出 DllGetClassObject 和 DllCanUnloadNow 函数。
DriverEntry 初始化流程 WudfHost.exe 加载 DllGetClassObject OnDriverInitialize 注册 OnDeviceAdd 全局初始化 驱动加载完成后等待 PnP 管理器创建设备

3.2 设备添加回调:OnDeviceAdd

OnDeviceAdd 是UMDF驱动中最重要的回调函数。当PnP管理器检测到新设备并确定由该驱动控制时,框架会调用此函数。在此函数中,驱动需要完成设备对象的创建、初始化以及I/O处理程序的注册。

步骤 操作 说明
1 创建设备对象 调用 pDriver->CreateDevice() 创建 IWDFDevice 实例
2 配置设备属性 设置设备名称、访问掩码、独占模式等
3 创建I/O队列 调用 IWDFDevice::CreateIoQueue() 创建默认或自定义队列
4 注册I/O回调 设置 IQueueCallbackRead、IQueueCallbackWrite 等接口
5 创建设备接口 调用 IWDFDevice::CreateDeviceInterface() 暴露给应用程序
HRESULT CMyDevice::OnDeviceAdd(
    __in IWDFDriver *pDriver,
    __in IWDFDeviceInitialize *pDeviceInit
    )
{
    HRESULT hr;
    CComPtr<IWDFDevice> pDevice;
    
    // 1. 创建设备对象
    hr = pDriver->CreateDevice(pDeviceInit, this, &pDevice);
    if (FAILED(hr)) return hr;
    
    // 2. 创建默认I/O队列(并行队列)
    CComPtr<IWDFIoQueue> pDefaultQueue;
    hr = pDevice->CreateIoQueue(
        this,                     // 回调接口
        TRUE,                     // 默认队列
        WdfIoQueueDispatchParallel, // 分发模式
        TRUE,                     // 电源管理
        FALSE,                    // 允许零长度缓冲区
        &pDefaultQueue
        );
    if (FAILED(hr)) return hr;
    
    // 3. 注册设备接口 GUID
    hr = pDevice->CreateDeviceInterface(
        &GUID_DEVINTERFACE_MYDRIVER,
        NULL
        );
    
    return hr;
}
常见错误:在 OnDeviceAdd 中执行耗时操作(如等待硬件就绪)会导致系统认为驱动无响应。建议将硬件初始化逻辑放在 IPnpCallback::OnD0Entry 中处理。
OnDeviceAdd 设备创建流程 PnP 设备到达通知 CreateDevice() CreateIoQueue() CreateDeviceInterface() 设备就绪

3.3 I/O目标(I/O Target)

I/O目标是UMDF中用于发送I/O请求到内核模式或其它驱动组件的抽象对象。UMDF驱动通常不能直接访问硬件,而是通过I/O目标将请求转发到内核模式驱动程序堆栈。

I/O目标分为两种主要类型:

  • 本地I/O目标:指向当前设备栈的下一个内核模式驱动(通常是KMDF驱动或端口驱动)。
  • 远程I/O目标:指向系统中任意其他设备栈,用于跨驱动通信。
属性 本地I/O目标 远程I/O目标
创建方式 IWDFDevice::GetDefaultIoTarget() IWDFDevice::CreateRemoteTarget()
目标对象 内核模式驱动栈 其他设备接口
典型用途 发送DMA请求、读写硬件寄存器 与传感器、过滤器等协同工作
生命周期 与设备对象绑定 由驱动管理
// 获取默认I/O目标并发送请求
HRESULT CMyDevice::SendIoRequest(
    __in IWDFDevice *pDevice,
    __in IWDFMemory *pMemory
    )
{
    CComPtr<IWDFIoTarget> pIoTarget;
    CComPtr<IWDFIoRequest> pRequest;
    HRESULT hr;
    
    // 获取默认I/O目标
    pIoTarget = pDevice->GetDefaultIoTarget();
    if (!pIoTarget) return E_FAIL;
    
    // 创建I/O请求
    hr = pDevice->CreateIoRequest(NULL, &pRequest);
    if (FAILED(hr)) return hr;
    
    // 格式化请求为写操作
    hr = pRequest->FormatWrite(
        pMemory,          // 数据缓冲区
        NULL,             // 偏移量
        NULL              // 完成回调
        );
    if (FAILED(hr)) return hr;
    
    // 发送请求到I/O目标
    hr = pRequest->Send(pIoTarget);
    
    return hr;
}
性能提示:对于频繁的I/O操作,建议使用 IWDFIoRequest::SendAndForget 方法,它不需要等待完成回调,减少了用户模式与内核模式之间的上下文切换开销。
UMDF I/O目标通信架构 用户模式 (UMDF 宿主进程) UMDF 驱动 本地 I/O 目标 远程 I/O 目标 内核模式 KMDF 驱动 其他设备栈 I/O目标封装了跨模式通信的细节,驱动无需处理IRP

3.4 设备接口(Device Interface)

设备接口是UMDF驱动暴露给用户模式应用程序的通信端点。通过注册一个全局唯一的GUID,应用程序可以使用Win32 API(如 CreateFile)打开设备并发送IOCTL请求。

设备接口的注册流程:

  1. 在驱动中定义一个GUID(例如 GUID_DEVINTERFACE_MYDRIVER)。
  2. 在 OnDeviceAdd 中调用 CreateDeviceInterface 注册。
  3. 应用程序通过 SetupDiGetClassDevs 枚举设备接口实例。
  4. 应用程序调用 CreateFile 打开设备路径。
  5. 驱动通过I/O队列接收来自应用程序的请求。
// 定义设备接口GUID
// {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
DEFINE_GUID(GUID_DEVINTERFACE_MYDRIVER, 
    0xa1b2c3d4, 0xe5f6, 0x7890, 
    0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90);

// 在OnDeviceAdd中注册
hr = pDevice->CreateDeviceInterface(
    &GUID_DEVINTERFACE_MYDRIVER,
    NULL    // 参考字符串,可选
    );

// 应用程序端打开设备
HANDLE hDevice = CreateFile(
    L"\\\\.\\GLOBALROOT\\Device\\UMDF\\{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}",
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
    );
最佳实践:
  • 使用 SetupDiGetClassDevs 和 SetupDiEnumDeviceInfo 动态获取设备路径,避免硬编码。
  • 设备接口GUID应在驱动头文件中定义,并随驱动安装包一起发布。
  • 支持多个设备实例时,每个实例都会注册独立的设备接口。

3.5 驱动卸载(Driver Unload)

当驱动被卸载或系统关闭时,UMDF框架会调用驱动的清理回调。驱动必须在此阶段释放所有已分配的资源,包括内存、句柄、同步对象等。

UMDF驱动的卸载流程:

阶段 回调函数 操作
1 IPnpCallback::OnD0Exit 设备进入D0(工作状态)退出,停止硬件访问
2 IPnpCallback::OnSurpriseRemoval 处理意外移除(如USB热插拔)
3 IDriverEntry::OnDriverUnload 驱动卸载前清理全局资源
4 COM卸载 DllCanUnloadNow 返回S_OK,宿主进程释放DLL
// 驱动卸载回调
HRESULT CMyDriver::OnDriverUnload(
    __in IWDFDriver *pDriver
    )
{
    UNREFERENCED_PARAMETER(pDriver);
    
    // 释放全局资源
    if (m_pGlobalLock) {
        delete m_pGlobalLock;
        m_pGlobalLock = NULL;
    }
    
    // 关闭全局日志句柄
    if (m_hLogFile != INVALID_HANDLE_VALUE) {
        CloseHandle(m_hLogFile);
        m_hLogFile = INVALID_HANDLE_VALUE;
    }
    
    return S_OK;
}

// 设备清理回调
HRESULT CMyDevice::OnD0Exit(
    __in IWDFDevice *pDevice,
    __in WDF_POWER_DEVICE_STATE previousState
    )
{
    UNREFERENCED_PARAMETER(pDevice);
    UNREFERENCED_PARAMETER(previousState);
    
    // 停止所有待处理的I/O请求
    AbortPendingRequests();
    
    // 释放设备级资源
    ReleaseHardwareResources();
    
    return S_OK;
}
重要:UMDF驱动卸载时,框架会自动清理I/O队列和设备对象。但驱动必须确保所有异步操作(如待处理的DMA传输)在 OnD0Exit 中完成或取消,否则可能导致内存泄漏或系统崩溃。
UMDF 驱动卸载生命周期 设备移除通知 OnD0Exit 资源清理 卸载 卸载过程中的关键操作 1. 停止所有I/O队列,拒绝新的请求 2. 取消所有待处理的DMA传输和异步操作 3. 释放硬件资源(寄存器映射、中断句柄等) 4. 框架自动销毁设备对象和I/O目标

3.6 本章小结

UMDF驱动的基本结构围绕COM对象模型和事件回调机制构建。核心要点总结如下:

  • DriverEntry:通过COM类工厂实现,注册 OnDeviceAdd 回调并执行全局初始化。
  • OnDeviceAdd:创建设备对象、I/O队列和设备接口,是驱动初始化的核心。
  • I/O目标:封装了与内核模式通信的细节,支持本地和远程两种模式。
  • 设备接口:通过GUID暴露给应用程序,实现用户模式与驱动的IOCTL通信。
  • 驱动卸载:必须按顺序清理资源,确保所有异步操作完成。
学习建议:建议读者使用Visual Studio的UMDF模板创建空驱动项目,逐步添加上述组件,并通过WinDbg观察驱动加载和卸载的日志输出,加深对驱动生命周期的理解。