第 9 章 设备驱动 --- 9.1 Windows的设备驱动框架
本节深入剖析 Windows NT 设备驱动(WDM)框架。 WDM(Windows Driver Model)是 Windows 2000/XP 引入的分层驱动模型,ReactOS 在 ntoskrnl/io/iomgr/ 中实现了它的核心管理逻辑。理解 WDM 的关键,是把握 驱动对象(DRIVER_OBJECT)、设备对象(DEVICE_OBJECT)、IRP(I/O Request Packet)三件套 之间的关系,以及 AddDevice / DriverEntry / MajorFunction 派发表 的协作机制。
概述
Windows NT 设备驱动框架是 NT 内核 I/O 管理器的子系统。它由 三件套 构成:
- DRIVER_OBJECT:描述一个驱动的"身份",包含派发函数数组
- DEVICE_OBJECT:描述一个设备实例,多个设备可挂在同一驱动下
- IRP:描述一次 I/O 请求,沿设备栈从顶向下流动
WDM 相对 NT 4.0 旧式驱动的最大改进是 分层(layered) 和 PnP(Plug and Play) 。WDM 驱动通过 设备栈(device stack) 串联,PnP 管理器(ntoskrnl/io/pnpmgr/)动态构造和调整设备栈。
本节内容概览
- 9.1.0 框架图
- 9.1.1 驱动对象
DRIVER_OBJECT - 9.1.2 设备对象
DEVICE_OBJECT与设备栈 - 9.1.3 IRP(I/O Request Packet)
- 9.1.4 驱动入口点:DriverEntry
- 9.1.5 AddDevice 与 PnP
- 9.1.6 MajorFunction 派发表
- 9.1.7 关键 I/O API
- 9.1.8 总结与代码索引
学习目标
- 能够画出 DRIVER_OBJECT / DEVICE_OBJECT / IRP 三者关系
- 理解设备栈(device stack)的构造
- 知道
IoCallDriver、IoCompleteRequest的语义 - 区分 WDM 与 WDF 框架
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| I/O 管理器 | ntoskrnl/io/iomgr/driver.c(file:///d:/reactos/ntoskrnl/io/iomgr/driver.c) | IoCreateDriver、IoAllocateDriverObjectExtension |
| I/O 管理器(设备) | ntoskrnl/io/iomgr/device.c(file:///d:/reactos/ntoskrnl/io/iomgr/device.c) | IoCreateDevice、IoDeleteDevice |
| I/O 管理器(IRP) | ntoskrnl/io/iomgr/irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) | IoAllocateIrp、IoCompleteRequest |
| I/O 管理器(派发) | ntoskrnl/io/iomgr/dispatch.c(file:///d:/reactos/ntoskrnl/io/iomgr/dispatch.c) | IoCallDriver |
| PnP 管理器 | ntoskrnl/io/pnpmgr/(file:///d:/reactos/ntoskrnl/io/pnpmgr/) | IopInitializePnpManager、IopProcessStartDevice |
| 驱动装载 | ntoskrnl/io/iomgr/loader.c(file:///d:/reactos/ntoskrnl/io/iomgr/loader.c) | IopLoadDriverImage、NtLoadDriver |
| 设备对象类型 | sdk/include/xdk/iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) | DRIVER_OBJECT、DEVICE_OBJECT 定义 |
| IRP 类型 | sdk/include/xdk/iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) | IRP、IO_STACK_LOCATION 定义 |
9.1.0 框架图
应用层
| (CreateFileW / ReadFile / WriteFile)
v
+--------------------+
| I/O 管理器 | <--- ntoskrnl/io/iomgr/
+--------------------+
| (构造 IRP)
v
+--------------------+ 设备栈(device stack)
| Top DeviceObject | <--- 文件系统/类驱动
| (AttachedDevice链头)|
+--------------------+
|
v
+--------------------+
| FDO (Function Do) | <--- 功能驱动
| MajorFunction[IRP] |
+--------------------+
|
v
+--------------------+
| PDO (Physical Do) | <--- 总线驱动(如 pci.sys)
+--------------------+
|
v
+--------------------+
| HAL/Hardware |
+--------------------+
关键数据结构:
+-------------------+ +-------------------+
| DRIVER_OBJECT | | DEVICE_OBJECT |
| - MajorFunction[] |---> | - NextDevice |
| - DriverStartIo | | - AttachedDevice |
| - DriverExtension | | - CurrentIrp |
| - DeviceObject | | - DeviceExtension |
+-------------------+ | - Vpb |
+-------------------+
|
v
+-------------------+
| IRP |
| - MdlAddress |
| - CurrentStackLoc |
| - StackLocation[] |
+-------------------+
9.1.0a 驱动程序开发流程图
驱动开发全流程
┌─────────────────────────────────────────────────────────────────┐
│ 驱动程序开发全流程 │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┐
│ 1. 需求分析 │ 确定设备类型、功能需求、PnP/电源支持
└──────┬───────┘
│
v
┌──────────────┐ 否 ┌────────────────┐
│ 2. 选择模型 ├───────────>│ 老式驱动(NT4) │
│ WDM/WDF/旧式 │ │ DriverEntry 中 │
└──────┬───────┘ │ IoCreateDevice │
│ 是 └───────┬────────┘
v │
┌──────────────┐ │
│ 3. 编写代码 │ │
│ │ │
│ ┌──────────┐│ │
│ │DriverEntry││ 注册 MajorFunction[]│
│ │ 入口函数 ││ + AddDevice + Unload│
│ └────┬─────┘│ │
│ v │ │
│ ┌──────────┐│ │
│ │ AddDevice ││ 创建 FDO │
│ │ (WDM) ││ 附加到 PDO │
│ └────┬─────┘│ │
│ v │ │
│ ┌──────────┐│ │
│ │派发表实现 ││ IRP_MJ_CREATE │
│ │MajorFunc ││ IRP_MJ_READ/WRITE │
│ │ ││ IRP_MJ_CLOSE │
│ │ ││ IRP_MJ_PNP/POWER │
│ └────┬─────┘│ │
│ v │ │
│ ┌──────────┐│ │
│ │ IRP 处理 ││ IoCallDriver 下传 │
│ │ 流程 ││ IoCompleteRequest │
│ └────┬─────┘│ │
└──────┼──────┘ │
│ │
v │
┌──────────────┐ │
│ 4. 编译构建 │ │
│ gcc / cl │ │
│ 链接 .sys │ │
└──────┬───────┘ │
│ │
v │
┌──────────────┐ │
│ 5. 注册安装 │ │
│ 注册表配置 │ │
│ Services\ │ │
│ <DriverName>│ │
└──────┬───────┘ │
│ │
v │
┌──────────────┐ │
│ 6. 测试调试 │ │
│ WinDbg │ │
│ !devstack │ │
│ !drvobj │ │
│ !irp │ │
└──────┬───────┘ │
│ │
v │
┌──────────────┐ 否 │
│ 7. 验证通过? ├──────────────────>┘
└──────┬───────┘
│ 是
v
┌──────────────┐
│ 8. 部署发布 │
│ 打包 .sys │
│ INF 安装文件 │
└──────────────┘
IRP 在设备栈中的流动流程
用户态 CreateFileW / ReadFile / WriteFile
│
v
┌─────────────────────────────┐
│ I/O 管理器 │
│ 构造 IRP + 分配 StackLocation│
└──────────────┬──────────────┘
│ IoCallDriver
v
┌─────────────────────────────┐
│ 过滤设备 (Filter DO) │
│ ┌─────────────────────┐ │
│ │ 处理/修改 IRP │ │
│ │ 设置 CompletionRoutine│ │
│ │ IoCallDriver(下层) │ │
│ └──────────┬──────────┘ │
└─────────────┼───────────────┘
│ IoCallDriver
v
┌─────────────────────────────┐
│ 功能设备 (FDO) │
│ ┌─────────────────────┐ │
│ │ 解析 IRP 参数 │ │
│ │ 执行设备操作 │ │
│ │ 同步完成 或 异步排队 │ │
│ └──────────┬──────────┘ │
└─────────────┼───────────────┘
│ IoCallDriver
v
┌─────────────────────────────┐
│ 物理设备 (PDO) │
│ ┌─────────────────────┐ │
│ │ 总线驱动处理 │ │
│ │ 访问硬件寄存器 │ │
│ └──────────┬──────────┘ │
└─────────────┼───────────────┘
│
v
┌─────────────────────────────┐
│ IoCompleteRequest │
│ 逐层回调 CompletionRoutine │
│ 唤醒等待线程 │
└─────────────────────────────┘
9.1.1 驱动对象 DRIVER_OBJECT
DRIVER_OBJECT 是驱动的"身份证",定义于 iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h#L60-L91):
c
typedef struct _DRIVER_OBJECT {
PDEVICE_OBJECT DeviceObject; // 此驱动创建的设备链
PDRIVER_STARTIO DriverStartIo; // 旧式 StartIO 例程(可选)
PDRIVER_DELETEDEVICE DriverDeleteDevice; // 设备删除回调
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; // 派发表
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName; // 驱动名(\Driver\xxx)
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch; // Fast I/O 派发表(可选)
PDRIVER_INITIALIZE DriverInit; // DriverEntry
PDRIVER_STARTIO DriverStartIoEvent;
PDRIVER_UNLOAD DriverUnload; // Unload 例程
PDRIVER_ADD_DEVICE AddDevice; // PnP AddDevice 例程
struct _KDEVICE_QUEUE IoQueue; // StartIO 队列
} DRIVER_OBJECT;
关键字段
| 字段 | 作用 |
|---|---|
DeviceObject |
此驱动创建的所有设备链表头 |
MajorFunction[] |
主功能码(IRP_MJ_*)的派发函数数组,索引即 IRP 主功能码 |
DriverExtension |
驱动扩展(由驱动自由使用) |
HardwareDatabase |
注册表路径(HKLM...\Enum) |
DriverInit |
指向 DriverEntry,I/O 管理器在调用后清空 |
AddDevice |
PnP 驱动初始化新设备时调用 |
DriverUnload |
驱动卸载时调用 |
IoQueue |
IoStartPacket 使用的 StartIO 队列 |
生命周期
IopLoadDriverImage调用驱动的DriverEntryDriverEntry调用IoCreateDriver或由 I/O 管理器自动创建- 驱动调用
IoCreateDevice创建 DEVICE_OBJECT,挂到DriverObject->DeviceObject - PnP 触发
AddDevice创建 FDO 并附加到 PDO - 卸载时调用
DriverUnload释放资源
9.1.2 设备对象 DEVICE_OBJECT 与设备栈
DEVICE_OBJECT 是设备的"运行时表示",定义于 iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h#L150-L200):
c
typedef struct _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
LONG ReferenceCount;
PDRIVER_OBJECT DriverObject; // 拥有此设备的驱动
PDEVICE_OBJECT NextDevice; // 同驱动内的下一个设备
PDEVICE_OBJECT AttachedDevice; // 附加在顶上的设备
struct _IRP *CurrentIrp; // 正在处理的 IRP
PIO_TIMER Timer; // 设备定时器
ULONG Flags;
ULONG Characteristics;
PVPB Vpb; // 卷参数块
PVOID DeviceExtension; // 设备扩展
DEVICE_TYPE DeviceType;
ULONG StackSize; // 栈深度(IRP 分配参考)
LIST_ENTRY ListEntry;
PIO_COMPLETION_ROUTINE CompletionRoutine;
PVOID DeviceObjectExtension;
PDEVICE_OBJECT DeviceObjectExtension;
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
DEVOBJ_EXTENSION DevObjExtension;
PNP_DEVICE_STATE DeviceState;
PDMA_ADAPTER DmaAdapter;
ULONG StartIoKey;
PKEVENT StartIoEvent;
} DEVICE_OBJECT;
设备栈(Device Stack)
WDM 的核心结构是 设备栈。每个设备对象可以"附加"到另一个设备对象的顶端:
/------------------\
| Filter Device 0 | <- IoAttachDeviceToDeviceStack
+------------------+
| FDO (Function DO)| <- AddDevice 附加
+------------------+
| PDO (Physical DO)| <- 总线驱动创建
+------------------+
| Filter Device 1 | <- IoAttachDeviceToDeviceStack
\------------------/
每个设备栈的 最底层 是 PDO(Physical Device Object,由总线驱动创建),上层 是 FDO(Function Device Object,由功能驱动创建),更上层 是过滤设备(Filter Device Object)。
IoAttachDeviceToDeviceStack(device.c(file:///d:/reactos/ntoskrnl/io/iomgr/device.c))让一个设备附加到目标设备上方。当 IRP 沿设备栈流动时,最顶层的设备首先收到 IRP。
设备对象之间的关系
DeviceObject->NextDevice:本驱动内的下一个设备(设备链表)DeviceObject->AttachedDevice:附加在本设备顶端的设备DeviceObject->DeviceExtension:设备扩展(驱动私有数据)DeviceObject->Vpb:卷参数块(仅对卷设备有效)
IoCreateDevice
c
NTSTATUS
IoCreateDevice(IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject);
| 参数 | 含义 |
|---|---|
DriverExtensionSize |
设备扩展字节数(驱动私有) |
DeviceName |
对象名(可选;如 \Device\Beep) |
DeviceType |
设备类型(如 FILE_DEVICE_BEEP) |
Characteristics |
特性标志 |
Exclusive |
是否独占(一般 FALSE) |
9.1.3 IRP(I/O Request Packet)
IRP 是 I/O 请求的核心数据结构,定义于 iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h#L320-L420):
c
typedef struct _IRP {
CSHORT Type;
USHORT Size;
PMDL MdlAddress; // 缓冲区 MDL(直接 I/O)
ULONG Flags;
union {
struct _IRP *MasterIrp;
LONG IrpCount;
PVOID SystemBuffer; // 缓冲区(缓冲 I/O)
} AssociatedIrp;
LIST_ENTRY ThreadListEntry;
IO_STACK_LOCATION *CurrentStackLocation;
PFILE_OBJECT OriginalFileObject;
union {
struct {
IO_STACK_LOCATION *CurrentStackLocation;
PETHREAD Thread;
LIST_ENTRY ListEntry;
// ... Queue 字段
} Overlay;
struct {
PETHREAD Thread;
PCHAR AuxiliaryBuffer;
LIST_ENTRY ListEntry;
struct _IO_STACK_LOCATION *CurrentStackLocation;
PCHAR PacketType;
PCHAR Read;
PCHAR Write;
PCHAR Wait;
} Tail;
} Tail;
IO_STACK_LOCATION StackLocation[ANYSIZE_ARRAY]; // 栈单元
} IRP;
关键字段
MdlAddress:直接 I/O 时描述缓冲区的 MDLAssociatedIrp.SystemBuffer:缓冲 I/O 时由 I/O 管理器分配的内核缓冲CurrentStackLocation:当前栈单元指针StackLocation[]:栈单元数组(每个设备层一个),包含主功能码和参数
IO_STACK_LOCATION
c
typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction;
UCHAR MinorFunction;
UCHAR Flags;
UCHAR Control;
union {
struct { ... } Create; // IRP_MJ_CREATE
struct { ... } Read; // IRP_MJ_READ
struct { ... } Write; // IRP_MJ_WRITE
struct { ... } IoControl; // IRP_MJ_DEVICE_CONTROL
struct { ... } Pnp; // IRP_MJ_PNP
struct { ... } Power; // IRP_MJ_POWER
// ...
} Parameters;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
PIO_COMPLETION_ROUTINE CompletionRoutine;
PVOID Context;
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
主功能码 IRP_MJ_*
| 编号 | 名称 | 含义 |
|---|---|---|
| 0 | IRP_MJ_CREATE |
打开文件/设备 |
| 1 | IRP_MJ_CREATE_NAMED_PIPE |
命名管道创建 |
| 2 | IRP_MJ_CLOSE |
关闭 |
| 3 | IRP_MJ_READ |
读 |
| 4 | IRP_MJ_WRITE |
写 |
| 5 | IRP_MJ_QUERY_INFORMATION |
查询文件信息 |
| 6 | IRP_MJ_SET_INFORMATION |
设置文件信息 |
| 7 | IRP_MJ_QUERY_EA |
查询扩展属性 |
| 8 | IRP_MJ_SET_EA |
设置扩展属性 |
| 9 | IRP_MJ_FLUSH_BUFFERS |
刷新 |
| 10 | IRP_MJ_QUERY_VOLUME_INFORMATION |
查询卷信息 |
| 11 | IRP_MJ_SET_VOLUME_INFORMATION |
设置卷信息 |
| 12 | IRP_MJ_DIRECTORY_CONTROL |
目录控制 |
| 13 | IRP_MJ_FILE_SYSTEM_CONTROL |
文件系统控制 |
| 14 | IRP_MJ_DEVICE_CONTROL |
设备 I/O 控制 |
| 15 | IRP_MJ_INTERNAL_DEVICE_CONTROL |
内部 I/O 控制 |
| 16 | IRP_MJ_SHUTDOWN |
关机 |
| 17 | IRP_MJ_LOCK_CONTROL |
锁控制 |
| 18 | IRP_MJ_CLEANUP |
清理 |
| 19 | IRP_MJ_CREATE_MAILSLOT |
邮槽创建 |
| 20 | IRP_MJ_QUERY_SECURITY |
查询安全 |
| 21 | IRP_MJ_SET_SECURITY |
设置安全 |
| 22 | IRP_MJ_POWER |
电源管理 |
| 23 | IRP_MJ_SYSTEM_CONTROL |
系统控制 |
| 24 | IRP_MJ_DEVICE_CHANGE |
设备变化 |
| 25 | IRP_MJ_QUERY_QUOTA |
查询配额 |
| 26 | IRP_MJ_SET_QUOTA |
设置配额 |
| 27 | IRP_MJ_PNP |
即插即用 |
9.1.4 驱动入口点:DriverEntry
DriverEntry 是驱动的 初始化入口,签名:
c
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath);
标准 DriverEntry
c
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
PDEVICE_OBJECT DeviceObject;
UNICODE_STRING DeviceName;
NTSTATUS Status;
/* 1. 注册 MajorFunction 派发表 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyIoctl;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = MyCleanup;
/* 2. 注册 PnP AddDevice (WDM) */
DriverObject->DriverExtension->AddDevice = MyAddDevice;
/* 3. 注册 Unload */
DriverObject->DriverUnload = MyUnload;
/* 4. 创建设备对象(老式驱动) */
RtlInitUnicodeString(&DeviceName, L"\\Device\\MyDevice");
Status = IoCreateDevice(DriverObject, 0, &DeviceName,
FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN,
FALSE, &DeviceObject);
if (!NT_SUCCESS(Status)) return Status;
DeviceObject->Flags |= DO_DIRECT_IO; // 直接 I/O
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
DriverEntry 的工作流
- 注册派发表 :填充
DriverObject->MajorFunction[] - 注册 AddDevice:WDM 驱动必填,PnP 触发时调用
- 注册 Unload:可选,但需清理资源
- 创建设备对象(老式驱动):硬编码设备路径
- 返回 STATUS_SUCCESS :I/O 管理器将
DriverInit置为 NULL
PnP 与老式驱动的差异
| 维度 | 老式驱动(NT 4) | WDM 驱动(2000+) |
|---|---|---|
| 设备创建 | DriverEntry 中 IoCreateDevice |
AddDevice 中创建并附加到 PDO |
| 设备枚举 | 硬编码 | 注册表 Enumerator 路径枚举 |
| 电源管理 | 无 | IRP_MJ_POWER 派发 |
| PnP 通知 | 无 | IRP_MJ_PNP 派发 |
9.1.5 AddDevice 与 PnP
AddDevice 是 WDM 驱动的 PnP 入口:
c
NTSTATUS NTAPI
MyAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject)
{
PDEVICE_OBJECT Fdo;
NTSTATUS Status;
PDEVICE_EXTENSION Ext;
Status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), NULL,
FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN,
FALSE, &Fdo);
if (!NT_SUCCESS(Status)) return Status;
Ext = (PDEVICE_EXTENSION)Fdo->DeviceExtension;
/* 附加到 PDO 上方 */
Ext->LowerDevice = IoAttachDeviceToDeviceStack(Fdo, PhysicalDeviceObject);
if (!Ext->LowerDevice) { IoDeleteDevice(Fdo); return STATUS_DEVICE_NOT_CONNECTED; }
/* 设置标志 */
Fdo->Flags |= DO_POWER_PAGABLE | DO_DIRECT_IO;
Fdo->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
关键点
- PDO 传入 :
PhysicalDeviceObject是 PnP 管理器找到的总线驱动创建的 PDO - FDO 创建:在 AddDevice 中创建 FDO
- 附加 :调用
IoAttachDeviceToDeviceStack把 FDO 放在 PDO 上方 - LowerDevice :保存下层设备指针,用于
IoCallDriver(Ext->LowerDevice, Irp) - PnP IRP 下传 :当 PnP IRP 到达 FDO 时,FDO 必须 下传到 PDO,由 PDO 处理真正的硬件配置
9.1.6 MajorFunction 派发表
DriverObject->MajorFunction[] 是 索引即主功能码 的派发函数数组:
c
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyIoctl;
派发函数的标准签名
c
NTSTATUS MyDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
/* 1. 获取当前栈位置 */
PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
/* 2. 获取功能参数 */
switch (IoStackLocation->MajorFunction) { ... }
/* 3. 设置状态并完成 IRP */
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
同步 vs 异步完成
同步完成(在派发函数中):
c
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
异步完成(IRP 标记 pending、传递给下层或排队):
c
IoMarkIrpPending(Irp);
IoStartPacket(DeviceObject, Irp, NULL, NULL); // 进入 StartIO 队列
return STATUS_PENDING;
9.1.7 关键 I/O API
IoCallDriver
把 IRP 传递给下层设备:
c
NTSTATUS IoCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp);
调用后 IRP 流向 DeviceObject->MajorFunction[IRp->CurrentStackLocation->MajorFunction]。
IoCompleteRequest
完成 IRP,唤醒等待的线程:
c
VOID IoCompleteRequest(IN PIRP Irp, IN CCHAR PriorityBoost);
PriorityBoost 用于给等待线程的优先级提升(如 IO_NO_INCREMENT、IO_DISK_INCREMENT、IO_KEYBOARD_INCREMENT 等)。
IoMarkIrpPending
标记 IRP 为 pending 状态(异步返回):
c
VOID IoMarkIrpPending(IN PIRP Irp);
IoAllocateIrp / IoFreeIrp
分配/释放 IRP:
c
PIRP IoAllocateIrp(IN CCHAR StackSize, IN BOOLEAN ChargeQuota);
VOID IoFreeIrp(IN PIRP Irp);
StackSize 表示 IRP 需要多少层栈单元(每层一个 IO_STACK_LOCATION)。
IoStartPacket / IoStartNextPacket
旧式 StartIO 队列管理:
c
VOID IoStartPacket(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PULONG Key, IN PDRIVER_CANCEL CancelFunction);
VOID IoStartNextPacket(IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN Cancelable);
IoGetCurrentIrpStackLocation
获取当前栈位置:
c
FORCEINLINE PIO_STACK_LOCATION IoGetCurrentIrpStackLocation(PIRP Irp) {
return Irp->Tail.Overlay.CurrentStackLocation;
}
IoSkipCurrentIrpStackLocation
把 IRP 的当前栈位置前移,调用 IoCallDriver(NextDevice, Irp) 跳过本层:
c
#define IoSkipCurrentIrpStackLocation(Irp) { \
(Irp)->CurrentStackLocation--; \
(Irp)->Tail.Overlay.CurrentStackLocation--; \
}
9.1.8 总结
WDM 驱动框架的 核心要点:
- 三件套 :
DRIVER_OBJECT、DEVICE_OBJECT、IRP - 设备栈:PDO + FDO + 过滤设备 沿设备栈传递 IRP
- IRP 主功能码 :
IRP_MJ_*28 个,覆盖所有 I/O 操作 - 派发表 :
MajorFunction[]是索引数组 - DriverEntry:驱动初始化,注册派发函数与 AddDevice
- AddDevice:WDM PnP 入口,创建 FDO 并附加到 PDO
- IRP 流动 :
IoCallDriver下传,IoCompleteRequest唤醒 - 同步 vs 异步 :
IoMarkIrpPending标记异步
下一节 9.2 将以 beep.c 为例剖析一个完整的老式驱动实例。
9.1.A 深入理解驱动对象的生命周期管理
驱动对象的创建与初始化
在 ReactOS 中,DRIVER_OBJECT 的创建并非由驱动自身直接调用 ExAllocatePool 完成,而是由 I/O 管理器通过对象管理器(Object Manager)的机制来分配。当系统调用 NtLoadDriver 或在内核启动阶段加载驱动时,IopLoadDriverImage(loader.c(file:///d:/reactos/ntoskrnl/io/iomgr/loader.c))会首先从注册表路径 \Registry\Machine\System\CurrentControlSet\Services\<DriverName> 读取驱动的 ObjectName 字段,确定驱动对象的名称(通常为 \Driver\<DriverName>)。随后,对象管理器调用 ObCreateObject 分配一个带有对象头的 DRIVER_OBJECT,对象头的类型指针指向 IoDriverObjectType。
这一设计意味着每个驱动对象都受对象管理器的引用计数保护。当文件系统或其他内核组件通过 ObReferenceObjectByHandle 或 ObReferenceObjectByName 获取驱动对象引用时,引用计数递增;当引用归零时,对象管理器自动调用 IopDeleteDriver(driver.c(file:///d:/reactos/ntoskrnl/io/iomgr/driver.c))执行清理。
IopDeleteDriver 的清理流程
IopDeleteDriver 在驱动对象引用计数归零时被调用,它执行以下关键清理步骤:
-
释放客户端扩展链 :遍历
DriverExtension->ClientDriverExtension链表,逐一释放由IoAllocateDriverObjectExtension分配的扩展块。这些扩展块通常由多驱动共享框架(如 WDF 类库)使用,每个扩展块带有TAG_DRIVER_EXTENSION标签。 -
卸载驱动映像 :如果
DriverObject->DriverSection非空(表示驱动的二进制映像仍在内存中),调用MmUnloadSystemImage释放驱动的代码段和数据段。这一步将驱动的可执行映像从系统地址空间中移除。 -
释放名称字符串 :释放
DriverName.Buffer和DriverExtension->ServiceKeyName.Buffer的内存。
值得注意的是,IopDeleteDriver 中有一个断言 ASSERT(!DriverObject->DeviceObject),确保在驱动对象被销毁时,所有设备对象已经被删除。如果驱动卸载后遗留设备对象,将触发内核崩溃。这是 ReactOS 对驱动正确性的严格要求。
IopInvalidDeviceRequest:默认派发函数
在 IopLoadDriverImage 调用 DriverEntry 之前,I/O 管理器会将 DRIVER_OBJECT->MajorFunction[] 数组的所有 28 个槽位初始化为 IopInvalidDeviceRequest。这个函数的实现极其简单:
c
NTSTATUS NTAPI IopInvalidDeviceRequest(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_DEVICE_REQUEST;
}
这意味着如果驱动忘记注册某个主功能码的派发函数,对应的 IRP 将以 STATUS_INVALID_DEVICE_REQUEST 被拒绝,而不是导致系统崩溃。这是一个重要的安全网设计。在 Windows 中行为完全一致。
驱动重初始化机制
ReactOS 支持驱动的重初始化(re-initialization)。在 driver.c 中维护了两个链表:
DriverReinitListHead:运行时重初始化队列DriverBootReinitListHead:启动阶段重初始化队列
驱动可以通过 IoRegisterDriverReinitialization 注册一个回调函数,在特定条件下被重新调用。这在驱动需要延迟初始化(例如等待某些依赖资源就绪)时非常有用。
9.1.B 设备栈的深度剖析:附加、分离与同步
IopAttachDeviceToDeviceStackSafe 的实现细节
设备栈的构造核心在于 IoAttachDeviceToDeviceStack,其安全版本 IopAttachDeviceToDeviceStackSafe(device.c(file:///d:/reactos/ntoskrnl/io/iomgr/device.c))揭示了设备附加过程中的关键细节:
c
static PDEVICE_OBJECT NTAPI
IopAttachDeviceToDeviceStackSafe(PDEVICE_OBJECT SourceDevice,
PDEVICE_OBJECT TargetDevice,
PDEVICE_OBJECT* AttachedToDeviceObject)
{
OldIrql = KeAcquireQueuedSpinLock(LockQueueIoDatabaseLock);
AttachedDevice = IoGetAttachedDevice(TargetDevice);
// ... 检查目标设备状态 ...
AttachedDevice->AttachedDevice = SourceDevice;
SourceDevice->StackSize = AttachedDevice->StackSize + 1;
SourceDevice->AlignmentRequirement = AttachedDevice->AlignmentRequirement;
SourceDevice->SectorSize = AttachedDevice->SectorSize;
// ... 传播 DOE_START_PENDING 标志 ...
SourceDeviceExtension->AttachedTo = AttachedDevice;
KeReleaseQueuedSpinLock(LockQueueIoDatabaseLock, OldIrql);
}
这段代码揭示了几个重要设计:
-
StackSize 自动传播 :新附加的设备对象的
StackSize自动设为下层设备的StackSize + 1。这确保了 IRP 分配时能正确计算栈深度------IRP 的栈单元数组大小必须至少等于设备栈的深度。 -
对齐要求继承 :
AlignmentRequirement从下层设备继承到上层设备。这保证了上层驱动在分配缓冲区时满足下层硬件的对齐要求。例如,如果磁盘驱动要求扇区对齐(512 字节),所有附加在其上方的过滤驱动也会继承这一要求。 -
SectorSize 继承:类似地,扇区大小也沿设备栈向上传播。
-
全局 I/O 数据库锁 :设备附加操作使用
LockQueueIoDatabaseLock这个全局排队自旋锁保护,确保设备栈的修改是原子性的。这避免了并发附加导致的设备栈损坏。 -
状态检查 :在附加之前,代码检查目标设备是否处于
DO_DEVICE_INITIALIZING状态或正在被卸载/删除(DOE_UNLOAD_PENDING、DOE_DELETE_PENDING、DOE_REMOVE_PENDING、DOE_REMOVE_PROCESSED)。如果目标设备状态不正常,附加操作将失败返回NULL。
设备扩展(DEVOBJ_EXTENSION)的角色
在 ReactOS 中,每个设备对象实际上还有一个内部扩展结构 DEVOBJ_EXTENSION(或 EXTENDED_DEVOBJ_EXTENSION),它存储了设备对象管理器需要的元数据:
AttachedTo:记录本设备附加到哪个设备之上ExtensionFlags:包含DOE_START_PENDING、DOE_UNLOAD_PENDING等状态标志DeviceNode:指向 PnP 设备节点(DEVICE_NODE),用于 PnP 管理器追踪设备树
这个内部扩展与驱动通过 IoCreateDevice 分配的 DeviceExtension 是不同的。前者是 I/O 管理器内部使用的,后者是驱动私有数据的存储空间。
设备删除的安全机制
IoDeleteDevice 在删除设备对象时,需要确保:
- 设备已从设备栈中分离(
AttachedDevice链已断开) - 没有正在进行的 I/O 操作(
CurrentIrp为 NULL) - 设备对象的引用计数可以归零
在 IopDeleteDevice(device.c(file:///d:/reactos/ntoskrnl/io/iomgr/device.c))中,还会释放关联的 PnP 设备节点(IopFreeDeviceNode),并对驱动对象执行解引用(ObDereferenceObject(DriverObject)),因为 IoCreateDevice 时曾增加过驱动对象的引用计数。
9.1.C IRP 完成的深层机制
IopCompleteRequest:APC 级别的 IRP 完成
当 IoCompleteRequest 被调用时,IRP 的完成并非简单地唤醒等待线程那么简单。ReactOS 的实现(irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c))揭示了复杂的多阶段完成流程:
-
完成例程调用 :I/O 管理器从 IRP 的当前栈位置开始,逐层调用每个设备设置的完成例程(
CompletionRoutine)。每个完成例程可以检查 IRP 状态、修改数据、或决定是否继续向上传播。 -
APC 注入 :对于同步 I/O,IRP 完成后通过内核 APC(Asynchronous Procedure Call)机制通知等待线程。
IopCompleteRequest本身就是一个 APC 例程,它在目标线程的上下文中执行,负责:- 释放 IRP 关联的 MDL 链
- 释放系统缓冲区(如果设置了
IRP_DEALLOCATE_BUFFER标志) - 解引用文件对象和用户事件对象
- 处理重解析(
STATUS_REPARSE+IO_REPARSE_TAG_MOUNT_POINT)
-
重解析处理 :当文件系统返回
STATUS_REPARSE且重解析标签为IO_REPARSE_TAG_MOUNT_POINT时,IopCompleteRequest调用IopDoNameTransmogrify执行挂载点名称转换。这是 NTFS 挂载点和卷挂载的基础机制。
IRP 清理与线程解关联
IopDisassociateThreadIrp 处理一种特殊情况:当 IRP 在设备上停留过久(超过取消超时时间),I/O 管理器会将 IRP 从发起线程的 IrpList 中移除,使其"脱离"线程。这一操作包括:
- 将 IRP 的
Tail.Overlay.Thread设为 NULL - 从线程的
IrpList链表中移除 - 如果可能,向错误日志写入
IO_DRIVER_CANCEL_TIMEOUT事件
这一机制防止了"僵尸 IRP"导致线程无法终止的问题。
IRP 资源清理
IopCleanupIrp 负责 IRP 的最终资源释放:
c
// 释放 MDL 链
while ((Mdl = Irp->MdlAddress)) {
Irp->MdlAddress = Mdl->Next;
IoFreeMdl(Mdl);
}
// 释放系统缓冲区
if (Irp->Flags & IRP_DEALLOCATE_BUFFER)
ExFreePoolWithTag(Irp->AssociatedIrp.SystemBuffer, TAG_IOBUF);
// 解引用用户事件
if ((Irp->UserEvent) && !(Irp->Flags & IRP_SYNCHRONOUS_API) && (FileObject))
ObDereferenceObject(Irp->UserEvent);
// 解引用文件对象
if ((FileObject) && !(Irp->Flags & IRP_CREATE_OPERATION))
ObDereferenceObject(FileObject);
// 最终释放 IRP
IoFreeIrp(Irp);
注意 IRP_CREATE_OPERATION 的特殊处理:创建操作的 IRP 不解引用文件对象,因为文件对象的生命周期由文件系统的清理逻辑管理。
9.1.D Windows 与 ReactOS 驱动框架的对比分析
对象类型差异
在 Windows XP/2003 中,DRIVER_OBJECT 和 DEVICE_OBJECT 的结构比 ReactOS 头文件中展示的更为复杂。Windows 使用了更多的内部字段来支持:
- Verifier 集成:Windows 的 Driver Verifier 会在设备对象中插入验证层,拦截所有 IRP 并检查驱动的正确性。ReactOS 也实现了类似的机制,但覆盖范围较窄。
- Session 支持:Windows 的驱动对象包含会话(Session)相关的字段,用于支持终端服务(Terminal Services)中每个会话独立的驱动实例。ReactOS 目前不支持多会话。
- Power Framework :Windows 8+ 引入了 Power Framework(PoFx),用于更精细的设备电源管理。ReactOS 仍使用传统的
IRP_MJ_POWER模型。
IoCallDriver 的实现
ReactOS 的 IoCallDriver 实现为内联函数或快速调用函数(FASTCALL),其核心逻辑是:
- 保存当前栈位置指针
- 将
CurrentStackLocation递减(指向下一层设备的栈单元) - 通过
DeviceObject->DriverObject->MajorFunction[MajorFunction]查找派发函数 - 调用派发函数
在 Windows 中,IofCallDriver 是 IoCallDriver 的实际实现(IoCallDriver 是宏展开为 IofCallDriver)。ReactOS 保持了相同的命名约定。
关机通知机制
ReactOS 在 device.c 中实现了两阶段关机通知:
- Phase 0 :遍历
ShutdownListHead,向注册了关机通知的设备发送IRP_MJ_SHUTDOWN。这是"第一次机会"通知,允许文件系统和磁盘驱动刷新缓存。 - Phase 1 :遍历
LastChanceShutdownListHead,这是"最后机会"通知。在此阶段还会按顺序关闭磁盘文件系统(IopDiskFileSystemQueueHead)、光盘文件系统(IopCdRomFileSystemQueueHead)和磁带文件系统(IopTapeFileSystemQueueHead)。
这一两阶段设计与 Windows 完全一致,确保了系统关机时数据的完整性。
9.1.E 调试技巧与常见问题
使用 WinDbg 调试设备栈
在 ReactOS 调试环境中,以下 WinDbg 命令对设备驱动调试非常有用:
!devobj <地址>:显示设备对象的详细信息,包括设备扩展、标志、附加设备等!devstack <设备对象>:显示完整的设备栈,从 PDO 到最顶层的过滤设备!drvobj <驱动名>:显示驱动对象信息,包括设备链表和派发表!irp <地址>:显示 IRP 的详细状态,包括每个栈位置的主功能码和设备对象
常见驱动开发错误
-
忘记清除 DO_DEVICE_INITIALIZING :如果驱动在
DriverEntry或AddDevice中忘记清除DO_DEVICE_INITIALIZING标志,I/O 管理器将拒绝向该设备发送任何 IRP。这是最常见的初学者错误之一。 -
StackSize 不匹配 :如果驱动手动创建设备对象而不通过
IoCreateDevice(例如直接分配内存),StackSize可能不正确,导致 IRP 栈溢出或访问越界。 -
IRP 泄漏 :驱动在异步操作中必须确保每个 IRP最终都被
IoCompleteRequest完成。忘记完成 IRP 会导致发起线程永久挂起。 -
完成 IRP 后访问 :调用
IoCompleteRequest后,IRP 可能已经被释放(如果引用计数归零)。此后访问 IRP 的任何字段都是非法的,可能导致蓝屏。 -
设备删除顺序错误 :在卸载驱动时,必须先删除所有设备对象,再返回
STATUS_SUCCESS。如果遗留设备对象,IopDeleteDriver的断言将触发蓝屏。
9.1.F 十问为什么
1. 为什么 DRIVER_OBJECT 需要对象管理器来分配,而不是驱动自己 ExAllocatePool?
因为 DRIVER_OBJECT 需要受对象管理器的引用计数 保护。当文件系统或其他内核组件通过 ObReferenceObjectByName 获取驱动对象引用时,引用计数自动递增;归零时自动调用 IopDeleteDriver 执行清理。如果驱动自己分配内存,就无法实现这种自动生命周期管理,容易出现悬挂指针或内存泄漏。
2. 为什么 MajorFunction 数组要初始化为 IopInvalidDeviceRequest 而不是 NULL?
这是一个安全网设计 。如果数组初始化为 NULL,驱动忘记注册某个主功能码时,IRP 将触发空指针调用导致蓝屏。初始化为 IopInvalidDeviceRequest 后,未注册的 IRP 会以 STATUS_INVALID_DEVICE_REQUEST 被优雅拒绝,系统不会崩溃。
3. 为什么设备栈中 FDO 必须附加在 PDO 之上,而不是反过来?
因为 PDO 代表物理硬件 ,由总线驱动(如 pci.sys)创建,是设备栈的根基。FDO 代表功能逻辑,由功能驱动创建,负责解释硬件操作。IRP 从上层向下流动,先经过功能驱动处理逻辑,再到达总线驱动操作硬件------这符合"从抽象到具体"的分层原则。如果反过来,总线驱动将不得不处理所有上层逻辑,违反关注点分离。
4. 为什么 IoAttachDeviceToDeviceStack 要自动传播 StackSize?
因为 IRP 的 StackLocation[] 数组大小必须至少等于设备栈的深度 。每附加一层设备,IRP 就需要多一个 IO_STACK_LOCATION 来存储该层的参数和完成例程。如果 StackSize 不自动递增,上层驱动创建的 IRP 可能栈单元不够,导致下层设备访问越界内存,引发蓝屏。
5. 为什么 AlignmentRequirement 要从下层设备向上传播到上层?
因为上层驱动分配的缓冲区最终要传递给下层硬件。如果磁盘驱动要求 512 字节扇区对齐,而过滤驱动不知道这一点,分配了一个未对齐的缓冲区,DMA 传输时硬件将拒绝或产生数据损坏。自动传播确保整条设备栈使用最严格的对齐要求,避免硬件层面的错误。
6. 为什么 IRP 完成后不能再访问 IRP 的任何字段?
因为 IoCompleteRequest 会触发完成例程链,最终调用 IoFreeIrp 释放 IRP 内存。如果 IRP 的引用计数在完成后归零,内存会被立即回收或重用。此后访问 IRP 字段就是访问已释放内存(use-after-free),可能读到垃圾数据或触发蓝屏。这是驱动开发中最常见的错误之一。
7. 为什么 WDM 驱动要在 AddDevice 中创建设备,而不是在 DriverEntry 中?
因为 WDM 支持 PnP(即插即用) 。一个驱动可能管理多个设备实例(如多个 USB 存储设备),每个设备的 PDO 在插入时才由总线驱动创建。DriverEntry 只调用一次,无法预知会有多少设备。AddDevice 则每次 PnP 管理器发现新设备时都会被调用,驱动在此时为每个设备实例创建对应的 FDO 并附加到 PDO。
8. 为什么设备附加操作要使用全局排队自旋锁 LockQueueIoDatabaseLock?
因为设备栈是所有 I/O 操作的核心路径 。如果两个线程同时修改设备栈(例如一个附加过滤设备,另一个删除设备),没有锁保护会导致 AttachedDevice 指针混乱,IRP 流向错误的设备对象。使用全局排队自旋锁确保设备栈的修改是原子性的,维护设备树的完整性。
9. 为什么关机通知要分两阶段(Phase 0 和 Phase 1)?
因为不同组件的关机依赖关系不同。Phase 0 是"第一次机会",通知文件系统和磁盘驱动刷新缓存、写入待处理的数据。Phase 1 是"最后机会",在 Phase 0 完成后按顺序关闭文件系统(先磁盘、再光盘、再磁带)。两阶段设计确保文件系统在底层存储设备关闭之前有机会完成所有挂起的写操作,防止数据丢失。
10. 为什么忘记清除 DO_DEVICE_INITIALIZING 标志会导致设备无法工作?
因为 I/O 管理器在派发 IRP 之前会检查设备对象的标志位。如果 DO_DEVICE_INITIALIZING 仍然置位,I/O 管理器认为设备尚未准备好,会拒绝向该设备发送任何 IRP 。这是一个保护机制------防止设备在驱动还没完成初始化时就收到请求。但很多初学者在 DriverEntry 或 AddDevice 末尾忘记清除这个标志,导致设备"创建了但永远不响应"。
本章代码索引
| 文件 | 内容 |
|---|---|
| iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) | DRIVER_OBJECT、DEVICE_OBJECT、IRP、IO_STACK_LOCATION 定义 |
| driver.c(file:///d:/reactos/ntoskrnl/io/iomgr/driver.c) | IoCreateDriver、IoAllocateDriverObjectExtension |
| device.c(file:///d:/reactos/ntoskrnl/io/iomgr/device.c) | IoCreateDevice、IoDeleteDevice、IoAttachDeviceToDeviceStack |
| irp.c(file:///d:/reactos/ntoskrnl/io/iomgr/irp.c) | IoAllocateIrp、IoFreeIrp、IoCompleteRequest |
| dispatch.c(file:///d:/reactos/ntoskrnl/io/iomgr/dispatch.c) | IoCallDriver、IoSkipCurrentIrpStackLocation |
| loader.c(file:///d:/reactos/ntoskrnl/io/iomgr/loader.c) | IopLoadDriverImage、NtLoadDriver |
| pnpmgr(file:///d:/reactos/ntoskrnl/io/pnpmgr/) | PnP 管理器 |
| beep.c(file:///d:/reactos/drivers/base/beep/beep.c) | 经典老式驱动实例 |