第 9 章 设备驱动 --- 9.8 设备驱动模块的装载
本节深入剖析 Windows NT 设备驱动模块的装载过程。 驱动的装载涉及 服务控制管理器(SCM)→ 内核 I/O 管理器 → PE 加载器 → 驱动入口(DriverEntry) 的协作。ReactOS 的 SCM 实现在 base/system/services/(file:///d:/reactos/base/system/services/),内核驱动加载在 ntoskrnl/io/iomgr/loader.c(file:///d:/reactos/ntoskrnl/io/iomgr/loader.c),PE 加载在 ntoskrnl/ldr/ldrpe.c(file:///d:/reactos/ntoskrnl/ldr/ldrpe.c)。理解驱动装载的关键是把握 注册表驱动数据库、PE 映射、安全验证、DriverEntry 调用 这四个阶段。
概述
驱动装载流程可以分解为五个阶段:
- SCM 阶段 :
services.exe读取注册表HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName>决定启动类型 - NtLoadDriver 阶段 :SCM 通过 LPC 端口调用
NtLoadDriver,把驱动注册表键路径传入 - PE 加载阶段 :
IopLoadDriverImage调用LdrLoadDriver映射 PE 文件到内核空间 - 入口调用阶段 :I/O 管理器调用
DriverEntry入口 - 依赖与初始化 :处理
DependOnGroup、DependOnService等待依赖启动
本节内容概览
- 9.8.0 框架图
- 9.8.1 服务控制管理器(SCM)架构
- 9.8.2 注册表中的服务数据库
- 9.8.3
NtLoadDriver系统调用 - 9.8.4
IopLoadDriverImage完整流程 - 9.8.5 PE 映射与重定位
- 9.8.6 DriverEntry 调用与 DriverObject 初始化
- 9.8.7 PnP 驱动的自动启动
- 9.8.8 总结与代码索引
学习目标
- 理解 SCM 与内核的协作
- 掌握 NtLoadDriver 的完整流程
- 知道 PE 在内核中的映射机制
- 区分手动装载与 PnP 自动装载
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| SCM | base/system/services/database.c(file:///d:/reactos/base/system/services/database.c) | 服务数据库 |
| SCM 派发 | base/system/services/dispatch.c(file:///d:/reactos/base/system/services/dispatch.c) | SCM RPC 接口 |
| 事件日志 | base/system/services/eventlog.c(file:///d:/reactos/base/system/services/eventlog.c) | 服务事件 |
| 内核加载器 | ntoskrnl/io/iomgr/loader.c(file:///d:/reactos/ntoskrnl/io/iomgr/loader.c) | IopLoadDriverImage、NtLoadDriver |
| PE 加载 | ntoskrnl/ldr/ldrpe.c(file:///d:/reactos/ntoskrnl/ldr/ldrpe.c) | LdrpLoadPeImage、LdrpMapPeImage |
| LDR | ntoskrnl/ldr/ldr.c(file:///d:/reactos/ntoskrnl/ldr/ldr.c) | LdrLoadDriver、LdrUnloadDriver |
| SCM LPC | base/system/services/rpcserver.c(file:///d:/reactos/base/system/services/rpcserver.c) | SCM RPC 接口 |
| 启动驱动 | base/system/smss/(file:///d:/reactos/base/system/smss/) | 启动早期驱动 |
9.8.0 框架图
+----------------------+
| 应用或 Services.exe |
| (调用 StartService) |
+----------------------+
|
v
+----------------------+
| SCM 核心 (services.exe) |
| 1. 读注册表决定启动方式 |
| 2. 依赖检查 |
| 3. 状态记录 |
+----------------------+
| (LPC/RPC)
v
+----------------------+
| NtLoadDriver(注册表路径) |
+----------------------+
|
v
+----------------------+
| IopLoadDriverImage |
| 1. 打开注册表键 |
| 2. 读 ImagePath |
| 3. 调用 PE 加载器 |
| 4. 调用 DriverEntry |
+----------------------+
|
v
+----------------------+
| LdrLoadDriver |
| 1. ZwOpenFile |
| 2. ZwCreateSection |
| 3. ZwMapViewOfSection |
| 4. 处理导入表 |
| 5. 重定位 |
+----------------------+
|
v
+----------------------+
| DriverEntry(DriverObject, RegistryPath) |
+----------------------+
|
v
+----------------------+
| 驱动初始化 |
| 1. IoCreateDevice |
| 2. 注册 PnP/AddDevice|
| 3. Unload 例程 |
+----------------------+
9.8.1 服务控制管理器(SCM)架构
SCM 是 Windows 中 驱动和服务启动 的中心管理器。它运行在 services.exe 进程内,负责:
- 服务数据库 :读取
HKLM\SYSTEM\CurrentControlSet\Services - 启动顺序 :根据
DependOnGroup、DependOnService决定启动顺序 - 服务 RPC :暴露
SvcCtrl_*API 给其他进程 - 状态机:管理服务的运行状态(STOPPED、START_PENDING、RUNNING、STOP_PENDING)
SCM 启动流程
Smss 启动 services.exe
|
v
services.exe 初始化
|
v
读 HKLM\SYSTEM\CurrentControlSet\Services
|
v
对每个标记为 "SERVICE_AUTO_START" 的服务:
1. 检查依赖
2. 启动依赖
3. 启动服务
SCM 关键进程函数
| 函数 | 文件 | 作用 |
|---|---|---|
ServiceInit |
scm.c(file:///d:/reactos/base/system/services/scm.c) | SCM 主入口 |
ScmStartService |
dispatch.c(file:///d:/reactos/base/system/services/dispatch.c) | 启动服务 |
ScmControlService |
dispatch.c(file:///d:/reactos/base/system/services/dispatch.c) | 发送控制码 |
ScmDeleteService |
database.c(file:///d:/reactos/base/system/services/database.c) | 删除服务 |
ScmCreateService |
database.c(file:///d:/reactos/base/system/services/database.c) | 创建服务 |
9.8.2 注册表中的服务数据库
服务配置存放在 HKLM\SYSTEM\CurrentControlSet\Services\ 下,每个子键代表一个服务:
HKLM\SYSTEM\CurrentControlSet\Services\Beep
├── Type = 0x1 (SERVICE_KERNEL_DRIVER)
├── Start = 0x0 (SERVICE_BOOT_START) | 0x1 (SERVICE_SYSTEM_START) | 0x2 (SERVICE_AUTO_START) | 0x3 (SERVICE_DEMAND_START) | 0x4 (SERVICE_DISABLED)
├── ErrorControl = 0x1 (SERVICE_ERROR_NORMAL)
├── ImagePath = "\??\C:\ReactOS\system32\drivers\beep.sys"
├── Group = "Base"
├── Tag = 0x1
├── DependOnGroup = ...
├── DependOnService = ...
├── ObjectName = "\Driver\Beep"
├── DisplayName = "Beep"
└── Parameters
└── ...
关键字段
| 字段 | 含义 |
|---|---|
Type |
SERVICE_KERNEL_DRIVER (1) / SERVICE_FILE_SYSTEM_DRIVER (2) / SERVICE_WIN32_OWN_PROCESS (16) |
Start |
启动类型 |
ImagePath |
驱动文件路径 |
Group |
启动组("Base"、"Boot Bus Extender" 等) |
DependOnGroup |
依赖的组(多字符串) |
DependOnService |
依赖的服务(多字符串) |
Tag |
同组内的启动顺序 |
ObjectName |
驱动的 NT 路径(\Driver\Beep) |
启动类型
| 值 | 名称 | 启动时机 |
|---|---|---|
| 0 | SERVICE_BOOT_START |
内核初始化时(最早期) |
| 1 | SERVICE_SYSTEM_START |
系统初始化时 |
| 2 | SERVICE_AUTO_START |
系统启动后 |
| 3 | SERVICE_DEMAND_START |
手动启动 |
| 4 | SERVICE_DISABLED |
禁用 |
启动顺序
系统按以下顺序启动驱动:
- Boot Start(0) :内核初始化阶段,
IopInitializeBootDrivers - System Start(1):在 PnP 管理器启动后
- Auto Start(2):SCM 自动启动
- Demand Start(3):按需启动
同组内按 Tag 排序,跨组按组名排序。
9.8.3 NtLoadDriver 系统调用
NtLoadDriver 是 SCM 与内核之间的接口:
c
NTSTATUS NTAPI
NtLoadDriver(IN PUNICODE_STRING DriverServiceName);
参数 DriverServiceName 是注册表键路径,例如 \\Registry\\Machine\\System\\CurrentControlSet\\Services\\Beep。
完整流程
c
NTSTATUS NTAPI
NtLoadDriver(IN PUNICODE_STRING DriverServiceName)
{
NTSTATUS Status;
HANDLE KeyHandle;
UNICODE_STRING ServiceName;
/* 1. 验证 PreviousMode(必须从内核态调用) */
if (KeGetPreviousMode() == UserMode)
{
/* 验证指针 */
_SEH2_TRY
{
ProbeForRead(DriverServiceName, sizeof(UNICODE_STRING), sizeof(UCHAR));
ServiceName = *DriverServiceName;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode();
}
_SEH2_END;
}
/* 2. 打开注册表键 */
InitializeObjectAttributes(&ObjectAttributes,
&ServiceName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL, NULL);
Status = ZwOpenKey(&KeyHandle, KEY_READ, &ObjectAttributes);
if (!NT_SUCCESS(Status)) return Status;
/* 3. 加载驱动 */
Status = IopLoadDriverImage(KeyHandle, FALSE);
/* 4. 关闭键 */
ZwClose(KeyHandle);
return Status;
}
注意 NtLoadDriver 是 特权调用 ,只能从内核态调用,所以 services.exe 通过 LPC 端口调用。
SCM 端的调用
c
/* services.exe 调 NtLoadDriver */
UNICODE_STRING DriverServiceName;
RtlInitUnicodeString(&DriverServiceName, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Beep");
NtLoadDriver(&DriverServiceName);
9.8.4 IopLoadDriverImage 完整流程
定义于 ntoskrnl/io/iomgr/loader.c(file:///d:/reactos/ntoskrnl/io/iomgr/loader.c):
c
NTSTATUS NTAPI
IopLoadDriverImage(
IN HANDLE KeyHandle,
IN BOOLEAN AlreadyCreated)
{
NTSTATUS Status;
PKEY_VALUE_FULL_INFORMATION KeyValueInfo;
UNICODE_STRING ImagePath;
PDRIVER_OBJECT DriverObject;
PVOID ImageBase;
ULONG ImageSize;
PIMAGE_NT_HEADERS NtHeaders;
PDRIVER_INITIALIZE DriverEntry;
/* 1. 读 ImagePath */
Status = IopGetDriverImagePath(KeyHandle, &ImagePath);
if (!NT_SUCCESS(Status)) return Status;
/* 2. 加载 PE 文件 */
Status = LdrLoadDriver(&ImagePath, &ImageBase, &ImageSize);
if (!NT_SUCCESS(Status)) return Status;
/* 3. 验证 PE 头 */
NtHeaders = RtlImageNtHeader(ImageBase);
if (!NtHeaders) return STATUS_INVALID_IMAGE_FORMAT;
/* 4. 找 DriverEntry 入口 */
DriverEntry = (PDRIVER_INITIALIZE)RtlImageDirectoryEntryToData(ImageBase, TRUE,
IMAGE_DIRECTORY_ENTRY_EXPORT,
&Size);
/* 对于驱动,DriverEntry 是可选的,默认为 DriverEntry */
if (!DriverEntry)
{
/* 没有 export,驱动加载失败 */
return STATUS_INVALID_IMAGE_FORMAT;
}
/* 5. 创建 DriverObject(DRIVER_OBJECT 由 I/O 管理器分配) */
Status = IopCreateDriverObject(&DriverObject, KeyHandle);
if (!NT_SUCCESS(Status)) return Status;
/* 6. 调用 DriverEntry */
Status = DriverEntry(DriverObject, NULL); // RegistryPath 可选
if (!NT_SUCCESS(Status))
{
/* 初始化失败,清理 */
IopDeleteDriverObject(DriverObject);
return Status;
}
/* 7. 关联 DriverObject 和 ImageBase */
DriverObject->DriverStart = ImageBase;
DriverObject->DriverSize = ImageSize;
return STATUS_SUCCESS;
}
关键点
- ImagePath 读取 :从注册表
ImagePath键读 - PE 加载 :调用
LdrLoadDriver映射 PE 到内核空间 - DriverObject 分配:由 I/O 管理器创建
- DriverEntry 调用 :在 PnP 子系统中可被
AddDevice替换
9.8.5 PE 映射与重定位
PE 加载由 ntoskrnl/ldr/ldrpe.c(file:///d:/reactos/ntoskrnl/ldr/ldrpe.c) 实现。
流程
c
NTSTATUS NTAPI
LdrLoadDriver(IN PUNICODE_STRING ImagePath,
OUT PVOID *ImageBase,
OUT PULONG ImageSize)
{
NTSTATUS Status;
HANDLE FileHandle, SectionHandle;
PVOID BaseAddress = NULL;
SIZE_T ViewSize = 0;
PIMAGE_NT_HEADERS NtHeaders;
/* 1. 打开文件 */
Status = ZwOpenFile(&FileHandle, FILE_READ_DATA, &ObjectAttributes, &IoStatus,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(Status)) return Status;
/* 2. 创建 Section */
Status = ZwCreateSection(&SectionHandle, SECTION_MAP_READ | SECTION_MAP_EXECUTE,
NULL, NULL, PAGE_READONLY, SEC_IMAGE, FileHandle);
ZwClose(FileHandle);
if (!NT_SUCCESS(Status)) return Status;
/* 3. 映射到内核空间 */
BaseAddress = NULL;
ViewSize = 0;
Status = ZwMapViewOfSection(SectionHandle, NtCurrentProcess(),
&BaseAddress, 0, 0, NULL,
&ViewSize, ViewUnmap, 0, PAGE_READWRITE);
ZwClose(SectionHandle);
if (!NT_SUCCESS(Status)) return Status;
/* 4. 检查 PE 头 */
NtHeaders = RtlImageNtHeader(BaseAddress);
if (!NtHeaders) { ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress); return STATUS_INVALID_IMAGE_FORMAT; }
/* 5. 处理导入表(解析 IAT) */
Status = LdrpProcessImports(BaseAddress);
if (!NT_SUCCESS(Status)) { ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress); return Status; }
/* 6. 处理重定位 */
Status = LdrpProcessRelocations(BaseAddress);
if (!NT_SUCCESS(Status)) { ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress); return Status; }
*ImageBase = BaseAddress;
*ImageSize = ViewSize;
return STATUS_SUCCESS;
}
关键步骤
- ZwCreateSection with SEC_IMAGE:Windows PE loader 标记,会自动按节属性映射
- ZwMapViewOfSection:映射到虚拟地址空间
- 导入表处理 :解析
IMAGE_DIRECTORY_ENTRY_IMPORT,加载依赖 DLL - 重定位处理 :应用
.reloc节中的修复
导入表处理
c
static NTSTATUS NTAPI
LdrpProcessImports(PVOID BaseAddress)
{
PIMAGE_IMPORT_DESCRIPTOR ImportDesc;
ULONG Size;
/* 找导入表 */
ImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
RtlImageDirectoryEntryToData(BaseAddress, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &Size);
if (!ImportDesc) return STATUS_SUCCESS;
/* 遍历每个导入 DLL */
while (ImportDesc->Name)
{
PSTR DllName = (PSTR)((PUCHAR)BaseAddress + ImportDesc->Name);
PIMAGE_THUNK_DATA Thunk;
PIMAGE_THUNK_DATA NameThunk = (PIMAGE_THUNK_DATA)((PUCHAR)BaseAddress + ImportDesc->OriginalFirstThunk);
PIMAGE_THUNK_DATA FunctionThunk = (PIMAGE_THUNK_DATA)((PUCHAR)BaseAddress + ImportDesc->FirstThunk);
/* 加载依赖 DLL */
PVOID DllBase;
NTSTATUS Status = LdrpLoadDll(DllName, &DllBase);
if (!NT_SUCCESS(Status)) return Status;
/* 解析每个函数 */
while (NameThunk->u1.AddressOfData)
{
PIMAGE_IMPORT_BY_NAME Import = (PIMAGE_IMPORT_BY_NAME)((PUCHAR)BaseAddress + NameThunk->u1.AddressOfData);
/* 找函数地址 */
FARPROC Function = LdrpGetProcAddress(DllBase, Import->Name);
if (!Function) return STATUS_PROCEDURE_NOT_FOUND;
/* 写 IAT */
FunctionThunk->u1.Function = (ULONG_PTR)Function;
NameThunk++;
FunctionThunk++;
}
ImportDesc++;
}
return STATUS_SUCCESS;
}
重定位处理
驱动映像通常无法在期望的基址(ImageBase)加载,PE 提供 .reloc 节描述修复:
c
static NTSTATUS NTAPI
LdrpProcessRelocations(PVOID BaseAddress)
{
PIMAGE_BASE_RELOCATION Reloc = RtlImageDirectoryEntryToData(BaseAddress, TRUE, IMAGE_DIRECTORY_ENTRY_BASERELOC, &Size);
PIMAGE_NT_HEADERS NtHeaders = RtlImageNtHeader(BaseAddress);
ULONG_PTR Delta = (ULONG_PTR)BaseAddress - NtHeaders->OptionalHeader.ImageBase;
while (Reloc->VirtualAddress)
{
PUSHORT Fixup = (PUSHORT)(Reloc + 1);
ULONG Count = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
for (; Count; Count--, Fixup++)
{
USHORT Type = (*Fixup) >> 12;
USHORT Offset = (*Fixup) & 0xFFF;
PULONG_PTR PatchAddress = (PULONG_PTR)((PUCHAR)BaseAddress + Reloc->VirtualAddress + Offset);
switch (Type)
{
case IMAGE_REL_BASED_DIR64: // x64
*PatchAddress += Delta;
break;
case IMAGE_REL_BASED_HIGHLOW: // x86
*(PULONG)PatchAddress += (ULONG)Delta;
break;
case IMAGE_REL_BASED_ABSOLUTE: // 跳过
break;
}
}
Reloc = (PIMAGE_BASE_RELOCATION)((PUCHAR)Reloc + Reloc->SizeOfBlock);
}
return STATUS_SUCCESS;
}
9.8.6 DriverEntry 调用与 DriverObject 初始化
IopCreateDriverObject
c
static NTSTATUS NTAPI
IopCreateDriverObject(
OUT PDRIVER_OBJECT *DriverObject,
IN HANDLE KeyHandle)
{
NTSTATUS Status;
UNICODE_STRING DriverName;
OBJECT_ATTRIBUTES ObjectAttributes;
PDRIVER_OBJECT NewDriverObject;
/* 1. 从注册表读 \Driver\<Name> */
Status = IopGetDriverName(KeyHandle, &DriverName);
if (!NT_SUCCESS(Status)) return Status;
/* 2. 创建 DriverObject */
InitializeObjectAttributes(&ObjectAttributes,
&DriverName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL, NULL);
Status = ObCreateObject(KernelMode, IoDriverObjectType,
&ObjectAttributes, KernelMode,
NULL, sizeof(DRIVER_OBJECT),
0, 0, (PVOID*)&NewDriverObject);
if (!NT_SUCCESS(Status))
{
ExFreePoolWithTag(DriverName.Buffer, 'IopD');
return Status;
}
/* 3. 初始化 DriverObject 字段 */
RtlZeroMemory(NewDriverObject, sizeof(DRIVER_OBJECT));
NewDriverObject->Type = IO_TYPE_DRIVER;
NewDriverObject->Size = sizeof(DRIVER_OBJECT);
NewDriverObject->DriverInit = NULL; // 由调用者设置
NewDriverObject->HardwareDatabase = &CmRegistryMachineHardwareDescriptionSystemName;
/* 4. 插入对象目录 */
Status = ObInsertObject(NewDriverObject, NULL, FILE_READ_DATA,
0, NULL, NULL);
*DriverObject = NewDriverObject;
return Status;
}
DriverEntry 调用
c
/* 1. 设置 DriverInit 字段 */
DriverObject->DriverInit = DriverEntry;
/* 2. 调用 DriverEntry */
Status = DriverEntry(DriverObject, NULL);
/* 3. 检查结果 */
if (NT_SUCCESS(Status))
{
/* 成功:清空 DriverInit 防止重复调用 */
DriverObject->DriverInit = NULL;
}
else
{
/* 失败:删除驱动对象 */
ObMakeTemporaryObject(DriverObject);
ObDereferenceObject(DriverObject);
}
PnP 子系统的特殊处理
在 PnP 子系统中,IopLoadDriverImage 调用 DriverEntry 后:
c
/* PnP 系统的 DriverEntry 通常是 IopInitializePnpDriver 桩 */
NTSTATUS NTAPI
IopInitializePnpDriver(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
/* PnP 驱动有 AddDevice 入口 */
PnpDriverEntry(DriverObject, RegistryPath);
return STATUS_SUCCESS;
}
9.8.7 PnP 驱动的自动启动
不同于老式驱动的 NtLoadDriver,PnP 驱动的启动是 事件驱动 的:
PnP 管理器
|
v
IopEnumerateDevice: 启动阶段
|
v
调用所有 SERVICE_BOOT_START 驱动的 DriverEntry
|
v
PnP 设备枚举 (PCI 扫描 USB 设备)
|
v
对每个枚举出的设备:
1. 根据 HardwareID 查注册表找对应驱动
2. 加载该驱动
3. 调用 AddDevice
PnP 驱动加载
c
NTSTATUS NTAPI
IopLoadPnpDriver(IN HANDLE ServiceHandle)
{
/* 与 NtLoadDriver 类似,但走 PnP 路径 */
...
}
PnP 驱动调用流程
IopProcessNewResources -> IopReallocateResources
|
v
IopProcessStartDevice: 发送 IRP_MN_START_DEVICE
|
v
驱动 StartDevice 处理:分配资源、初始化硬件
9.8.7.1 驱动装载深度机制剖析
ReactOS的驱动装载机制涉及多个内核子系统的协作,本节深入分析驱动装载过程中的关键技术细节,包括依赖解析、安全验证、启动序列优化等。
服务控制管理器内部实现
ReactOS的SCM实现在base/system/services/目录,核心文件包括scm.c、database.c和dispatch.c。SCM采用多线程架构,主线程负责RPC监听,工作线程负责服务启动和管理。
SCM数据库结构
SCM维护一个内存中的服务数据库,从注册表HKLM\SYSTEM\CurrentControlSet\Services加载:
c
typedef struct _SERVICE_RECORD {
LPWSTR ServiceName; // 服务名称
LPWSTR DisplayName; // 显示名称
DWORD ServiceType; // 服务类型
DWORD StartType; // 启动类型
DWORD ErrorControl; // 错误控制
LPWSTR ImagePath; // 可执行文件路径
LPWSTR LoadOrderGroup; // 加载顺序组
DWORD TagId; // 组内标签
LPWSTR *Dependencies; // 依赖服务列表
LPWSTR *DependOnGroup; // 依赖组列表
LPWSTR AccountName; // 运行账户
DWORD CurrentState; // 当前状态
DWORD ControlsAccepted; // 接受的控制码
DWORD Win32ExitCode; // Win32退出码
DWORD ServiceSpecificExitCode;// 服务特定退出码
HANDLE ProcessHandle; // 进程句柄
DWORD ProcessId; // 进程ID
} SERVICE_RECORD, *PSERVICE_RECORD;
SCM在启动时遍历注册表中的所有服务键,为每个服务创建SERVICE_RECORD结构,并建立依赖关系图。
依赖解析算法
SCM使用拓扑排序算法解析服务依赖关系。依赖解析的核心逻辑在database.c的ScmResolveDependencies函数中:
c
BOOL ScmResolveDependencies(PSERVICE_RECORD Service)
{
DWORD i;
PSERVICE_RECORD DepService;
// 检查是否已处理
if (Service->dwFlags & SERVICE_FLAG_PROCESSED)
return TRUE;
// 标记为处理中
Service->dwFlags |= SERVICE_FLAG_PROCESSING;
// 递归解析依赖的服务
if (Service->lpDependencies)
{
for (i = 0; Service->lpDependencies[i]; i++)
{
DepService = ScmFindServiceRecord(Service->lpDependencies[i]);
if (!DepService)
{
// 依赖服务不存在
ScmLogEvent(EVENTLOG_ERROR_TYPE,
EVENT_DEPENDENCY_NOT_FOUND,
Service->lpServiceName,
Service->lpDependencies[i]);
return FALSE;
}
// 递归解析
if (!ScmResolveDependencies(DepService))
return FALSE;
}
}
// 解析依赖的组
if (Service->lpDependOnGroup)
{
for (i = 0; Service->lpDependOnGroup[i]; i++)
{
if (!ScmResolveGroupDependencies(Service->lpDependOnGroup[i]))
return FALSE;
}
}
// 标记为已处理
Service->dwFlags &= ~SERVICE_FLAG_PROCESSING;
Service->dwFlags |= SERVICE_FLAG_PROCESSED;
return TRUE;
}
依赖解析算法的关键特性:
- 循环依赖检测 : 通过
SERVICE_FLAG_PROCESSING标志检测循环依赖 - 组依赖展开: 将组依赖展开为具体服务依赖
- 错误恢复: 依赖失败时记录事件日志但不立即终止
加载顺序组(Load Order Group)
Windows使用加载顺序组来控制服务的启动顺序。ReactOS支持以下标准组:
| 组名 | 启动阶段 | 典型服务 |
|---|---|---|
Boot Bus Extender |
最早期 | PCI、ACPI总线驱动 |
Boot Start |
启动早期 | 磁盘、文件系统驱动 |
System Start |
系统初始化 | PnP管理器 |
Base |
基础服务 | 即插即用服务 |
Filter |
过滤驱动 | 文件系统过滤 |
Extended Start |
扩展启动 | 网络、显示驱动 |
MS_WindowsIcon |
最后阶段 | 用户界面服务 |
SCM按照HKLM\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder中定义的顺序启动各组。
驱动装载安全验证
ReactOS在内核层面实施了多项安全验证,防止恶意或损坏的驱动被加载。
数字签名验证
虽然ReactOS目前不强制要求驱动签名,但实现了签名验证框架:
c
NTSTATUS IopVerifyDriverSignature(
IN PUNICODE_STRING ImagePath,
IN HANDLE FileHandle)
{
NTSTATUS Status;
PIMAGE_NT_HEADERS NtHeaders;
PIMAGE_SECURITY_DIRECTORY SecurityDir;
// 获取PE头
NtHeaders = RtlImageNtHeader(ImageBase);
if (!NtHeaders)
return STATUS_INVALID_IMAGE_FORMAT;
// 检查安全目录
SecurityDir = &NtHeaders->OptionalHeader.DataDirectory[
IMAGE_DIRECTORY_ENTRY_SECURITY];
if (SecurityDir->VirtualAddress == 0)
{
// 无签名
if (IopRequireSignedDrivers)
return STATUS_INVALID_IMAGE_HASH;
return STATUS_SUCCESS;
}
// 验证签名(简化示例)
Status = IopValidateAuthenticodeSignature(
ImageBase,
SecurityDir->VirtualAddress,
SecurityDir->Size);
return Status;
}
PE映像验证
LdrLoadDriver在映射PE映像时执行严格的验证:
c
NTSTATUS LdrpValidatePeImage(PVOID BaseAddress)
{
PIMAGE_DOS_HEADER DosHeader;
PIMAGE_NT_HEADERS NtHeaders;
// 验证DOS头
DosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
return STATUS_INVALID_IMAGE_FORMAT;
// 验证PE头
NtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)BaseAddress +
DosHeader->e_lfanew);
if (NtHeaders->Signature != IMAGE_NT_SIGNATURE)
return STATUS_INVALID_IMAGE_FORMAT;
// 验证机器类型
if (NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_I386 &&
NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64)
return STATUS_INVALID_IMAGE_FORMAT;
// 验证是否为DLL或驱动
if (!(NtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL))
return STATUS_INVALID_IMAGE_FORMAT;
// 验证节区对齐
if (NtHeaders->OptionalHeader.SectionAlignment <
NtHeaders->OptionalHeader.FileAlignment)
return STATUS_INVALID_IMAGE_FORMAT;
// 验证入口点
if (NtHeaders->OptionalHeader.AddressOfEntryPoint == 0)
return STATUS_INVALID_IMAGE_FORMAT;
return STATUS_SUCCESS;
}
启动优化技术
ReactOS实现了多种启动优化技术,加快驱动装载速度。
并行驱动加载
对于无依赖关系的BOOT_START驱动,ReactOS支持并行加载:
c
VOID IopLoadBootDriversParallel(VOID)
{
HANDLE ThreadHandles[MAX_BOOT_THREADS];
DWORD ThreadCount = 0;
PLIST_ENTRY Entry;
PBOOT_DRIVER_RECORD Driver;
// 按组分类驱动
for (Entry = BootDriverList.Flink;
Entry != &BootDriverList;
Entry = Entry->Flink)
{
Driver = CONTAINING_RECORD(Entry, BOOT_DRIVER_RECORD, ListEntry);
// 检查依赖
if (IopCanLoadDriverNow(Driver))
{
// 创建加载线程
ThreadHandles[ThreadCount] = CreateThread(
NULL, 0,
IopLoadDriverThread,
Driver, 0, NULL);
ThreadCount++;
if (ThreadCount >= MAX_BOOT_THREADS)
break;
}
}
// 等待所有线程完成
WaitForMultipleObjects(ThreadCount, ThreadHandles, TRUE, INFINITE);
// 关闭句柄
for (DWORD i = 0; i < ThreadCount; i++)
CloseHandle(ThreadHandles[i]);
}
驱动预加载
SCM在系统空闲时预加载常用驱动到内存:
c
VOID ScmPrefetchDrivers(VOID)
{
HANDLE PrefetchListHandle;
WCHAR DriverPath[MAX_PATH];
HANDLE FileHandle;
// 读取预加载列表
PrefetchListHandle = IopOpenPrefetchList();
if (!PrefetchListHandle)
return;
// 遍历预加载列表
while (IopReadPrefetchEntry(PrefetchListHandle, DriverPath))
{
// 打开驱动文件
if (NT_SUCCESS(ZwOpenFile(&FileHandle, FILE_READ_DATA,
&ObjectAttributes, &IoStatusBlock,
FILE_SHARE_READ, 0)))
{
// 预读到内存
IopPrefetchFile(FileHandle);
ZwClose(FileHandle);
}
}
ZwClose(PrefetchListHandle);
}
驱动卸载机制
驱动卸载是装载的逆过程,需要仔细处理资源释放和依赖关系。
ZwUnloadDriver系统调用
c
NTSTATUS NTAPI ZwUnloadDriver(IN PUNICODE_STRING DriverServiceName)
{
NTSTATUS Status;
HANDLE KeyHandle;
PDRIVER_OBJECT DriverObject;
// 打开注册表键
Status = IopOpenDriverRegistryKey(DriverServiceName, &KeyHandle);
if (!NT_SUCCESS(Status))
return Status;
// 查找驱动对象
Status = IopFindDriverObject(KeyHandle, &DriverObject);
if (!NT_SUCCESS(Status))
{
ZwClose(KeyHandle);
return Status;
}
// 检查是否可以卸载
if (!IopCanUnloadDriver(DriverObject))
{
ZwClose(KeyHandle);
return STATUS_UNSATISFIED_DEPENDENCIES;
}
// 调用驱动卸载例程
if (DriverObject->DriverUnload)
{
Status = DriverObject->DriverUnload(DriverObject);
if (!NT_SUCCESS(Status))
{
ZwClose(KeyHandle);
return Status;
}
}
// 卸载PE映像
Status = LdrUnloadDriver(DriverObject->DriverStart);
// 删除驱动对象
ObMakeTemporaryObject(DriverObject);
ObDereferenceObject(DriverObject);
ZwClose(KeyHandle);
return Status;
}
卸载安全检查
驱动卸载前必须通过多项检查:
c
BOOLEAN IopCanUnloadDriver(PDRIVER_OBJECT DriverObject)
{
PDEVICE_OBJECT DeviceObject;
// 检查是否有打开的设备对象
DeviceObject = DriverObject->DeviceObject;
while (DeviceObject)
{
if (DeviceObject->ReferenceCount > 0)
return FALSE; // 有引用,不能卸载
if (DeviceObject->Flags & DO_DEVICE_UNLOADABLE)
return FALSE; // 标记为不可卸载
DeviceObject = DeviceObject->NextDevice;
}
// 检查是否有依赖服务
if (IopHasDependentServices(DriverObject))
return FALSE;
// 检查是否有活动的IRP
if (IopHasActiveIrps(DriverObject))
return FALSE;
return TRUE;
}
错误处理与恢复
驱动装载失败时的错误处理策略取决于ErrorControl设置:
c
VOID IopHandleDriverLoadFailure(
IN PUNICODE_STRING DriverName,
IN NTSTATUS Status,
IN DWORD ErrorControl)
{
switch (ErrorControl)
{
case SERVICE_ERROR_IGNORE:
// 忽略错误,继续启动
IopLogDriverEvent(EVENTLOG_INFORMATION_TYPE,
EVENT_DRIVER_LOAD_IGNORED,
DriverName, Status);
break;
case SERVICE_ERROR_NORMAL:
// 记录错误,尝试继续
IopLogDriverEvent(EVENTLOG_WARNING_TYPE,
EVENT_DRIVER_LOAD_FAILED,
DriverName, Status);
IopDisableService(DriverName);
break;
case SERVICE_ERROR_SEVERE:
// 严重错误,尝试使用LastKnownGood配置
IopLogDriverEvent(EVENTLOG_ERROR_TYPE,
EVENT_DRIVER_LOAD_SEVERE,
DriverName, Status);
if (!IopSwitchToLastKnownGood())
IopBugCheck(DRIVER_LOAD_FAILURE,
(ULONG_PTR)DriverName, Status, 0);
break;
case SERVICE_ERROR_CRITICAL:
// 致命错误,立即蓝屏
IopLogDriverEvent(EVENTLOG_ERROR_TYPE,
EVENT_DRIVER_LOAD_CRITICAL,
DriverName, Status);
IopBugCheck(DRIVER_LOAD_FAILURE,
(ULONG_PTR)DriverName, Status, 0);
break;
}
}
与Windows驱动装载的对比
ReactOS的驱动装载机制与Windows基本兼容,但存在一些差异:
| 特性 | Windows | ReactOS |
|---|---|---|
| 驱动签名强制 | Vista+强制要求 | 可选,默认不强制 |
| 并行加载 | 支持多线程并行 | 支持,但优化较少 |
| 预加载优化 | 超级预取(Superfetch) | 基本预取 |
| 快速启动 | 混合启动(Hybrid Boot) | 未实现 |
| 驱动隔离 | 用户模式驱动框架(UMDF) | 未实现 |
| 热补丁加载 | 支持热补丁 | 未实现 |
| 早期启动反恶意软件 | ELAM支持 | 未实现 |
尽管如此,ReactOS的驱动装载机制已经能够支持大多数标准WDM和KMDF驱动,为设备驱动开发提供了完整的运行时环境。
9.8.8 十问为什么
1. 为什么驱动装载需要 SCM(services.exe)参与,而不是内核直接读取注册表?
因为驱动装载不只是"把文件加载到内存",还涉及启动策略、依赖管理、安全权限、状态追踪等复杂逻辑。SCM 作为用户态服务管理器,可以:
- 解析
DependOnService和DependOnGroup的依赖图 - 管理服务的生命周期状态机(STOPPED → START_PENDING → RUNNING)
- 通过 RPC 接口响应其他进程(如设备管理器、安装程序)的请求
- 记录事件日志,便于故障排查
如果让内核直接读取注册表,这些策略逻辑就必须全部放在内核中,增加内核复杂度和攻击面。SCM 作为独立进程,崩溃后可以被重启,不影响内核稳定性。
2. 为什么 StartType 要分成 BOOT_START / SYSTEM_START / AUTO_START / DEMAND_START / DISABLED 五级?
因为不同类型的驱动对系统启动的时间点和依赖关系要求完全不同:
- BOOT_START(0):在引导加载器阶段就需要(如磁盘控制器驱动),没有它们系统根本无法读取硬盘
- SYSTEM_START(1):在内核初始化后、PnP 管理器之前加载(如总线驱动),为后续设备枚举做准备
- AUTO_START(2):系统启动完成后由 SCM 自动加载(如网络驱动),此时基本子系统已就绪
- DEMAND_START(3):按需手动启动(如调试驱动),节省资源
- DISABLED(4):明确禁用,防止有问题的驱动影响系统
这种分层确保了系统从最底层硬件到最上层功能的渐进式启动,避免"下层还没准备好就加载上层"的混乱。
3. 为什么 PE 加载后还需要处理导入表(Imports)和重定位(Relocations)?
PE 文件是位置无关的编译产物 ,其代码中引用的外部函数地址和全局数据地址在编译时都是相对值 或占位符:
- 导入表处理 :驱动代码中调用的
IoCreateDevice、KeRaiseIrql等函数并不在驱动自身映像中,而是在ntoskrnl.exe或hal.dll中。导入表处理就是遍历IMAGE_IMPORT_DESCRIPTOR,找到每个依赖 DLL 的导出表,将函数实际地址填入驱动的 IAT(Import Address Table)。 - 重定位处理 :编译时假设的加载基址(
ImageBase)可能已经被其他驱动占用,实际加载地址不同。.reloc节记录了所有需要修复的地址偏移,加载器根据实际基址与编译基址的差值(Delta)修正这些指针。
没有这两步,驱动一执行就会因为调用无效地址而蓝屏。
4. 为什么 DriverObject 要由 I/O 管理器创建而不是驱动自己分配?
因为 DRIVER_OBJECT 是受**对象管理器(Object Manager)**管理的内核对象,需要:
- 统一的引用计数管理(
ObReferenceObject/ObDereferenceObject) - 命名空间中的可查找性(
\Driver\Beep) - 自动清理回调(引用计数归零时调用
IopDeleteDriver) - 安全描述符保护(防止非特权代码修改驱动对象)
如果驱动自己用 ExAllocatePool 分配,这些对象管理功能都无法实现,驱动卸载时也无法自动清理,会导致内存泄漏或悬挂指针。
5. 为什么 NtLoadDriver 是特权调用,不能从用户态直接调用?
因为驱动代码运行在 RING 0(内核态) ,拥有对内核地址空间和硬件的完全访问权限。如果普通用户进程能直接调用 NtLoadDriver 加载任意驱动,恶意软件就可以加载一个恶意驱动来:
- 修改内核内存,隐藏自身进程
- 直接读写磁盘扇区,绕过文件系统权限
- 挂钩系统调用,窃取敏感数据
- 禁用安全软件
因此 NtLoadDriver 需要 SeLoadDriverPrivilege 权限(通常只有管理员拥有),并且 SCM 通过 LPC 端口与内核通信,确保只有受信任的系统服务才能触发驱动加载。
6. 为什么 PnP 驱动和老式驱动的装载路径不同?
老式驱动 (NT 4.0 风格)在 DriverEntry 中直接调用 IoCreateDevice 创建设备,设备路径是硬编码的(如 \Device\Beep)。这种驱动的装载路径是:SCM → NtLoadDriver → DriverEntry → 创建设备。
PnP 驱动 (WDM 风格)的设备不是由驱动自己创建的,而是由 总线驱动枚举 后由 PnP 管理器创建的。PnP 驱动的 DriverEntry 通常只是一个桩,真正的初始化在 AddDevice 中完成。装载路径是:总线枚举 → PnP 管理器发现新设备 → 根据 HardwareID 匹配驱动 → IopLoadDriverImage 加载驱动 → DriverEntry(桩)→ AddDevice(创建 FDO)。
两种路径的根本区别在于:谁决定何时创建设备------老式驱动自己决定,PnP 驱动由总线枚举和 PnP 管理器决定。
7. 为什么驱动卸载前要检查是否有打开的设备对象和活动的 IRP?
因为驱动卸载意味着驱动的代码段和数据段将从内存中移除。如果此时还有:
- 打开的设备对象 :用户态或内核态还有
HANDLE引用该设备,IoDeleteDevice会失败 - 活动的 IRP:某个线程正在等待 IRP 完成,如果驱动卸载了,完成例程指向的代码已经不存在,触发无效地址调用(蓝屏)
- 依赖服务:其他驱动依赖本驱动提供的服务(如过滤驱动依赖被过滤的驱动)
IopCanUnloadDriver 的检查顺序是:检查设备对象引用计数 → 检查 DO_DEVICE_UNLOADABLE 标志 → 检查依赖服务 → 检查活动 IRP。任何一项不满足都拒绝卸载,防止"卸载后崩溃"。
8. 为什么 SCM 需要用拓扑排序来解析服务依赖关系?
服务之间的依赖关系是一个有向图,可能存在:
- 多层级依赖:A 依赖 B,B 依赖 C,必须先启动 C,再 B,再 A
- 多依赖:A 同时依赖 B 和 C,必须等 B 和 C 都启动后才能启动 A
- 循环依赖:A 依赖 B,B 依赖 A,这是非法配置,必须检测并拒绝
拓扑排序可以:
- 确保每个服务在其所有依赖项启动后才启动
- 检测循环依赖(拓扑排序无法完成时即存在环)
- 支持组依赖的展开(将组依赖转换为具体服务依赖)
没有拓扑排序,SCM 可能先启动 A 再启动 B,导致 A 因为依赖未就绪而失败。
9. 为什么 ErrorControl 要分为 IGNORE / NORMAL / SEVERE / CRITICAL 四级?
因为不同驱动对系统的重要性不同,失败的后果也不同:
- IGNORE(0):可选驱动(如某些调试工具),加载失败不影响系统,只记录日志
- NORMAL(1):普通驱动(如打印机驱动),加载失败会记录警告,禁用该服务后继续启动
- SEVERE(2) :重要驱动(如显示驱动),加载失败会导致功能严重受损,系统尝试切换到
LastKnownGood配置 - CRITICAL(3) :核心驱动(如磁盘控制器),加载失败系统无法启动,直接蓝屏(
DRIVER_LOAD_FAILURE)
这种分级让系统能够根据驱动的重要性采取不同的恢复策略,避免"小驱动失败导致系统崩溃"或"核心驱动失败却继续启动导致更严重问题"。
10. 为什么 BOOT_START 驱动需要并行加载优化?
系统启动早期,CPU 可能处于空闲状态等待驱动加载完成。传统串行加载的瓶颈在于:
- 磁盘 I/O 是阻塞操作,CPU 在等待磁盘读取驱动文件
- 多个 BOOT_START 驱动之间通常没有依赖关系(如磁盘驱动和基本文件系统驱动可以并行加载)
并行加载通过创建多个线程同时加载独立的驱动,可以:
- 充分利用多核 CPU
- 重叠多个驱动的磁盘 I/O,提高磁盘吞吐量
- 缩短系统启动时间
但并行加载必须满足前提:驱动之间无依赖关系 (IopCanLoadDriverNow 检查)。如果先加载依赖项再并行加载被依赖项,就失去了并行的意义。
9.8.8 总结
驱动装载机制的核心要点:
- SCM 与内核协作 :
services.exe决定何时装载,内核执行装载 - 注册表驱动数据库 :
HKLM\SYSTEM\CurrentControlSet\Services\<Name>包含所有元数据 NtLoadDriver入口:SCM 通过此系统调用触发装载IopLoadDriverImage:读取 ImagePath + 加载 PE + 调用 DriverEntry- PE 加载:LdrLoadDriver 创建 Section + 映射 + 处理导入/重定位
- DriverObject 分配:由 I/O 管理器创建,驱动可自由扩展
- 启动类型:BOOT / SYSTEM / AUTO / DEMAND / DISABLED
- PnP 自动装载:通过 HardwareID 匹配 + IopInitializePnpDriver 路径
下一节 9.9 介绍 磁盘的设备驱动堆叠。
本章代码索引
| 文件 | 内容 |
|---|---|
| services/scm.c(file:///d:/reactos/base/system/services/scm.c) | SCM 主入口 |
| services/dispatch.c(file:///d:/reactos/base/system/services/dispatch.c) | 服务控制派发 |
| services/database.c(file:///d:/reactos/base/system/services/database.c) | 服务数据库 |
| services/eventlog.c(file:///d:/reactos/base/system/services/eventlog.c) | 事件日志 |
| services/rpcserver.c(file:///d:/reactos/base/system/services/rpcserver.c) | RPC 接口 |
| io/iomgr/loader.c(file:///d:/reactos/ntoskrnl/io/iomgr/loader.c) | NtLoadDriver、IopLoadDriverImage |
| ldr/ldrpe.c(file:///d:/reactos/ntoskrnl/ldr/ldrpe.c) | LdrLoadDriver、LdrpProcessImports |
| ldr/ldr.c(file:///d:/reactos/ntoskrnl/ldr/ldr.c) | LdrLoadDriver、LdrUnloadDriver |
| ldr/ldrp.c(file:///d:/reactos/ntoskrnl/ldr/ldrp.c) | LdrpMapDll 等 |
| smss/(file:///d:/reactos/base/system/smss/) | 启动早期驱动装载 |
| iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) | DRIVER_OBJECT、PDRIVER_INITIALIZE |