4. WDM设备扩展:设备扩展结构体的定义、初始化与使用

在WDM驱动框架中,设备扩展(Device Extension)是驱动开发者自定义的一块内存区域,与每个设备对象(DEVICE_OBJECT)一一绑定。它相当于驱动为每个设备实例维护的“私有数据仓库”,用于存储设备状态、硬件资源、中断上下文、I/O缓冲区指针等关键信息。正确使用设备扩展是编写可重入、多实例安全WDM驱动的基础。

核心概念:设备扩展不是系统预定义的结构,而是由驱动开发者自行定义的结构体类型。驱动在创建设备对象时,通过 IoCreateDevice 的 DeviceExtensionSize 参数指定其大小,系统自动分配并清零。

4.1 设备扩展结构体的定义

设备扩展结构体通常定义在驱动的主头文件中,其内容完全由驱动的功能需求决定。以下是一个典型的中断驱动型WDM设备扩展结构体定义:

// 定义设备扩展结构体
typedef struct _DEVICE_EXTENSION {
    // 设备对象指针(反向引用)
    PDEVICE_OBJECT  Self;

    // 硬件资源
    PHYSICAL_ADDRESS  MemoryBase;
    ULONG             MemoryLength;
    UCHAR             InterruptVector;
    KINTERRUPT_MODE   InterruptMode;

    // 中断对象与DPC
    PKINTERRUPT       InterruptObject;
    KDPC              Dpc;
    KTIMER            Timer;

    // I/O缓冲区管理
    PUCHAR            ReadBuffer;
    ULONG             ReadBufferSize;
    PUCHAR            WriteBuffer;
    ULONG             WriteBufferSize;

    // 设备状态标志
    ULONG             DeviceState;      // 0=停止, 1=启动, 2=运行
    BOOLEAN           InterruptEnabled;
    BOOLEAN           DeviceRemoved;

    // 同步锁
    KSPIN_LOCK        SpinLock;

    // IOCTL参数缓存
    ULONG             IoctlCode;
    PVOID             IoctlInputBuffer;
    ULONG             IoctlInputLength;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
设计要点:
  • Self指针: 存储设备对象地址,便于从扩展反向获取设备对象。
  • 硬件资源字段: 在 IRP_MN_START_DEVICE 处理中填充,供中断和I/O例程使用。
  • 中断对象指针: 保存 IoConnectInterrupt 返回的 PKINTERRUPT,用于中断连接/断开。
  • DPC与Timer: 用于中断下半部处理或超时操作。
  • 同步锁: 保护多线程/多中断并发访问的共享数据。

4.2 设备扩展的初始化

设备扩展的初始化发生在驱动处理 IRP_MJ_CREATE 或 IRP_MN_START_DEVICE 时。初始化流程分为三个阶段:

阶段 触发IRP 初始化内容 关键函数
阶段1:基础清零 IRP_MJ_CREATE 系统已自动清零,驱动填充Self指针、锁、默认状态 KeInitializeSpinLock
阶段2:资源分配 IRP_MN_START_DEVICE 解析CM_RESOURCE_LIST,填充MemoryBase、InterruptVector等 IoGetDeviceProperty
阶段3:中断连接 IRP_MN_START_DEVICE(资源分配后) 调用IoConnectInterrupt,保存InterruptObject IoConnectInterrupt

以下展示阶段1和阶段2的典型初始化代码:

// 阶段1:在AddDevice或IRP_MJ_CREATE中初始化
NTSTATUS InitializeDeviceExtension(PDEVICE_OBJECT DeviceObject)
{
    PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // 1. 反向引用
    devExt->Self = DeviceObject;

    // 2. 初始化同步锁
    KeInitializeSpinLock(&devExt->SpinLock);

    // 3. 初始化DPC对象(中断下半部)
    KeInitializeDpc(&devExt->Dpc, DpcRoutine, devExt);

    // 4. 设置默认状态
    devExt->DeviceState = 0;          // 停止状态
    devExt->InterruptEnabled = FALSE;
    devExt->DeviceRemoved = FALSE;

    // 5. 清零硬件资源字段(系统已清零,但显式赋值更安全)
    devExt->MemoryBase.QuadPart = 0;
    devExt->InterruptVector = 0;

    return STATUS_SUCCESS;
}

// 阶段2:在IRP_MN_START_DEVICE中填充硬件资源
NTSTATUS StartDeviceResources(PDEVICE_OBJECT DeviceObject, PCM_RESOURCE_LIST ResourceList)
{
    PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
    // 解析ResourceList,提取Memory和Interrupt资源
    // ...(省略具体解析代码)
    devExt->MemoryBase = memoryDescriptor->u.Port.Start;
    devExt->MemoryLength = memoryDescriptor->u.Port.Length;
    devExt->InterruptVector = interruptDescriptor->u.Interrupt.Vector;
    devExt->InterruptMode = interruptDescriptor->u.Interrupt.Mode;

    return STATUS_SUCCESS;
}
常见错误: 在 IRP_MN_START_DEVICE 之前就尝试访问硬件资源字段(如MemoryBase),此时资源尚未分配,访问会导致系统崩溃。必须严格遵循“资源分配 → 资源使用 → 资源释放”的生命周期。

4.3 设备扩展的使用模式

设备扩展在WDM驱动的各个例程中通过设备对象指针访问。以下展示三种典型使用场景:

4.3.1 在中断服务例程(ISR)中访问

BOOLEAN InterruptServiceRoutine(PKINTERRUPT Interrupt, PDEVICE_EXTENSION DevExt)
{
    // 读取硬件状态寄存器(通过DevExt->MemoryBase映射的地址)
    UCHAR status = READ_PORT_UCHAR((PUCHAR)DevExt->MemoryBase.QuadPart);

    if (status & INTERRUPT_PENDING) {
        // 记录中断数据到扩展缓冲区
        DevExt->ReadBuffer[0] = status;

        // 请求DPC(下半部处理)
        KeInsertQueueDpc(&DevExt->Dpc, NULL, NULL);

        return TRUE;  // 中断已处理
    }
    return FALSE;     // 不是本设备的中断
}

4.3.2 在IOCTL分发例程中访问

NTSTATUS IoctlDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    ULONG ioctlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

    // 使用自旋锁保护IOCTL参数缓存
    KIRQL oldIrql;
    KeAcquireSpinLock(&devExt->SpinLock, &oldIrql);

    devExt->IoctlCode = ioctlCode;
    devExt->IoctlInputBuffer = Irp->AssociatedIrp.SystemBuffer;
    devExt->IoctlInputLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;

    KeReleaseSpinLock(&devExt->SpinLock, oldIrql);

    // 根据IOCTL码执行具体操作
    switch (ioctlCode) {
        case IOCTL_GET_DEVICE_STATUS:
            // 从设备扩展读取状态
            break;
        case IOCTL_SET_INTERRUPT_MASK:
            // 修改设备扩展中的中断掩码
            break;
    }
    // ...
}

