2. 驱动程序基本结构:DriverEntry、DriverUnload、IRP分发函数、驱动对象与设备对象的概念
一个完整的 Windows 内核驱动程序,无论其功能是处理中断、管理内存还是控制 I/O,都遵循一套固定的基本结构。理解 DriverEntry、DriverUnload、IRP 分发函数、驱动对象与设备对象是编写任何驱动程序的基石。本章将逐一剖析这些核心要素,并通过内联 SVG 图展示它们之间的协作关系。
2.1 驱动对象(DRIVER_OBJECT)
驱动对象是操作系统内核为每个已加载的驱动程序创建的一个数据结构,由 DRIVER_OBJECT 结构体表示。它是驱动在内核中的“身份证明”,包含了驱动名称、指向各个函数例程的指针、以及关联的设备对象链表头。
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 缓冲区策略以及指向下一个设备对象的指针。
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 的职责
- 注册卸载例程:设置
DriverObject->DriverUnload指针。 - 注册 IRP 分发函数:填充
DriverObject->MajorFunction[]数组。 - 创建设备对象:调用
IoCreateDevice创建一个或多个设备对象。 - 创建符号链接(可选):调用
IoCreateSymbolicLink使应用层可以访问设备。 - 返回状态:返回
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 中分配的所有资源,包括删除符号链接和设备对象。
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 分发函数以及设备栈之间的完整协作关系。理解这张图是掌握驱动基本结构的关键。
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格式。