2. KMDF 对象模型:WDF对象层次结构、WDF对象句柄、对象上下文空间、对象生命周期管理
KMDF 的核心设计哲学是“对象化”。它将内核资源(设备、队列、中断、DMA 适配器等)抽象为 WDF 对象,通过统一的句柄、上下文和生命周期管理机制,大幅降低驱动开发的复杂度和错误率。本章深入剖析 WDF 对象模型的四大支柱:层次结构、句柄、上下文空间与生命周期。
2.1 WDF 对象层次结构
WDF 对象以树状层次组织,每个对象都有一个父对象。父对象的生命周期决定了子对象的生命周期——当父对象被销毁时,所有子对象自动被清理。这种设计消除了传统 WDM 驱动中繁琐的手动资源释放逻辑。
核心原则: 子对象不能比父对象存活更久。驱动框架保证销毁顺序从叶子向根进行。
2.1.1 主要 WDF 对象类型与层级关系
| 对象类型 | WDF 结构体 | 典型父对象 | 用途 |
|---|---|---|---|
| WDFDRIVER | WDF_DRIVER_CONFIG | 无(根对象) | 驱动入口,管理全局资源 |
| WDFDEVICE | WDF_DEVICE_INIT | WDFDRIVER | 代表一个物理或虚拟设备 |
| WDFQUEUE | WDF_IO_QUEUE_CONFIG | WDFDEVICE | 管理 I/O 请求的调度与并行 |
| WDFINTERRUPT | WDF_INTERRUPT_CONFIG | WDFDEVICE | 中断服务例程(ISR)与被动级处理 |
| WDFDMAENABLER | WDF_DMA_ENABLER_CONFIG | WDFDEVICE | DMA 适配器管理与通道分配 |
| WDFDMATRANSACTION | WDF_DMA_TRANSACTION_CONFIG | WDFDMAENABLER | 单次 DMA 传输的生命周期控制 |
| WDFMEMORY | WDF_MEMORY_DESCRIPTOR | WDFDEVICE 或 WDFDMATRANSACTION | 缓冲区抽象,支持分页/非分页内存 |
| WDFTIMER | WDF_TIMER_CONFIG | WDFDEVICE 或 WDFQUEUE | 定时器回调,用于超时或周期性任务 |
2.1.2 层次结构 SVG 图
2.2 WDF 对象句柄
WDF 不直接暴露对象指针,而是通过 句柄(Handle) 引用所有对象。句柄是一个不透明类型,例如 WDFDEVICE、WDFQUEUE、WDFINTERRUPT。驱动代码只能通过句柄调用框架提供的 API,不能直接解引用或访问内部结构。
2.2.1 句柄的设计优势
- 类型安全: 每个句柄类型在编译时即被检查,防止将队列句柄误传给中断 API。
- 间接层: 框架可以在句柄背后执行引用计数、调试跟踪、对象验证,而无需驱动参与。
- 调试友好: 在 WinDbg 中,
!wdfhandle命令可以显示句柄对应的对象类型、状态、上下文和引用计数。
2.2.2 句柄的获取与使用
句柄通常在对象创建时通过输出参数获得,或通过父对象查询子对象句柄。典型模式如下:
WDFDEVICE device;
WDF_OBJECT_ATTRIBUTES attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
// 创建设备对象,句柄存储在 device 中
status = WdfDeviceCreate(&deviceInit, &attributes, &device);
// 使用句柄创建队列
WDFQUEUE queue;
WDF_IO_QUEUE_CONFIG queueConfig;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue);
// 使用句柄创建中断
WDFINTERRUPT interrupt;
WDF_INTERRUPT_CONFIG interruptConfig;
WDF_INTERRUPT_CONFIG_INIT(&interruptConfig, MyEvtInterruptIsr, MyEvtInterruptDpc);
status = WdfInterruptCreate(device, &interruptConfig, WDF_NO_OBJECT_ATTRIBUTES, &interrupt);
最佳实践: 始终使用
WDF_NO_OBJECT_ATTRIBUTES 作为默认属性,除非你需要自定义上下文或父对象。这可以减少代码量并避免属性配置错误。
2.3 对象上下文空间
每个 WDF 对象都可以关联一个 上下文空间(Context Space),这是驱动为对象分配的私有数据区域。上下文空间的大小和类型在对象创建时通过 WDF_OBJECT_ATTRIBUTES 指定,框架自动分配并清零。
2.3.1 上下文空间的定义与分配
使用 WDF_DECLARE_CONTEXT_TYPE 宏声明上下文结构体,然后在创建对象时关联:
// 定义设备上下文结构体
typedef struct _DEVICE_CONTEXT {
WDFQUEUE DefaultQueue;
WDFINTERRUPT Interrupt;
WDFDMAENABLER DmaEnabler;
ULONG DeviceFlags;
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
// 声明上下文类型(生成 Get/Set 辅助函数)
WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT);
// 创建设备时关联上下文
WDF_OBJECT_ATTRIBUTES attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.ContextSizeOverride = sizeof(DEVICE_CONTEXT);
attributes.EvtCleanupCallback = MyDeviceCleanup; // 可选清理回调
WDFDEVICE device;
status = WdfDeviceCreate(&deviceInit, &attributes, &device);
// 获取上下文指针
PDEVICE_CONTEXT devContext = GetDeviceContext(device);
devContext->DeviceFlags = 0x01;
2.3.2 上下文空间的生命周期与访问
| 阶段 | 上下文状态 | 驱动行为 |
|---|---|---|
| 对象创建时 | 框架自动分配并清零 | 在 EvtDriverDeviceAdd 中初始化字段 |
| 对象存活期 | 可读写,与对象绑定 | 通过 GetDeviceContext(handle) 随时访问 |
| 对象销毁前 | 框架调用 EvtCleanupCallback |
释放上下文中的手动分配资源(如自旋锁) |
| 对象销毁后 | 上下文内存被框架回收 | 驱动不能再访问,否则导致崩溃 |
常见错误: 在
EvtCleanupCallback 之后仍然持有上下文指针并尝试访问。框架在回调返回后立即释放上下文内存。务必在回调中将指针置 NULL 或不再使用。
2.4 对象生命周期管理
WDF 对象的生命周期由 引用计数 和 父子关系 共同控制。框架自动管理大部分生命周期,但驱动在特定场景下需要主动干预。
2.4.1 生命周期状态机
2.4.2 引用计数与父子关系
框架为每个对象维护内部引用计数。以下情况会增加引用:
- 对象被创建时,父对象持有 1 个引用。
- 驱动调用
WdfObjectReference显式增加引用(例如将对象指针传递给工作项)。 - 框架内部(如 I/O 队列持有请求对象的引用)自动增加。
引用计数归零时,框架依次调用 EvtCleanupCallback 和 EvtDestroyCallback,然后释放对象内存。
2.4.3 驱动主动干预的场景
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 延迟删除对象 | WdfObjectDereference + 异步清理 |
在 DPC 或工作项中释放引用,避免在 IRQL > PASSIVE_LEVEL 时删除 |
| 防止对象提前销毁 | WdfObjectReference / WdfObjectDereference |
在异步操作期间增加引用,操作完成后释放 |
| 自定义清理顺序 | 在 EvtCleanupCallback 中手动销毁子对象 |
例如先销毁中断再销毁 DMA 适配器 |
| 调试对象泄漏 | 启用 WDF 验证器:!wdfdriverinfo 0x10 |
显示所有未释放的对象及其调用栈 |
2.4.4 生命周期管理代码示例
// 在 EvtCleanupCallback 中释放手动分配的资源
VOID MyDeviceCleanup(WDFOBJECT Object) {
PDEVICE_CONTEXT devContext = GetDeviceContext(Object);
// 释放手动分配的自旋锁
if (devContext->Lock) {
KeReleaseSpinLock(devContext->Lock, devContext->OldIrql);
ExFreePoolWithTag(devContext->Lock, 'LCKD');
devContext->Lock = NULL;
}
// 注意:不要在此处调用 WdfObjectDelete,框架会自动处理子对象
}
// 在异步操作中安全使用对象
VOID MyAsyncWorkItem(_In_ WDFWORKITEM WorkItem) {
PWORKITEM_CONTEXT workCtx = GetWorkItemContext(WorkItem);
WDFDEVICE device = workCtx->Device;
// 执行异步操作...
// 操作完成后释放设备引用
WdfObjectDereference(device);
WdfObjectDelete(WorkItem); // 工作项自身清理
}
性能提示: 频繁调用
WdfObjectReference / WdfObjectDereference 在高吞吐路径(如每秒数万次中断)中会产生可测量的开销。考虑使用 引用计数批处理 或 对象池 来减少原子操作次数。在 DMA 热路径中,优先复用 WDFDMATRANSACTION 对象而非频繁创建销毁。
2.5 本章小结
- 层次结构: WDF 对象以树状组织,父对象控制子对象生命周期,自动清理避免泄漏。
- 句柄: 不透明类型提供类型安全和调试能力,驱动通过句柄与框架交互。
- 上下文空间: 每个对象可关联私有数据,通过
WDF_DECLARE_CONTEXT_TYPE声明,框架自动管理内存。 - 生命周期: 引用计数 + 父子关系驱动状态机,驱动在
EvtCleanupCallback中释放手动资源,框架负责最终销毁。