2. 驱动程序基本结构:DriverEntry、DriverUnload、IRP分发函数、驱动对象与设备对象的概念

一个完整的 Windows 内核驱动程序,无论其功能是处理中断、管理内存还是控制 I/O,都遵循一套固定的基本结构。理解 DriverEntry、DriverUnload、IRP 分发函数、驱动对象与设备对象是编写任何驱动程序的基石。本章将逐一剖析这些核心要素,并通过内联 SVG 图展示它们之间的协作关系。

2.1 驱动对象(DRIVER_OBJECT)

驱动对象是操作系统内核为每个已加载的驱动程序创建的一个数据结构,由 DRIVER_OBJECT 结构体表示。它是驱动在内核中的“身份证明”,包含了驱动名称、指向各个函数例程的指针、以及关联的设备对象链表头。

关键点: 驱动对象由 I/O 管理器在驱动加载时自动创建,并在驱动卸载时销毁。驱动开发者无需手动分配或释放该结构。

DRIVER_OBJECT 核心字段

字段名 类型 说明
DriverSize ULONG 驱动映像的大小(字节)
DriverStart PVOID 驱动映像在内存中的起始地址
DriverInit PVOID 指向 DriverEntry 例程的指针
DriverUnload PDRIVER_UNLOAD 指向 DriverUnload 例程的指针
MajorFunction PDRIVER_DISPATCH[IRP_MJ_MAXIMUM_FUNCTION] IRP 分发函数表,共 28 个函数指针
DeviceObject PDEVICE_OBJECT 指向该驱动创建的第一个设备对象的指针
Flags ULONG 驱动标志位(如 DO_BUFFERED_IO)

2.2 设备对象(DEVICE_OBJECT)

设备对象是驱动程序用来代表一个真实或虚拟设备的内核数据结构。每个设备对象都隶属于一个驱动对象,并可以挂载到设备栈中。设备对象定义了设备的特征、I/O 缓冲区策略以及指向下一个设备对象的指针。

提示: 一个驱动可以创建多个设备对象(例如,一个控制设备 + 多个功能设备),但至少需要创建一个设备对象才能接收 IRP。

DEVICE_OBJECT 核心字段

字段名 类型 说明
DriverObject PDRIVER_OBJECT 指向拥有该设备对象的驱动对象
NextDevice PDEVICE_OBJECT 指向同一驱动创建的下一个设备对象
AttachedDevice PDEVICE_OBJECT 指向设备栈中附加的上层设备对象
CurrentIrp PIRP 当前正在处理的 IRP(仅限 StartIO 驱动)
Flags ULONG 设备标志(如 DO_DIRECT_IO、DO_POWER_PAGABLE)
Characteristics ULONG 设备特性(如 FILE_REMOVABLE_MEDIA)
AlignmentRequirement ULONG 设备对数据对齐的要求
DeviceExtension PVOID 设备扩展,驱动自定义数据存储区

2.3 DriverEntry:驱动的入口点

DriverEntry 是每个内核驱动程序的入口函数,在驱动加载时由 I/O 管理器调用。它负责驱动的初始化工作,是所有驱动逻辑的起点。

DriverEntry 的职责

  1. 注册卸载例程:设置 DriverObject->DriverUnload 指针。
  2. 注册 IRP 分发函数:填充 DriverObject->MajorFunction[] 数组。
  3. 创建设备对象:调用 IoCreateDevice 创建一个或多个设备对象。
  4. 创建符号链接(可选):调用 IoCreateSymbolicLink 使应用层可以访问设备。
  5. 返回状态:返回 STATUS_SUCCESS 表示加载成功,否则驱动将被卸载。
