2. WDM驱动对象与设备对象:DRIVER_OBJECT与DEVICE_OBJECT结构详解
在WDM框架中,驱动对象(DRIVER_OBJECT)与设备对象(DEVICE_OBJECT)是驱动开发者必须掌握的两个核心数据结构。它们构成了操作系统与驱动程序之间的通信桥梁,也是I/O请求分发、设备栈管理的基础。本章将深入剖析这两个结构体的内部布局、关键字段含义以及它们之间的关联关系。
2.1 DRIVER_OBJECT:驱动对象的基石
每个WDM驱动程序在加载时,I/O管理器会为其创建一个唯一的DRIVER_OBJECT实例。该结构体位于<wdm.h>中,是驱动与系统交互的入口点。其核心字段定义如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
DriverSize |
CSHORT |
驱动对象结构体的大小(字节数),用于版本兼容性检查。 |
DriverStart |
PVOID |
驱动映像在内存中的起始地址。 |
DriverSection |
PVOID |
指向驱动映像加载信息的内部指针(由系统管理)。 |
DriverExtension |
PDRIVER_EXTENSION |
指向驱动扩展结构,包含驱动对象名称、服务键名等附加信息。 |
MajorFunction |
PDRIVER_DISPATCH[IRP_MJ_MAXIMUM_FUNCTION] |
分发函数数组,每个IRP主功能码对应一个处理函数指针。这是驱动处理I/O请求的核心入口表。 |
DriverUnload |
PDRIVER_UNLOAD |
驱动卸载回调函数指针,用于清理资源。 |
DeviceObject |
PDEVICE_OBJECT |
指向该驱动创建的第一个设备对象的指针。通过此字段可遍历驱动管理的所有设备对象链表。 |
HardwareDatabase |
PVOID |
指向硬件配置数据库的指针(通常用于PnP驱动)。 |
FastIoDispatch |
PFAST_IO_DISPATCH |
快速I/O分发表指针(用于缓存文件系统等高性能场景)。 |
DriverInit |
PDRIVER_INITIALIZE |
驱动初始化入口点(DriverEntry)的地址。 |
关键要点:
MajorFunction 数组是驱动对象中最活跃的字段。驱动在 DriverEntry 中必须为至少一个IRP主功能码(如 IRP_MJ_CREATE、IRP_MJ_DEVICE_CONTROL)注册分发函数,否则系统无法与驱动正常通信。
2.2 DEVICE_OBJECT:设备对象的抽象
设备对象是WDM框架中表示“设备”的抽象结构。每个设备对象代表一个逻辑或物理设备,并参与设备栈的构建。其核心字段如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
Type |
SHORT |
对象类型,必须设置为 IO_TYPE_DEVICE。 |
Size |
USHORT |
设备对象结构体的大小(字节数)。 |
ReferenceCount |
LONG |
对象引用计数,系统用于管理对象生命周期。 |
DriverObject |
PDRIVER_OBJECT |
指向创建该设备对象的驱动对象。这是设备对象与驱动对象之间的反向关联。 |
NextDevice |
PDEVICE_OBJECT |
指向同一驱动创建的下一个设备对象,形成单链表。驱动可通过 DRIVER_OBJECT.DeviceObject 遍历此链表。 |
AttachedDevice |
PDEVICE_OBJECT |
指向附加到该设备对象上层的设备对象(设备栈中的下一层)。 |
CurrentIrp |
PIRP |
当前正在处理的IRP指针(主要用于Start I/O模式)。 |
Flags |
ULONG |
设备对象特性标志,如 DO_BUFFERED_IO、DO_DIRECT_IO、DO_POWER_PAGABLE 等。 |
Characteristics |
ULONG |
设备特性标志,如 FILE_DEVICE_UNKNOWN、FILE_REMOVABLE_MEDIA 等。 |
DeviceExtension |
PVOID |
指向设备扩展结构的指针。驱动在此分配自定义数据(如硬件资源、状态标志、自旋锁等)。 |
DeviceType |
DEVICE_TYPE |
设备类型,如 FILE_DEVICE_UNKNOWN、FILE_DEVICE_KEYBOARD 等。 |
StackSize |
CCHAR |
设备栈中IRP堆栈单元的大小(字节数),用于IRP向下传递时分配堆栈空间。 |
AlignmentRequirement |
ULONG |
设备对数据对齐的要求(通常为0或按字节对齐)。 |
开发提示:
DeviceExtension 是驱动开发者最常使用的字段。在 IoCreateDevice 时指定扩展大小,系统会自动分配并清零。建议将设备相关的所有状态(如中断对象指针、Dpc对象、工作项、硬件寄存器基址等)集中存放在设备扩展中,避免使用全局变量。
2.3 驱动对象与设备对象的关联关系
驱动对象与设备对象之间存在双向关联,这种关系构成了WDM驱动管理设备的基础。下图展示了这种关联的静态结构:
2.4 设备对象链表遍历
驱动可以通过驱动对象的 DeviceObject 字段遍历其管理的所有设备对象。以下代码展示了典型的遍历模式:
// 遍历驱动对象下的所有设备对象
PDEVICE_OBJECT pDevObj = pDriverObject->DeviceObject;
while (pDevObj != NULL) {
// 访问当前设备对象
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
// 处理设备对象...
KdPrint(("设备对象: 0x%p, 扩展: 0x%p\n", pDevObj, pDevExt));
// 移动到下一个设备对象
pDevObj = pDevObj->NextDevice;
}
注意事项: 遍历设备对象链表时,必须持有适当的同步锁(如驱动对象的资源锁或设备扩展中的自旋锁),防止在遍历过程中设备对象被删除或卸载。在
DriverUnload 回调中遍历时,系统已保证不会有新的IRP到达,但仍需注意多核环境下的并发删除问题。
2.5 设备对象在设备栈中的位置
WDM框架中,设备对象通过 AttachedDevice 和 DeviceObject->StackSize 等字段参与设备栈的构建。下图展示了设备栈中设备对象的层次关系:
2.6 设备扩展的设计模式
设备扩展(Device Extension)是驱动开发者自定义数据的主要载体。良好的设备扩展设计能显著提升代码的可维护性和可读性。以下是一个典型的设备扩展结构体定义:
// 设备扩展结构体定义
typedef struct _DEVICE_EXTENSION {
// 设备对象指针(反向引用)
PDEVICE_OBJECT SelfDeviceObject;
// 硬件资源
PHYSICAL_ADDRESS IoBaseAddress; // I/O端口基址
ULONG IoRange; // I/O端口范围
PVOID MappedRegisterBase; // 内存映射寄存器基址
// 中断相关
PKINTERRUPT InterruptObject; // 中断对象指针
KSPIN_LOCK SpinLock; // 中断自旋锁
BOOLEAN InterruptRegistered; // 中断是否已注册
// DPC对象
KDPC DpcForIsr; // 用于ISR延后处理的DPC
KTIMER TimerForDpc; // 定时器对象(可选)
// 工作项
IO_WORKITEM *pWorkItem; // 系统工作项指针
// 设备状态
ULONG DeviceState; // 设备状态标志
BOOLEAN DeviceRemoved; // 设备是否已移除
// 同步对象
FAST_MUTEX Mutex; // 快速互斥体
// IOCTL相关
ULONG IoctlBufferSize; // IOCTL缓冲区大小
PVOID IoctlBuffer; // IOCTL缓冲区指针
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
设计建议:
- 设备扩展中应包含
SelfDeviceObject字段,方便在扩展中反向获取设备对象指针。 - 所有需要跨IRQL访问的数据(如中断处理中使用的标志)必须使用自旋锁保护。
- 避免在设备扩展中放置大块缓冲区,建议使用动态分配或Lookaside列表管理。
- 设备扩展在
IoCreateDevice时分配,在IoDeleteDevice时自动释放,无需手动管理内存。
2.7 驱动对象与设备对象的生命周期
理解驱动对象与设备对象的创建和销毁时机,对于正确管理资源至关重要。下表总结了它们的生命周期关键点:
| 阶段 | 驱动对象 | 设备对象 |
|---|---|---|
| 创建 | 由I/O管理器在驱动加载时自动创建 | 由驱动在 DriverEntry 或 AddDevice 中调用 IoCreateDevice 创建 |
| 初始化 | 在 DriverEntry 中填充分发函数指针、卸载回调等 |
在 AddDevice 中设置设备扩展、注册中断、附加设备栈等 |
| 运行期 | 作为分发函数调用的上下文,通过 DriverObject 参数传入 |
作为IRP处理的目标,通过 DeviceObject 参数传入 |
| 销毁 | 由I/O管理器在驱动卸载时自动销毁 | 由驱动在 DriverUnload 或 RemoveDevice 中调用 IoDeleteDevice 销毁 |
重要规则: 设备对象必须在驱动卸载前全部删除。如果驱动卸载时仍有未删除的设备对象,系统会触发蓝屏(BugCheck 0xCE: DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS)。建议在
DriverUnload 中遍历设备对象链表并逐一删除。
2.8 本章小结
- DRIVER_OBJECT 是驱动与系统交互的入口,核心字段包括
MajorFunction分发函数数组、DeviceObject设备对象链表头、DriverUnload卸载回调。 - DEVICE_OBJECT 是设备抽象的核心,关键字段包括
DriverObject反向引用、NextDevice链表指针、AttachedDevice设备栈链接、DeviceExtension自定义数据区。 - 驱动对象与设备对象通过
DeviceObject和DriverObject字段形成双向关联,支持设备对象链表的遍历。 - 设备对象通过
AttachedDevice字段参与设备栈构建,IRP在设备栈中逐层传递。 - 设备扩展是驱动开发者管理设备状态的主要手段,应包含所有设备相关的自定义数据,并注意同步保护。