Reactos 第 9 章 设备驱动 — 9.8 设备驱动模块的装载

第 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 调用 这四个阶段。


概述

驱动装载流程可以分解为五个阶段:

  1. SCM 阶段services.exe 读取注册表 HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName> 决定启动类型
  2. NtLoadDriver 阶段 :SCM 通过 LPC 端口调用 NtLoadDriver,把驱动注册表键路径传入
  3. PE 加载阶段IopLoadDriverImage 调用 LdrLoadDriver 映射 PE 文件到内核空间
  4. 入口调用阶段 :I/O 管理器调用 DriverEntry 入口
  5. 依赖与初始化 :处理 DependOnGroupDependOnService 等待依赖启动

本节内容概览

  • 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) IopLoadDriverImageNtLoadDriver
PE 加载 ntoskrnl/ldr/ldrpe.c(file:///d:/reactos/ntoskrnl/ldr/ldrpe.c) LdrpLoadPeImageLdrpMapPeImage
LDR ntoskrnl/ldr/ldr.c(file:///d:/reactos/ntoskrnl/ldr/ldr.c) LdrLoadDriverLdrUnloadDriver
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 进程内,负责:

  1. 服务数据库 :读取 HKLM\SYSTEM\CurrentControlSet\Services
  2. 启动顺序 :根据 DependOnGroupDependOnService 决定启动顺序
  3. 服务 RPC :暴露 SvcCtrl_* API 给其他进程
  4. 状态机:管理服务的运行状态(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 禁用

启动顺序

系统按以下顺序启动驱动:

  1. Boot Start(0) :内核初始化阶段,IopInitializeBootDrivers
  2. System Start(1):在 PnP 管理器启动后
  3. Auto Start(2):SCM 自动启动
  4. 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;
}

关键点

  1. ImagePath 读取 :从注册表 ImagePath 键读
  2. PE 加载 :调用 LdrLoadDriver 映射 PE 到内核空间
  3. DriverObject 分配:由 I/O 管理器创建
  4. 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;
}

关键步骤

  1. ZwCreateSection with SEC_IMAGE:Windows PE loader 标记,会自动按节属性映射
  2. ZwMapViewOfSection:映射到虚拟地址空间
  3. 导入表处理 :解析 IMAGE_DIRECTORY_ENTRY_IMPORT,加载依赖 DLL
  4. 重定位处理 :应用 .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.cdatabase.cdispatch.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.cScmResolveDependencies函数中:

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 作为用户态服务管理器,可以:

  • 解析 DependOnServiceDependOnGroup 的依赖图
  • 管理服务的生命周期状态机(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 文件是位置无关的编译产物 ,其代码中引用的外部函数地址和全局数据地址在编译时都是相对值占位符

  • 导入表处理 :驱动代码中调用的 IoCreateDeviceKeRaiseIrql 等函数并不在驱动自身映像中,而是在 ntoskrnl.exehal.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 → NtLoadDriverDriverEntry → 创建设备。

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 总结

驱动装载机制的核心要点:

  1. SCM 与内核协作services.exe 决定何时装载,内核执行装载
  2. 注册表驱动数据库HKLM\SYSTEM\CurrentControlSet\Services\<Name> 包含所有元数据
  3. NtLoadDriver 入口:SCM 通过此系统调用触发装载
  4. IopLoadDriverImage:读取 ImagePath + 加载 PE + 调用 DriverEntry
  5. PE 加载:LdrLoadDriver 创建 Section + 映射 + 处理导入/重定位
  6. DriverObject 分配:由 I/O 管理器创建,驱动可自由扩展
  7. 启动类型:BOOT / SYSTEM / AUTO / DEMAND / DISABLED
  8. 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) NtLoadDriverIopLoadDriverImage
ldr/ldrpe.c(file:///d:/reactos/ntoskrnl/ldr/ldrpe.c) LdrLoadDriverLdrpProcessImports
ldr/ldr.c(file:///d:/reactos/ntoskrnl/ldr/ldr.c) LdrLoadDriverLdrUnloadDriver
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_OBJECTPDRIVER_INITIALIZE
相关推荐
caimouse1 小时前
Reactos 第 9 章 设备驱动 — 9.2 一个“老式“驱动模块的实例
windows
caimouse1 小时前
Reactos 第 9 章 设备驱动 — 9.4 内核劳务线程
开发语言·windows
星栈独行1 小时前
Rust + Makepad 应用怎么打包发布:Windows、macOS、Linux 全平台交付
windows·程序人生·macos·ui·rust
辣香牛肉面2 小时前
Windows PDF转换工具箱
windows·pdf
daly5202 小时前
PyCharm怎么下载?2026最新版PyCharm安装教程(Windows/macOS/Linux)
windows·macos·pycharm
西凉的悲伤2 小时前
redis-windows 安装 redis 到 windows 电脑
java·windows·redis·redis-windows
caimouse2 小时前
Reactos 第 8 章 结构化异常处理 — 8.4 软异常
服务器·开发语言·windows
chushiyunen2 小时前
codex笔记、thinkai中转站
windows
z落落13 小时前
C#WinForm 窗体切换与窗体传值(登录跳转案例)+WinForm 窗体传值(从上往下传、从下往上传)
开发语言·windows·c#