NTSTATUS DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
)
{
    NTSTATUS status;
    PDEVICE_OBJECT deviceObject = NULL;

    // 1. 注册卸载例程
    DriverObject->DriverUnload = DriverUnload;

    // 2. 注册 IRP 分发函数
    DriverObject->MajorFunction[IRP_MJ_CREATE]         = DispatchCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]          = DispatchCreateClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
    DriverObject->MajorFunction[IRP_MJ_READ]           = DispatchRead;
    DriverObject->MajorFunction[IRP_MJ_WRITE]          = DispatchWrite;

    // 3. 创建设备对象
    status = IoCreateDevice(
        DriverObject,
        sizeof(DEVICE_EXTENSION),   // 设备扩展大小
        &DEVICE_NAME,               // 设备名称
        FILE_DEVICE_UNKNOWN,        // 设备类型
        0,                          // 设备特征
        FALSE,                      // 是否独占
        &deviceObject
    );
    if (!NT_SUCCESS(status)) {
        return status;
    }

    // 4. 创建符号链接(可选)
    status = IoCreateSymbolicLink(&SYMBOLIC_LINK_NAME, &DEVICE_NAME);
    if (!NT_SUCCESS(status)) {
        IoDeleteDevice(deviceObject);
        return status;
    }

    // 初始化设备扩展(略)

    KdPrint(("DriverEntry: 驱动加载成功\n"));
    return STATUS_SUCCESS;
}

2.4 DriverUnload:驱动的卸载例程

DriverUnload 在驱动被卸载时由 I/O 管理器调用。它的核心任务是清理 DriverEntry 中分配的所有资源,包括删除符号链接和设备对象。

警告: 如果 DriverUnload 未能正确清理资源(如未删除设备对象),系统可能会在卸载后发生内存泄漏或蓝屏崩溃(BugCheck)。
VOID DriverUnload(
    _In_ PDRIVER_OBJECT DriverObject
)
{
    PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject;

    // 遍历并删除所有设备对象
    while (deviceObject != NULL) {
        PDEVICE_OBJECT nextDevice = deviceObject->NextDevice;

        // 删除符号链接(每个设备对象可能有对应的符号链接)
        UNICODE_STRING symLink;
        RtlInitUnicodeString(&symLink, L"\\DosDevices\\MyDriver");
        IoDeleteSymbolicLink(&symLink);

        // 删除设备对象
        IoDeleteDevice(deviceObject);

        deviceObject = nextDevice;
    }

    KdPrint(("DriverUnload: 驱动卸载完成\n"));
}

2.5 IRP 分发函数

IRP(I/O Request Packet)是 Windows 内核中用于传递 I/O 请求的核心数据结构。当应用程序调用 CreateFile、ReadFile、DeviceIoControl 等 API 时,I/O 管理器会构造一个 IRP,并根据请求类型调用驱动对象中对应的分发函数。

常见的 IRP 主功能码

主功能码 宏定义 对应应用层 API
0 IRP_MJ_CREATE CreateFile
2 IRP_MJ_CLOSE CloseHandle
3 IRP_MJ_READ ReadFile
4 IRP_MJ_WRITE WriteFile
14 IRP_MJ_DEVICE_CONTROL DeviceIoControl
15 IRP_MJ_INTERNAL_DEVICE_CONTROL (内核内部使用)

分发函数的标准实现模式