4.3.3 在DPC例程中访问

VOID DpcRoutine(PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
    PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeferredContext;

    // 从设备扩展读取中断累积数据
    // 执行耗时的数据处理(如拷贝到用户缓冲区)
    // 注意:DPC运行在DISPATCH_LEVEL,不能访问分页内存
    if (devExt->ReadBufferSize > 0) {
        // 处理数据...
    }
}

4.4 设备扩展的生命周期与清理

设备扩展的内存由系统管理,但驱动必须在 IRP_MN_REMOVE_DEVICE 中执行清理操作:

NTSTATUS RemoveDevice(PDEVICE_OBJECT DeviceObject)
{
    PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // 1. 断开中断(如果已连接)
    if (devExt->InterruptObject != NULL) {
        IoDisconnectInterrupt(devExt->InterruptObject);
        devExt->InterruptObject = NULL;
    }

    // 2. 释放硬件资源映射
    if (devExt->MemoryBase.QuadPart != 0) {
        MmUnmapIoSpace((PVOID)devExt->MemoryBase.QuadPart, devExt->MemoryLength);
    }

    // 3. 释放驱动分配的额外缓冲区
    if (devExt->ReadBuffer != NULL) {
        ExFreePoolWithTag(devExt->ReadBuffer, 'BufT');
        devExt->ReadBuffer = NULL;
    }

    // 4. 删除设备对象(系统自动释放设备扩展内存)
    IoDeleteDevice(DeviceObject);
}
最佳实践: 设备扩展的内存由 IoDeleteDevice 自动释放,驱动不要在扩展上调用 ExFreePool。但扩展中指向的额外分配内存(如ReadBuffer)必须由驱动显式释放。

