FreeLDR → ntoskrnl.exe 启动过程分析
FreeLDR → ntoskrnl.exe 启动过程分析
- 概述
ReactOS 的启动加载器 FreeLDR (freeldr.sys) 负责将 Windows NT 兼容的内核 (ntoskrnl.exe) 和硬件抽象层 (hal.dll) 加载到内存中,建立合适的分页环境,最终将控制权转交给内核的入口点 KiSystemStartup。
整个转换过程是裸机环境到 Windows 内核虚拟内存环境的完整过渡,完成了硬件初始化、文件解析、PE 镜像加载、内存布局重构、分页模式切换、内核权限移交等核心操作,完全兼容 Windows NT 内核启动规范,也是 ReactOS 实现 Windows 系统兼容启动的核心模块。
整个转换过程可分为以下阶段:
第一阶段: FreeLDR 初始化 (BootMain)
第二阶段: 启动菜单与 OS 选择 (RunLoader)
第三阶段: WinLDR 加载核心模块 (LoadWindowsCore)
第四阶段: 注册表与硬件初始化
第五阶段: 建立分页内存布局 (SetupMachineDependent)
第六阶段: 切换到分页模式 (SetProcessorContext)
第七阶段: 最终移交 (→ KiSystemStartup)
- 第一阶段: FreeLDR 自身初始化
本阶段是整个启动流程的基石,运行于 CPU 保护模式、物理地址寻址环境下,核心目标是完成加载器自身环境搭建、基础硬件与软件模块初始化,为后续文件读取、镜像加载、内存管理提供基础运行环境,全程无虚拟内存、无分页机制。
2.1 入口点
[arch/i386/entry.S](file:///d:/reactos/boot/freeldr/freeldr/arch/i386/entry.S) — _RealEntryPoint
该函数是 FreeLDR 最底层汇编入口,由 BIOS/VBR/MBR 引导程序跳转执行,是系统上电后内核运行前的核心过渡代码,核心执行逻辑如下:
-
模式切换:从 BIOS 默认的 16 位实模式切换为 32 位保护模式,解锁 4GB 内存寻址能力,突破实模式 1MB 内存寻址限制。
-
段寄存器初始化:统一设置数据段、代码段、栈段选择子为 PMODE_DS,构建平坦内存模型,消除段式内存寻址的地址偏移复杂度。
-
栈初始化:初始化 32 位保护模式专属栈 stack32,为 C 语言代码运行提供栈空间,保障函数调用、局部变量存储正常执行。
-
中断表初始化:加载临时 IDT(中断描述符表),初始化基础中断处理机制,避免启动过程中硬件中断触发导致系统异常。
-
跳转 C 入口:完成底层硬件模式配置后,跳转至 C 语言入口函数,进入高层逻辑初始化阶段。
2.2 C 入口
[freeldr.c](file:///d:/reactos/boot/freeldr/freeldr/freeldr.c) — BootMain(CmdLine)
BootMain 是 FreeLDR 高层初始化核心函数,按固定顺序完成所有基础组件初始化,各步骤具备强依赖关系,不可颠倒,核心代码与逻辑解析如下:
VOID __cdecl BootMain(IN PCCH CmdLine)
{
LoadSettings(CmdLine); // 从命令行加载默认设置
DebugInit(…); // 调试器预初始化
MachInit(CmdLine); // 硬件平台初始化
FrLdrCheckCpuCompatibility(); // 检查 CPU 兼容性
UiInitialize(FALSE); // UI 预初始化
MmInitializeMemoryManager(); // 初始化内存管理器
FsInit(); // 初始化文件系统 I/O
PeLdrInitializeModuleList(); // 初始化模块列表
MachInitializeBootDevices(); // 初始化启动设备LaunchSecondStageLoader(); // 启动二级加载器 (rosload.exe)
// 失败则 Reboot()
}
各函数核心作用详解: -
LoadSettings:解析引导传递的命令行参数,加载默认启动配置,覆盖默认参数优先级低于后续 ini 配置与手动启动选项。
-
DebugInit:初始化串口、调试端口,开启启动日志输出,为启动故障排查提供调试支撑,未开启调试模式时为空实现。
-
MachInit:初始化 x86 平台基础硬件架构,识别主板、总线基础信息,完成平台通用硬件适配。
-
FrLdrCheckCpuCompatibility:检测 CPU 架构、指令集(32位、保护模式支持),不兼容则直接重启设备,保障启动基础环境合规。
-
UiInitialize(FALSE):轻量 UI 初始化,仅初始化图形基础资源,不加载完整菜单界面,为后续菜单显示做预热。
-
MmInitializeMemoryManager:初始化物理内存管理器,扫描 BIOS 内存映射,划分可用内存、保留内存、坏内存区域,为后续内存分配奠定基础。
-
FsInit:初始化 FAT32、NTFS 等文件系统驱动,注册文件读写接口,实现系统分区文件读取能力。
-
PeLdrInitializeModuleList:初始化内核模块链表,用于后续记录所有加载的内核、驱动、DLL 模块信息。
-
MachInitializeBootDevices:初始化磁盘、分区等启动设备,识别系统分区位置,建立设备访问通路。
2.3 二级加载器 (rosload.exe)
LaunchSecondStageLoader() 加载同目录或 loader/ 子目录下的 rosload.exe,该模块是 FreeLDR 的二级过渡加载器,核心设计为兼容适配 SCSI 启动驱动。
rosload.exe 以 “scsiport.sys” 别名对外导出 ScsiPort* 系列函数,适配 ntbootdd.sys SCSI 启动驱动的依赖需求,解决传统 SCSI 磁盘启动的兼容性问题。加载完成后调用其入口点,由 rosload 完成完整版 freeldr.sys 内核模块的重加载,执行完毕后返回主加载器流程。
实际启动路径(简化):
BIOS → bootsect.bin → freeldr.sys → rosload.exe → 返回 freeldr.sys
注:当不使用 SCSI 驱动、仅使用 IDE/SATA 普通磁盘启动时,rosload 无实际工作需求,会直接快速返回控制权给 freeldr.sys,跳过完整二级加载阶段,提升启动速度。
-
第二阶段: 启动菜单与操作系统选择
本阶段核心是读取启动配置、展示启动菜单、接收用户选择、匹配对应系统启动方案,是人机交互与启动分支选择的核心阶段,运行于物理地址模式。
[bootmgr.c](file:///d:/reactos/boot/freeldr/freeldr/bootmgr.c) — RunLoader()
VOID RunLoader(VOID)
{
LoadBootDeviceDriver(); // 加载额外的 SCSI 启动设备驱动
IniFileInitialize(); // 打开 FREELDR.INI
UiInitialize(TRUE); // 初始化 UI(全功能)OperatingSystemList = InitOperatingSystemList(…); // 读取启动项列表
// 显示启动菜单循环
for (;😉
{
UiDisplayMenu(…); // 显示菜单
LoadOperatingSystem(&…); // 加载所选操作系统
}
}
核心流程解析:
- LoadBootDeviceDriver:补充加载大容量磁盘、SCSI、RAID 等非常规启动设备驱动,确保系统分区可正常访问。
- IniFileInitialize:解析根目录下FREELDR.INI 配置文件,读取启动超时、默认启动项、启动参数、菜单样式等配置。
- UiInitialize(TRUE):初始化完整 UI 界面,加载字体、颜色、菜单渲染资源,支持键盘交互、超时自动选择功能。
- InitOperatingSystemList:从 ini 配置中解析所有系统启动项,构建启动项链表,支持多系统共存启动。
- 菜单循环:持续渲染启动菜单,监听键盘输入,超时未选择则自动选中默认启动项,调用加载函数执行启动流程。
3.1 加载操作系统
LoadOperatingSystem() 是启动分支分发核心函数,通过系统类型字符串匹配 OSLoadingMethods[] 注册表,调用对应的系统加载入口,实现不同版本 Windows/ReactOS 启动适配。
static const OS_LOADING_METHOD OSLoadingMethods[] = {
{“WindowsNT40” , …, LoadAndBootWindows},
{“Windows” , …, LoadAndBootWindows}, // 即 Windows2003
{“Windows2003” , …, LoadAndBootWindows},
{“WindowsVista”, …, LoadAndBootWindows},
{“ReactOSSetup”, …, LoadReactOSSetup},
};
其中 LoadAndBootWindows() 是兼容所有 NT 内核系统的 WinLDR 通用入口函数,后续所有内核加载、系统初始化逻辑均从该函数展开;LoadReactOSSetup 为系统安装模式专属启动入口,用于 ReactOS 安装流程初始化。
- 第三阶段: WinLDR 加载核心模块
本阶段是内核启动的核心加载阶段,WinLDR 模拟 Windows 原生 NTLDR 加载逻辑,完成启动参数解析、核心数据结构初始化、注册表加载、NLS 数据初始化、内核及依赖镜像加载,是从配置解析到实体内核加载的关键过渡阶段。
[winldr.c](file:///d:/reactos/boot/freeldr/freeldr/ntldr/winldr.c) — LoadAndBootWindows(Argc, Argv, Envp)
4.1 解析启动参数
从启动项参数数组Argv[] 中解析系统启动核心配置,所有参数将贯穿整个启动流程,并最终传递给内核,核心参数如下:
参数
示例
说明
SystemPartition
multi(0)disk(0)rdisk(0)partition(1)
系统分区 ARC 标准路径,内核设备识别核心标识
SystemPath
\ReactOS
系统根目录路径,用于定位 system32 核心目录
Options
/FASTDETECT /MININT /DEBUG
启动控制选项,控制硬件检测、调试模式、最小启动等特性
BootType
Windows2003
操作系统版本类型,用于适配对应内核加载规则
BootType 与 NT 内核版本严格映射,决定内核特性、兼容规则、内存模型,映射关系如下:
BootType
宏定义
值
WindowsNT40
_WIN32_WINNT_NT4
0x0400
Windows / Windows2003
_WIN32_WINNT_WS03
0x0502
WindowsVista
_WIN32_WINNT_VISTA
0x0600
4.2 分配并初始化 Loader Parameter Block
AllocateAndInitLPB() 是启动流程核心函数,负责分配并初始化 LOADER_SYSTEM_BLOCK 结构体,该结构体是加载器与内核通信的唯一数据载体,内核启动的所有配置、内存信息、模块信息、硬件信息均通过该结构体传递。
WinLdrSystemBlock = MmAllocateMemoryWithType(sizeof(LOADER_SYSTEM_BLOCK), LoaderSystemBlock);
LoaderBlock = &WinLdrSystemBlock->LoaderBlock;
// 初始化三个关键双向链表
InitializeListHead(&LoaderBlock->LoadOrderListHead); // 模块加载顺序链表
InitializeListHead(&LoaderBlock->MemoryDescriptorListHead); // 内存描述符链表
InitializeListHead(&LoaderBlock->BootDriverListHead); // 启动驱动链表
// 设置 LoaderBlockExtension
Extension->MajorVersion = (Version >> 8) & 0xFF;
Extension->MinorVersion = Version & 0xFF;
Extension->BootViaEFI = …; // EFI 引导标记
核心链表作用:
- LoadOrderListHead:记录所有内核、HAL、驱动、DLL 模块的加载顺序与内存地址,内核初始化时按该顺序初始化模块。
- MemoryDescriptorListHead:记录整机内存布局、内存类型、可用范围,内核内存管理器基于该链表构建虚拟内存体系。
- BootDriverListHead:记录所有启动阶段加载的核心驱动,内核 I/O 管理器优先加载该链表内驱动。
4.3 加载 SYSTEM 配置单元
注册表 SYSTEM 配置单元存储系统硬件配置、驱动加载规则、系统服务配置,是内核初始化的核心配置来源,WinLdrInitSystemHive() 负责完成注册表加载与解析。
// 普通启动加载: SystemRoot\system32\config\SYSTEM
// 安装模式加载: SystemRoot\SETUPREG.HIV
WinLdrLoadSystemHive(LoaderBlock, SearchPath, “SYSTEM”, &Reason);
-> ArcOpen(), ArcGetFileInformation(), ArcRead()
-> MmAllocateMemoryWithType(fileSize, LoaderRegistryData)
// 导入二进制配置单元到内存注册表
RegImportBinaryHive(VaToPa(LoaderBlock->RegistryBase), LoaderBlock->RegistryLength, …);
// 失败时回退到 SYSTEM.ALT
if (!Success) 尝试加载 “SYSTEM.ALT” 并标记 HBOOT_BOOT_RECOVERED_BY_ALTERNATE_HIVE
// 初始化 CurrentControlSet 链接
RegInitCurrentControlSet(FALSE);
异常容错机制:当默认 SYSTEM 注册表文件损坏、缺失时,自动加载备份文件 SYSTEM.ALT,保障系统可正常启动,同时标记启动恢复状态,供内核后续识别修复。最终初始化 CurrentControlSet 链接,指向当前生效的系统配置集。
4.4 扫描注册表与加载 NLS 数据
WinLdrScanSystemHive() 完成注册表驱动扫描、NLS 语言资源加载,为系统硬件适配、字符编码、语言显示提供支撑。
// 1. 扫描注册表,构建启动驱动列表
WinLdrScanRegistry(&LoaderBlock->BootDriverListHead);
-> CmpFindDrivers() // 从 HKLM\SYSTEM\CurrentControlSet\Services 查找启动驱动
-> CmpSortDriverList() // 按组/标签排序
-> CmpResolveDriverDependencies() // 解析循环依赖
// 2. 从注册表获取 NLS 文件名
WinLdrGetNLSNames(…, &AnsiFileName, &OemFileName, &LangFileName, …);
// 读取 HKLM…\Control\NLS\CodePage\ACP
// 读取 HKLM…\Control\NLS\CodePage\OEMCP
// 读取 HKLM…\Control\NLS\Language\Default
// 3. 加载 NLS 文件
WinLdrLoadNLSData(…, SearchPath, &AnsiFileName, &OemFileName, &LangFileName, …);
-> 加载 c_1252.nls (ANSI), c_437.nls (OEM), l_intl.nls (Unicode CaseTable)
-> 分配 LoaderNlsData 类型内存
核心逻辑说明:
- 驱动扫描会自动解析驱动依赖关系、解决循环依赖,按系统预设优先级排序,避免驱动加载顺序错误导致硬件初始化失败。
- NLS 数据决定系统默认编码、大小写转换规则、语言适配,是系统文本显示、文件编码识别的基础,加载后统一存入 LoaderBlock 供内核调用。
4.5 进入 LoadAndBootWindowsCommon()
该函数是启动流程的不可返回前置节点,完成所有兜底准备工作,后续流程一旦执行将无法回退,直至内核移交。
// 1. 设置 EMS 重定向
WinLdrSetupEms(BootOptions);
// 2. 硬件检测
LoaderBlock->ConfigurationRoot = MachHwDetect(BootOptions);
// 检测 PCI 总线、ACPI、显示设备等,构建 ARC 配置树
// 3. ★ 加载 Windows 内核核心 ★
LoadWindowsCore(OperatingSystemVersion, LoaderBlock, BootOptions, BootPath, &KernelDTE);
硬件检测阶段会遍历整机总线与设备,构建标准化 ARC 硬件配置树,记录所有可识别硬件设备信息,内核 HAL 层将基于该配置树完成硬件初始化。
4.6 LoadWindowsCore() — 加载内核/HAL/KD DLL
该函数是核心镜像加载入口,负责加载所有内核必备核心组件,解析启动选项适配不同内核加载模式,递归处理所有模块依赖。
LoadWindowsCore()
├── 解析启动选项: 3GB, SAFEBOOT, BOOTLOGO, NOEXECUTE, PAE 等
├── 确定 HAL 和 KERNEL 文件名 (默认 hal.dll, ntoskrnl.exe)
│ └── 支持 /HAL=xxx.dll 和 /KERNEL=xxx.exe 覆盖
├── ★ 加载内核 ntoskrnl.exe → LoaderSystemCode
│ └── PeLdrLoadImage(BootPath\system32\ntoskrnl.exe, LoaderSystemCode, &KernelBase)
├── ★ 加载 HAL hal.dll → LoaderHalCode
├── ★ 加载 KD 传输 DLL kdcom.dll → LoaderSystemCode
│ └── 根据 /DEBUGPORT= 选项选择: kdcom.dll / kd1394.dll / kdusb2.dll
├── ★ 加载 BootVid bootvid.dll → LoaderSystemCode
│ └── 自动检测: PC-98 → pc98bvid.dll, Xbox → xboxbvid.dll, EFI → lfbbvid.dll, 含 Framebuffer → lfbbvid.dll
├── 扫描并加载所有导入依赖 (PeLdrScanImportDescriptorTable)
│ └── 递归加载 kernel32.dll, nt.dll 等系统 DLL
└── 成功返回
PE 加载器核心处理流程:PeLdrLoadImage() 是自定义 PE 镜像加载器,完全适配 NT 内核镜像格式,核心步骤如下:
- 读取 DOS 头、PE 头,校验镜像合法性,拒绝损坏、不兼容的内核文件;
- 解析节表(代码节、数据节、资源节),按内存对齐规则规划内存布局;
- 将各节数据映射到对应类型的物理内存区域;
- 遍历重定位表,修正镜像内存地址,适配实际加载基址;
- 初始化安全 Cookie,开启内核基础安全防护机制。
- 第四阶段: 加载启动驱动与 Phase1 初始化
本阶段聚焦启动驱动加载与核心数据结构完善,完成硬件驱动预加载、LoaderBlock 结构体补全、物理地址预处理,为分页模式切换和内核移交做最后数据准备。
5.1 加载启动驱动
基于前期注册表扫描生成的驱动链表,批量加载系统必备启动驱动,保障磁盘、总线、基础硬件在内核初始化前可用。
WinLdrLoadBootDrivers(LoaderBlock, BootPath);
// 遍历 BootDriverListHead
// 对每个启动驱动:
// PeLdrLoadImage(BootPath + FilePath, LoaderBootDriver, &DriverBase)
// PeLdrAllocateDataTableEntry(…)
// PeLdrScanImportDescriptorTable(…) // 加载依赖
所有启动驱动统一加载至 LoaderBootDriver 类型内存,记录加载地址、大小、依赖关系,存入驱动链表,内核启动后直接复用,无需重复加载。
5.2 WinLdrInitializePhase1()
一阶初始化是启动流程的数据补全核心函数,完成所有路径信息、硬件信息、内存信息的填充,同时完成物理地址到虚拟地址的预处理转换,是分页切换的前置关键步骤。
WinLdrInitializePhase1(OperatingSystemVersion, LoaderBlock, BootOptions, SystemPartition, BootPath);
├── 填充 LoaderBlock->ArcBootDeviceName (引导分区 ARC 路径)
├── 填充 LoaderBlock->ArcHalDeviceName (系统分区 ARC 路径)
├── 填充 LoaderBlock->NtBootPathName (系统根目录, 如 \ReactOS)
├── 填充 LoaderBlock->NtHalPathName (HAL 路径, 通常为 )
├── 填充 LoaderBlock->LoadOptions (启动选项字符串)
├── 填充 ARC 磁盘信息列表 ArcDiskInformation
├── 转换所有链表到 VA 地址 (PaToVa)
│ └── List_PaToVa(&LoadOrderListHead)
│ └── ConvertConfigToVA(ConfigurationRoot)
│ └── List_PaToVa(&BootDriverListHead)
├── 设置 ACPI 表指针
├── 填充 LoaderPerformanceData (Vista+)
├── 填充 HeadlessLoaderBlock (EMS 远程管理)
└── 加载驱动数据库 drvmain.sdb (LoaderRegistryData)
核心价值:统一所有核心数据结构的地址类型,解决分页切换后物理地址失效问题,确保内核接收的所有数据均为合法虚拟地址,避免内核初始化指针异常。
-
第五阶段: 建立分页内存布局
本阶段是物理内存寻址到虚拟内存分页寻址的核心铺垫阶段,完成硬件收尾、架构相关内存配置、页目录/页表分配、特殊内存映射、内存描述符构建,搭建 Windows 内核标准的 32 位分页内存模型。
6.1 MachPrepareForReactOS()
分页切换前的硬件收尾函数,停止所有闲置硬件设备(软驱马达、闲置总线),重置显示模式为内核默认显示状态,清理启动阶段残留的硬件状态,避免硬件占用内存导致分页映射冲突,保障内存布局纯净。
6.2 WinLdrSetupMachineDependent() — 架构相关设置
[arch/i386/winldr.c](file:///d:/reactos/boot/freeldr/freeldr/arch/i386/winldr.c),为 x86 架构专属初始化函数,完成 PCR、TSS、GDT/IDT、页表、特殊内核区域的内存分配与映射。
WinLdrSetupMachineDependent(LoaderBlock)
{
LoaderBlock->u.I386.CommonDataArea = NULL; // 禁止 ABIOS
LoaderBlock->u.I386.MachineType = MACHINE_TYPE_ISA;// 1. 分配 PCR (处理器控制区) 页面 — 2 页
Pcr = MmAllocateMemoryWithType(2 * MM_PAGE_SIZE, LoaderStartupPcrPage);
// 第 1 页: Boot Processor PCR (KIP0PCRADDRESS = 0xFFDFF000)
// 第 2 页: KI_USER_SHARED_DATA (0xFFDF0000)// 2. 分配 TSS (任务状态段)
Tss = MmAllocateMemoryWithType(TssSize, LoaderMemoryData);// 3. 分配 GDT + IDT 空间 (128 GDT 条目 + 256 IDT 条目)
GdtIdt = MmAllocateMemoryWithType(NumPages * MM_PAGE_SIZE, LoaderMemoryData);// 4. ★ 分配页目录和页表
MempAllocatePageTables();
// 计算所需页表数: NumPageTables = 总物理页数 >> 10
// 总大小 = PDE(1) + HAL页表(1) + 物理PT(NumPageTables) + 内核PT(NumPageTables)
// PDE 自映射在 0xC0300000 (SELFMAP_ENTRY = 0x300 即 PDE[768])
// HAL 页表映射在 PDE[1023] (虚拟地址 0xFFC00000-0xFFFFFFFF)// 5. 映射特殊页面
WinLdrMapSpecialPages();
// KI_USER_SHARED_DATA → Pcr+1 页
// KIP0PCRADDRESS → Pcr 页
// APIC 寄存器 → HAL 页表区域
}
x86 标准分页结构布局:
CR3 → PDE[0…1023] (自映射在 0xC0300000)
├── PDE[0] → PhysicalPT[0] (标识映射低端内存)
├── …
├── PDE[0x300] → 自身 (自映射)
├── …
├── PDE[768] → KernelPT[0] (KSEG0_BASE = 0x80000000)
├── …
└── PDE[1023] → HalPageTable (0xFFC00000-0xFFFFFFFF)
├── APIC 映射
├── PCR (0xFFDFF000)
└── KI_USER_SHARED_DATA (0xFFDF0000)
该布局完全兼容 Windows x86 内核内存规范,实现用户空间、内核空间、HAL 专属空间、自映射空间的严格划分。
6.3 WinLdrSetupMemoryLayout() — 创建内存描述符
[wlmemory.c](file:///d:/reactos/boot/freeldr/freeldr/ntldr/wlmemory.c),负责梳理整机内存资源,标准化划分内存类型,构建内核可识别的内存描述符链表。
WinLdrSetupMemoryLayout(LoaderBlock)
{
FrLdrHeapCleanupAll(); // 清理堆// 1. 分配 Memory Allocation Descriptors (MAD)
Mad = MmAllocateMemoryWithType(200 * sizeof(MEMORY_ALLOCATION_DESCRIPTOR), LoaderMemoryData);// 2. 遍历内存查找表,为每个区域设置分页
MemoryMap = MmGetMemoryMap(&NoEntries);
for (i = 2; i < NoEntries; i++)
{
MempSetupPagingForRegion(LastPageIndex, i - LastPageIndex, LastPageType);
// LoaderSystemCode / LoaderHalCode → 内核模式映射
// LoaderFree / LoaderBad → 不映射
// LoaderFirmwareTemporary → 用户模式映射
}// 3. 创建内存描述符链表 (插入排序 + 合并空闲块)
for (i = 0; i < NoEntries; i++)
MempAddMemoryBlock(LoaderBlock, BasePage, PageCount, Type);// 4. 添加 BIOS 内存映射中的高端描述符
for (i = 0; i < BiosMemoryMapEntryCount; i++)
MempAddMemoryBlock(LoaderBlock, …);// 5. 转换内存描述符链表到 VA
List_PaToVa(&LoaderBlock->MemoryDescriptorListHead);
}
内存类型映射规则(内核内存权限管控核心):
内存类型
区域用途
映射模式
LoaderSystemCode
内核代码
内核模式
LoaderHalCode
HAL 代码
内核模式
LoaderRegistryData
注册表配置单元
内核模式
LoaderNlsData
NLS 表
内核模式
LoaderStartupPcrPage
PCR 页
内核模式
LoaderBootDriver
启动驱动
内核模式
LoaderLoadedProgram
加载的程序
用户模式
LoaderFirmwareTemporary
固件临时
用户模式
LoaderOsloaderStack
加载器栈
用户模式
LoaderFree
空闲内存
不映射
LoaderBad
坏内存
不映射
LoaderSpecialMemory
特殊内存 (MMIO)
不可见
-
第六阶段: 切换到分页模式
本阶段是整个启动流程最核心、最关键的硬件模式切换步骤,完成从平坦物理地址模式到标准 NT 内核分页虚拟地址模式的切换,重构 CPU 寻址方式、段描述符、中断机制,让系统完全进入内核运行环境。
[arch/i386/winldr.c](file:///d:/reactos/boot/freeldr/freeldr/arch/i386/winldr.c) — WinLdrSetProcessorContext()
7.1 执行步骤
WinLdrSetProcessorContext(OperatingSystemVersion)
{
Pcr = KIP0PCRADDRESS; // 0xFFDFF000
Tss = KSEG0_BASE | (TssBasePage << 12); // 内核虚拟地址空间的 TSS// 1. 关闭中断
_disable();// 2. 重置 EFLAGS
__writeeflags(0);// 3. 临时关闭分页 (UEFI 可能已经启用 PAE 分页)
__writecr0(__readcr0() & ~CR0_PG);
__writecr4(__readcr4() & ~CR4_PAE);// 4. ★ 设置新的页目录基址 (PDBR)
__writecr3((ULONG_PTR)PDE);// 5. 启用分页
__writecr0(__readcr0() | CR0_PG);// 从这里开始,CPU 在分页模式下运行!
// FreeLDR 仍然可以通过标识映射 + 内核映射访问// 6. 清零 PCR 页 (内核期望空 PCR)
RtlZeroMemory((PVOID)Pcr, MM_PAGE_SIZE);// 7. ★ 构建新的 GDT 和 IDT
GDT 布局:
KGDT_NULL (0x00): Null 选择子
KGDT_R0_CODE (0x08): Ring 0 代码段 (平坦 4GB)
KGDT_R0_DATA (0x10): Ring 0 数据段 (平坦 4GB)
KGDT_R3_CODE (0x18): Ring 3 代码段 (平坦 4GB)
KGDT_R3_DATA (0x20): Ring 3 数据段 (平坦 4GB)
KGDT_TSS (0x28): TSS 段 (Ring 0)
KGDT_R0_PCR (0x30): PCR 段 (Ring 0) — 基址 = KIP0PCRADDRESS
KGDT_R3_TEB (0x38): TEB 段 (Ring 3)
KGDT_VDM_TILE(0x40): VDM BIOS 数据段 (Ring 3)
KGDT_LDT (0x48): LDT (保留)
KGDT_DF_TSS (0x50): 双错 TSS
KGDT_NMI_TSS (0x58): NMI TSS
0x60: 保留 (Vista+)
0x68: 视频显示缓冲 (Vista+ 保留)
0x70: GDT 别名 (Vista+ 基址不固定)IDT: 复制旧的 IDT 内容到新位置
// 8. 加载新 GDT + IDT
Ke386SetGlobalDescriptorTable(&GdtDesc);
__lidt(&IdtDesc);// 9. 远跳转刷新 CS 预取队列
asm(“ljmp $0x08, $1f\n 1:\n”); // 跳转到 KGDT_R0_CODE// 10. 设置段寄存器
SS = KGDT_R0_DATA
DS = KGDT_R0_DATA
ES = KGDT_R0_DATA // 对 rep stosd 至关重要!
FS = KGDT_R0_PCR // FS 指向 PCR → 内核通过 FS 访问 KPCR
GS = 0
LDT = 0// 11. 加载 TSR (任务寄存器)
Ke386SetTr(KGDT_TSS);
}
关键核心细节解析:
- 中断关闭:模式切换全程关闭中断,避免硬件中断打断分页配置,导致内存映射异常、系统崩溃。
- 分页重置与重建:临时关闭原有分页机制,清空残留配置,加载全新标准化页目录,保证分页环境纯净合规。
- 远跳转刷新:通过远跳转刷新 CPU 代码段预取队列,清除旧模式下的指令缓存,确保后续代码在全新段机制下运行。
- FS 寄存器绑定:将 FS 寄存器固定指向 PCR 区域,是 Windows 内核核心特性,内核所有线程、CPU 核心信息均通过 FS 寄存器快速访问。
7.2 地址空间转换
分页启用后,CPU 完全基于虚拟地址寻址,构建用户/内核隔离的完整地址空间,映射规则完全匹配 Windows NT 规范:
地址范围
映射目标
说明
0x00000000 - 0x7FFFFFFF
用户模式
标识映射的低端物理内存,供应用层使用
0x80000000 - 0x9FFFFFFF
内核模式
KSEG0_BASE 映射区域,存放内核、HAL、驱动核心代码
0xC0000000 - 0xC00FFFFF
自映射
页目录自映射区域,用于动态修改页表配置
0xFFC00000 - 0xFFFFFFFF
HAL 区域
存放页表、PCR、系统共享数据、APIC 映射
核心特性:已加载的内核、FreeLDR 镜像同时存在于物理标识映射和内核虚拟映射区域,保障模式切换前后数据无缝衔接,内核可直接通过虚拟地址访问所有启动阶段加载的资源。
- 第七阶段: 最终移交给内核
本阶段是启动加载器的收尾阶段,完成所有数据校验、地址转换、环境初始化,最终将 CPU 控制权完整移交 ntoskrnl.exe 内核,加载器生命周期结束,系统进入内核初始化阶段。
8.1 移交前的最后准备
在 LoadAndBootWindowsCommon() 函数尾部,完成最终环境校验与数据补全:
// 1. 保存 LoaderPagesSpanned (以供内核使用)
LoaderBlock->Extension->LoaderPagesSpanned = MmGetLoaderPagesSpanned();
// 2. 清零 KI_USER_SHARED_DATA 页面
RtlZeroMemory((PVOID)KI_USER_SHARED_DATA, MM_PAGE_SIZE);
// 3. 调试输出内存描述符、启动驱动、ARC 磁盘信息
// 4. 获取内核入口点
KiSystemStartup = (KERNEL_ENTRY_POINT)KernelDTE->EntryPoint;
// 5. 将 LoaderBlock 转换为虚拟地址
LoaderBlockVA = PaToVa(LoaderBlock);
所有准备工作确保内核接收的入口地址、数据结构体、内存环境完全合法,无残留脏数据、无地址错误。
8.2 最终移交
// 传递控制权给内核!
(*KiSystemStartup)(LoaderBlockVA);
// 永远不会返回到这里
UNREACHABLE;
该调用是不可逆的控制权移交,调用后 CPU 跳转至内核入口函数,FreeLDR 加载器彻底退出运行,后续所有系统行为均由内核主导。
8.3 内核初始化的开始
KiSystemStartup(LoaderBlockVA) 作为 ntoskrnl.exe 唯一入口,启动内核全模块初始化,逐层完成系统核心组件加载:
KiSystemStartup(LoaderBlock)
├── 从 LoaderBlock 获取内存描述符列表
├── 建立内核自己的分页结构
├── 初始化 PCR (Processor Control Region) 和 PRCB
├── 初始化 HAL (HalInitSystem)
├── 初始化内存管理器 (MmInitSystem)
│ └── 创建非分页池和分页池
├── 初始化对象管理器 (ObInitSystem)
├── 初始化进程/线程管理器 (PsInitSystem)
├── 初始化配置管理器 (CmInitSystem) — 注册表
├── 初始化 I/O 管理器 (IoInitSystem)
│ └── 加载启动驱动
├── 初始化安全引用监视器 (SeInitSystem)
├── …
└── → 最终创建 System 进程和 SMSS → Win32 子系统 → GUI → 登录
- 关键数据结构
以下两个结构体是加载器与内核交互的核心载体,承载所有启动信息,是系统启动的核心数据基石。
9.1 LOADER_PARAMETER_BLOCK
核心参数块,存储模块、内存、驱动、设备、启动配置等所有核心信息:
typedef struct _LOADER_PARAMETER_BLOCK {
LIST_ENTRY LoadOrderListHead;
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)