NTSTATUS DispatchCreateClose(
    _In_ PDEVICE_OBJECT DeviceObject,
    _In_ PIRP           Irp
)
{
    UNREFERENCED_PARAMETER(DeviceObject);

    // 设置完成状态
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;

    // 完成 IRP
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

NTSTATUS DispatchDeviceControl(
    _In_ PDEVICE_OBJECT DeviceObject,
    _In_ PIRP           Irp
)
{
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    ULONG ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;

    switch (ioControlCode) {
        case IOCTL_MY_OPERATION:
            // 处理自定义控制码
            KdPrint(("DispatchDeviceControl: 收到 IOCTL_MY_OPERATION\n"));
            Irp->IoStatus.Status = STATUS_SUCCESS;
            break;

        default:
            Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }

    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return Irp->IoStatus.Status;
}

2.6 驱动对象与设备对象的协作关系

下图展示了驱动对象、设备对象、IRP 分发函数以及设备栈之间的完整协作关系。理解这张图是掌握驱动基本结构的关键。

驱动对象与设备对象协作关系图 DRIVER_OBJECT DriverInit → DriverEntry DriverUnload → DriverUnload MajorFunction[0] → DispatchCreate MajorFunction[14] → DispatchControl MajorFunction[...] → ... DeviceObject → 指向设备对象链表 Flags → 驱动标志 DEVICE_OBJECT (设备1) DriverObject → 指向驱动对象 NextDevice → 指向设备2 AttachedDevice → 上层过滤驱动 CurrentIrp → 当前 IRP Flags → DO_DIRECT_IO DeviceExtension → 自定义数据 DEVICE_OBJECT (设备2) DriverObject → 指向驱动对象 NextDevice → NULL AttachedDevice → NULL Flags → DO_BUFFERED_IO DeviceExtension → 自定义数据 IRP 分发函数 IRP_MJ_CREATE IRP_MJ_CLOSE IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_DEVICE_CONTROL IRP_MJ_INTERNAL_DEVICE_CTL IRP_MJ_PNP IRP_MJ_POWER IRP_MJ_SYSTEM_CONTROL ... IRP_MJ_MAXIMUM_FUNCTION DeviceObject NextDevice MajorFunction[] 说明:驱动对象通过 DeviceObject 指针链接设备对象链表;通过 MajorFunction 数组索引 IRP 分发函数。 设备对象通过 NextDevice 形成链表,通过 AttachedDevice 参与设备栈。

2.7 设备扩展(Device Extension)

设备扩展是驱动开发者可以自定义的一块内存区域,与设备对象一一对应。它通常用于存储设备状态、自旋锁、中断对象指针、缓冲区指针等运行时数据。

最佳实践: 将设备相关的所有全局数据放入设备扩展中,而不是使用全局变量。这样可以支持多设备实例,并避免同步问题。
// 设备扩展结构体定义
typedef struct _DEVICE_EXTENSION {
    PDEVICE_OBJECT  DeviceObject;       // 回指设备对象
    UNICODE_STRING  DeviceName;         // 设备名称
    UNICODE_STRING  SymLinkName;        // 符号链接名称
    KSPIN_LOCK      SpinLock;           // 自旋锁
    LIST_ENTRY      PendingIrpList;     // 待处理 IRP 链表
    ULONG           DeviceState;        // 设备状态
    PVOID           InterruptObject;    // 中断对象指针(后续章节)
    // ... 其他自定义字段
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

// 在 DriverEntry 中创建设备对象时指定扩展大小
status = IoCreateDevice(
    DriverObject,
    sizeof(DEVICE_EXTENSION),   // 指定扩展大小
    &DEVICE_NAME,
    FILE_DEVICE_UNKNOWN,
    0,
    FALSE,
    &deviceObject
);

// 获取设备扩展指针
PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)deviceObject->DeviceExtension;

2.8 驱动加载与卸载的完整流程

下表总结了从驱动加载到卸载的完整生命周期,以及每个阶段涉及的核心函数和数据结构。

阶段 触发条件 调用的例程 关键操作
加载 系统启动 / sc start / 动态加载 DriverEntry 注册分发函数、创建设备对象、创建符号链接
运行 应用层发起 I/O 请求 MajorFunction[] 中的分发函数 处理 IRP、访问硬件、管理中断
卸载 sc stop / 系统关闭 / 动态卸载 DriverUnload 删除符号链接、删除设备对象、释放资源

2.9 常见错误与避坑指南

  • 忘记注册 DriverUnload:驱动卸载时不会调用清理例程,导致设备对象残留,系统可能蓝屏。
  • 在 DriverEntry 中返回失败状态后未清理已创建资源:例如先创建了设备对象,后续操作失败直接返回,导致设备对象泄漏。
  • IRP 分发函数未调用 IoCompleteRequest:IRP 永远挂起,应用层线程阻塞,最终导致系统死锁。
  • 设备扩展大小指定为 0:无法存储设备状态,驱动功能受限。
  • 符号链接名称格式错误:应用层无法通过 Win32 API 打开设备,必须使用 \DosDevices\XXX 格式。
核心记忆点: 驱动对象是“工厂”,设备对象是“产品”,IRP 分发函数是“生产线”,设备扩展是“产品说明书”。工厂(DriverEntry)负责搭建生产线并生产产品;产品(设备对象)通过生产线(分发函数)处理客户(应用层)的订单(IRP);说明书(设备扩展)记录产品的状态和配置。