4、UEFI启动流程概览:从按下电源键到操作系统加载的完整旅程

大家好,我是你们的讲师。今天我们来聊聊整个UEFI启动的完整流程。说实话,很多工程师干了几年,对按下电源键之后到底发生了什么,还是模模糊糊的。你问他,他就说“BIOS启动,然后引导系统”。嗯,这话没错,但太笼统了。

我个人习惯把UEFI启动分成几个清晰的阶段。每个阶段都有明确的任务和输出。搞懂了这些,你以后排查启动问题,心里就有谱了。

核心观点:UEFI启动不是一蹴而就的,它是一个分阶段、有状态机的过程。从按下电源键到操作系统完全接管,中间要经历固件初始化、驱动加载、启动策略选择、OS加载器执行等多个环节。

4.1 启动流程的宏观视角

我们先从宏观上看看整个旅程。我把这个过程画成了一张流程图,你一看就明白。

UEFI 启动流程宏观图 1. 按下电源键 / SEC 阶段 2. PEI 阶段(早期初始化) 3. DXE 阶段(驱动执行环境) 4. BDS 阶段(启动设备选择) 5. TSL 阶段(OS 加载器执行) 6. RT 阶段(OS 运行时服务) 关键说明 • 每个阶段都有明确的入口和出口 • 阶段之间通过全局变量传递状态 • 任何阶段失败都会导致启动中断 常见误区 • 很多人以为BIOS启动就是POST • 其实POST只是PEI的一部分 • 真正的重头戏在DXE和BDS

这张图把整个流程分成了6个阶段。你想想看,从你按下电源键,到看到Windows或Linux的登录界面,背后就是这6个阶段在接力跑。任何一个阶段出问题,机器就起不来。

4.2 第一阶段:SEC(安全验证)—— 最容易被忽略的环节

SEC阶段,全称是Security Phase。说白了,它就是CPU复位后执行的第一段代码。这个阶段非常短,可能只有几百条指令,但它的任务很关键。

SEC主要干三件事:

  • 建立临时内存:CPU刚复位时,内存控制器还没初始化,连栈都没有。SEC得先利用CPU内部的缓存(Cache)作为临时内存(CAR,Cache As RAM),这样才能调用C函数。
  • 验证下一个阶段:SEC会校验PEI阶段的代码完整性。如果固件被篡改,SEC会直接停在这里,机器黑屏。
  • 传递系统参数:SEC把CPU的基本信息、复位原因等打包成一个结构体,传给PEI。

我的经验:有一次客户反馈说机器偶尔启动不了,概率很低。我查了很久,最后发现是SEC阶段的CAR设置有问题,Cache大小没算对,导致栈溢出。这种问题用示波器都抓不到,只能靠加调试打印。嗯,从那以后我每次写SEC代码都格外小心。

4.3 第二阶段:PEI(早期初始化)—— 内存是重中之重

PEI阶段,全称是Pre-EFI Initialization。这个阶段的核心任务只有一个:把内存初始化好。

为什么内存这么重要?因为之前的SEC只是用Cache当临时内存,容量小(通常就几KB),而且不能跑复杂的驱动。只有真正的DRAM初始化完成后,系统才能进入下一个阶段。

PEI阶段的工作流程大致如下:

  1. PEI内核(PEI Core)被SEC加载到CAR中执行。
  2. PEI内核调度各个PEIM(PEI Module)执行。
  3. PEIM依次初始化:CPU微码、内存控制器、内存条(SPD)、内存训练。
  4. 内存初始化成功后,PEI把内存信息记录在HOB(Hand-Off Block)中。
  5. PEI找到DXE阶段的入口,准备移交控制权。

注意:内存训练是PEI阶段最耗时、也最容易出问题的地方。不同的内存条、不同的插法,训练参数都不一样。我见过很多启动慢的案例,最后都是因为内存训练参数没优化好。如果你遇到机器第一次启动特别慢,但第二次就快了,那多半是内存训练在做“慢速训练”。

4.4 第三阶段:DXE(驱动执行环境)—— 真正的重头戏

DXE阶段,全称是Driver Execution Environment。这个阶段是整个UEFI启动的核心。说白了,PEI只是把地基打好了,DXE才开始盖楼。

DXE阶段的特点:

  • 有完整的内存环境:可以动态分配内存,可以加载各种驱动。
  • 支持协议:UEFI的精华——Protocol机制,就是在DXE阶段全面铺开的。
  • 驱动按需加载:DXE Dispatcher会扫描所有固件卷(FV),找到驱动并执行。

DXE阶段主要干这些事:

驱动类型 功能 典型例子
CPU驱动 初始化多核、设置MSR、配置中断 CpuDxe
芯片组驱动 配置PCIe、SATA、USB控制器 PchInitDxe
总线驱动 枚举PCI总线、分配资源 PciBusDxe
设备驱动 初始化具体外设 AhciPassThruDxe
ACPI驱动 生成ACPI表(供OS使用) AcpiPlatformDxe

我个人习惯把DXE阶段看作是“硬件抽象层”的构建过程。所有硬件都被封装成了UEFI协议,后续的BDS和OS加载器只需要调用这些协议,不用关心底层细节。

4.5 第四阶段:BDS(启动设备选择)—— 决定从哪启动

BDS阶段,全称是Boot Device Selection。这个阶段的任务很明确:找到可以启动的设备,然后执行它。

BDS的工作流程:

  1. DXE阶段结束后,控制权交给BDS。
  2. BDS读取UEFI启动管理器中的启动顺序(BootOrder)。
  3. 按照BootOrder依次尝试每个启动项。
  4. 每个启动项指向一个EFI应用程序(比如\EFI\BOOT\BOOTX64.EFI)。
  5. 如果某个启动项执行成功,BDS就停止尝试。
  6. 如果所有启动项都失败,进入UEFI Shell或显示错误。

避坑指南:我曾经遇到一个案例,客户说机器插了U盘启动不了。我检查发现,BootOrder里根本没有USB启动项。原因是BDS阶段扫描USB设备时,USB驱动还没加载完。解决方案是在BDS之前,强制让USB总线驱动先完成枚举。这种时序问题在UEFI开发中非常常见。

4.6 第五阶段:TSL(OS加载器执行)—— 交接棒的时刻

TSL阶段,全称是Transient System Load。这个阶段其实很短,就是执行OS加载器(比如Windows的bootmgfw.efi,或者Linux的grubx64.efi)。

OS加载器会做几件事:

  • 调用UEFI的Boot Service来读取文件、分配内存。
  • 加载OS内核到内存。
  • 调用ExitBootServices(),告诉UEFI:“我要接管了,你可以关闭Boot Service了。”

这里有个关键点:ExitBootServices()调用之后,UEFI固件就失去了对硬件的控制权。所有中断、定时器、DMA等,都由OS内核直接管理。这个调用是不可逆的。

// 典型的OS加载器调用ExitBootServices的伪代码
Status = gBS->ExitBootServices(ImageHandle, MapKey);
if (EFI_ERROR(Status)) {
  // 如果MapKey不匹配,说明内存映射发生了变化,需要重试
  // 实际代码中会重新获取内存映射再试一次
}
// 调用成功后,UEFI固件不再可用
// 此时OS内核已经掌握了所有硬件资源

4.7 第六阶段:RT(运行时服务)—— 固件的“售后服务”

RT阶段,全称是Runtime Phase。OS加载完成后,UEFI并没有完全退出。它留下了一组运行时服务(Runtime Services),供OS在运行时调用。

这些服务包括:

  • GetTime/SetTime:读写RTC时间。
  • GetVariable/SetVariable:读写UEFI变量(比如启动顺序、安全启动状态)。
  • ResetSystem:重启或关机。
  • UpdateCapsule:固件更新。

这些服务被映射到OS的虚拟地址空间中,OS可以通过特定的调用方式(比如Windows的UEFI Runtime API)来访问它们。

我的建议:调试RT阶段的Bug是最头疼的。因为这时候系统已经进入OS,你不能随便打断点。我遇到过最离谱的一个Bug是,Windows休眠唤醒后,GetVariable返回了错误数据。查了三天,最后发现是RT段的代码在S3恢复时被覆盖了。这种问题,没有扎实的UEFI功底,根本无从下手。

4.8 总结:一张表看懂整个流程

为了方便你记忆,我把整个流程整理成了一张表:

阶段 全称 核心任务 内存状态 典型耗时
SEC Security Phase 建立CAR,验证PEI Cache作为临时内存 < 100ms
PEI Pre-EFI Initialization 初始化DRAM 从CAR切换到DRAM 100ms ~ 2s
DXE Driver Execution Environment 加载驱动,构建协议 完整DRAM 500ms ~ 3s
BDS Boot Device Selection 选择启动设备 完整DRAM < 500ms
TSL Transient System Load 执行OS加载器 完整DRAM < 1s
RT Runtime Phase 提供运行时服务 OS管理 持续运行

嗯,到这里,整个UEFI启动流程的概览就讲完了。你可能会觉得内容有点多,但没关系。后面的章节我们会逐一深入每个阶段,把每个细节都掰开揉碎了讲清楚。你只要记住这张流程图和这张表,心里就有个框架了。