4.5 设备扩展结构图

下图展示了设备扩展在WDM驱动框架中的位置及其与各系统组件的关系:

设备扩展结构图:DEVICE_EXTENSION 与系统组件关系 DEVICE_OBJECT 设备对象 DEVICE_EXTENSION 设备扩展(自定义数据) DeviceExtension 扩展内部字段 • Self (PDEVICE_OBJECT) • MemoryBase / MemoryLength • InterruptVector / InterruptMode • InterruptObject (PKINTERRUPT) • Dpc / Timer • ReadBuffer / WriteBuffer • SpinLock / DeviceState • IoctlCode / IoctlInputBuffer KINTERRUPT 中断对象 InterruptObject KDPC DPC对象 硬件资源 Memory / Interrupt MemoryBase 访问路径说明 1. IRP处理中通过 DeviceObject->DeviceExtension 获取扩展指针 2. 扩展内字段直接访问 3. 中断/DPC通过上下文传入

4.6 多设备实例与设备扩展

WDM驱动支持多个设备实例(如多个相同硬件),每个实例拥有独立的 DEVICE_OBJECT 和 DEVICE_EXTENSION。设备扩展的独立性保证了多实例安全:

场景 设备实例1 设备实例2 说明
设备对象 DeviceObject1 DeviceObject2 由 IoCreateDevice 分别创建
设备扩展 DevExt1 DevExt2 内存地址不同,互不干扰
硬件资源 MemoryBase=0xD0000 MemoryBase=0xD1000 PnP管理器分配不同资源
中断对象 Interrupt1 Interrupt2 可共享同一中断向量
ISR上下文 DevExt1 DevExt2 中断服务例程通过上下文区分
多实例优势: 设备扩展机制使得驱动代码可以完全重入。ISR、DPC、Dispatch例程只需通过传入的 PDEVICE_EXTENSION 指针操作对应实例的数据,无需全局变量或静态数组。这是WDM驱动支持热插拔和多设备的核心设计。

4.7 设备扩展使用避坑指南

  • 不要跨IRP级别访问: 在DISPATCH_LEVEL及以上不能访问分页内存,设备扩展本身在非分页池中,但扩展内的指针(如ReadBuffer)可能指向分页内存,需用 ProbeForRead/ProbeForWrite 验证。
  • 不要假设扩展已清零: 虽然 IoCreateDevice 会清零扩展,但 IoCreateDeviceSecure 等变体可能行为不同,建议在初始化时显式赋值所有字段。
  • 不要存储IRP指针: 设备扩展中不应长期保存IRP指针,IRP可能被取消或超时,应使用 IoCancelIrp 或取消例程处理。
  • 同步访问保护: 多线程/多中断并发访问设备扩展时,必须使用自旋锁(KSPIN_LOCK)或互斥体(FAST_MUTEX)保护共享字段。
  • 资源释放顺序: 在 IRP_MN_REMOVE_DEVICE 中,先断开中断、释放映射、释放额外内存,最后调用 IoDeleteDevice。
严重警告: 不要在设备扩展中存储指向 IRP 或 MDL 的指针并在ISR/DPC中访问。IRP可能在任意时刻被完成或取消,访问已释放的IRP会导致系统崩溃(BugCheck 0xA)。正确的做法是在ISR中记录状态,在DPC中通过 IoGetCurrentIrpStackLocation 重新获取当前IRP。