4. UEFI启动流程总览:从按下电源键到操作系统加载的完整路径
按下电源键的那一刻,机器里发生了什么?
很多人觉得,开机就是BIOS自检,然后引导系统。但说实话,UEFI时代这套流程已经完全不同了。我最早接触UEFI时也犯过迷糊——明明看到屏幕亮了,系统却卡在某处不动,连个报错都没有。后来才明白,这中间藏着好几个关键阶段。
今天我就带你走一遍完整的UEFI启动路径。从按下电源键,到操作系统接管控制权,每一步我都会告诉你:谁在执行、执行什么、为什么这么设计。
核心观点:UEFI启动不是一条直线,而是一个分阶段、可扩展的框架。每个阶段都有明确的责任边界,并且可以通过驱动和协议动态组合。
4.1 启动的五个阶段
UEFI规范把启动过程划分为七个阶段,但实际开发中我们通常关注五个核心阶段。我习惯把它们记成:SEC → PEI → DXE → BDS → TSL。你看,缩写连起来就是一句话:Security, Platform, Driver, Boot, System Load。
| 阶段 | 全称 | 主要任务 | 谁在执行 |
|---|---|---|---|
| SEC | Security (安全验证) | 验证固件完整性,建立临时运行环境 | CPU + 固件ROM中的微码 |
| PEI | Pre-EFI Initialization | 初始化内存、检测基本硬件 | PEI Core + PEIM |
| DXE | Driver Execution Environment | 加载UEFI驱动,枚举设备 | DXE Core + UEFI Drivers |
| BDS | Boot Device Selection | 选择启动设备,加载启动管理器 | BDS Manager |
| TSL | Transient System Load | 加载操作系统引导程序 | OS Boot Loader |
嗯,这里要注意:RT (Runtime) 和 AL (After Life) 两个阶段是操作系统接管后的状态,我们放到后面章节再细讲。
4.2 SEC阶段:信任的起点
按下电源键,CPU复位,第一条指令从固件ROM中取出。这时候内存还没初始化,连栈都没有。你想想看,代码在什么环境下运行?
答案是:Cache As RAM (CAR)。CPU内部缓存被临时当作内存用。我在调试一个国产平台时,就遇到过CAR配置错误导致SEC阶段直接死循环的问题——屏幕全黑,连Debug口都没输出。后来发现是MTRR寄存器没设对。
SEC阶段的核心任务有三件:
- 验证Boot Firmware Volume (BFV) 的签名——确保固件没被篡改
- 建立临时内存 (CAR)——让后续代码有地方放栈和变量
- 找到PEI Core入口——把控制权交给下一阶段
避坑指南:我曾经在移植UEFI固件到新CPU时,忽略了SEC阶段的Cache配置。结果PEI阶段一访问内存就挂。折腾了两天才发现,是CAR的MTRR掩码写错了,导致缓存覆盖了非连续地址。记住:SEC阶段的CAR配置必须精确匹配CPU的缓存拓扑。
4.3 PEI阶段:把内存“变”出来
SEC阶段结束后,系统还处于“无内存”状态。PEI阶段的第一件事,就是初始化内存控制器,把真正的DRAM激活。
PEI阶段由 PEI Core 和一堆 PEIM (PEI Module) 组成。PEI Core负责调度,PEIM负责干活。常见的PEIM包括:
- CPU PEIM:初始化CPU微码、设置MSR
- Memory PEIM:训练内存、配置SPD
- Platform PEIM:配置芯片组、GPIO、时钟
我印象最深的一次,是在某款笔记本上,Memory PEIM训练DDR5花了将近3秒。用户觉得开机慢,但其实是内存频率太高,需要反复校准。后来我们加了一个“快速启动”选项,跳过完整训练,只恢复上次保存的参数——开机时间直接砍半。
小技巧:PEI阶段可以通过 HOB (Hand-Off Block) 把数据传给DXE阶段。比如内存大小、CPU信息、启动原因(冷启动/热启动/唤醒)。如果你在DXE阶段发现某些信息丢了,多半是HOB没传对。
4.4 DXE阶段:驱动大爆发
内存有了,DXE阶段就可以放开手脚了。这个阶段的核心是 DXE Core 和 UEFI Driver Model。
DXE Core会扫描所有固件卷 (FV),找到并加载UEFI驱动。这些驱动按依赖关系排序,先加载基础驱动(如PCI总线驱动),再加载设备驱动(如网卡、硬盘、显卡)。
说白了,DXE阶段就是“搭积木”。每个驱动提供一个或多个 UEFI Protocol,其他驱动通过Protocol接口调用服务。比如:
- PCI Bus Driver 枚举PCI设备,产生
gEfiPciIoProtocolGuid - Disk I/O Driver 识别分区表,产生
gEfiDiskIoProtocolGuid - Simple File System Driver 挂载文件系统,产生
gEfiSimpleFileSystemProtocolGuid
// 一个典型的UEFI驱动入口
EFI_STATUS
EFIAPI
MyDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
// 注册驱动绑定协议
return gBS->InstallMultipleProtocolInterfaces(
&ImageHandle,
&gEfiDriverBindingProtocolGuid,
&mMyDriverBinding,
NULL
);
}
你可能会问:这么多驱动,加载顺序怎么保证?答案是 Dependency Expression。每个驱动都声明自己依赖哪些Protocol,DXE Core按拓扑排序加载。如果某个驱动依赖的Protocol还没出现,它就等着——直到被其他驱动满足。
4.5 BDS阶段:选谁启动?
DXE阶段把所有硬件都枚举完了,接下来BDS阶段决定:从哪个设备启动?
BDS Manager会读取 Boot Manager Menu 中的启动项列表。每个启动项包含:
- 设备路径:比如
PciRoot(0)/Pci(0x1F,0x2)/Sata(0x0,0x0)/HD(1,MBR,0x1234) - 启动选项:比如
EFI\BOOT\BOOTX64.EFI - 优先级:数字越小越优先
如果所有启动项都失败了,BDS会回退到 UEFI Shell 或者显示“No bootable device”。
实际经验:我遇到过一台服务器,每次重启都进Shell。查了半天,发现是NVRAM里的BootOrder被清空了。原因是CMOS电池没电,导致UEFI变量丢失。解决方案很简单:换电池,或者把默认启动项写死在固件里。
4.6 TSL阶段:操作系统接管
BDS选定了启动设备,加载了OS Boot Loader(比如GRUB2、Windows Boot Manager)。这时候系统进入TSL阶段。
OS Boot Loader会做几件事:
- 调用
ExitBootServices()——告诉UEFI:我要接管了,你把启动服务关掉 - 加载操作系统内核到内存
- 跳转到内核入口点
注意:ExitBootServices() 是一个不可逆操作。一旦调用,UEFI的启动服务(如内存分配、Protocol查找)全部失效,只剩下Runtime Service(如设置时间、重启、关机)。
我调试过一个Bug:某Linux发行版在ExitBootServices之后,系统直接死机。后来发现是UEFI固件在退出前没有关闭DMA,导致设备还在往内存写数据,覆盖了内核。解决方案是在ExitBootServices之前,先停掉所有Bus Master设备。
4.7 完整流程图
下面这张图,把整个流程串起来了。你可以对照着看,每个阶段做了什么,数据怎么传递。
4.8 几个容易踩的坑
最后,我总结几个实际项目中常见的坑,你遇到了可以少走弯路:
| 问题 | 原因 | 解决思路 |
|---|---|---|
| SEC阶段死机,无任何输出 | CAR配置错误,或BFV签名验证失败 | 检查MTRR寄存器,确认固件签名 |
| PEI阶段卡在内存训练 | SPD数据异常,或内存频率不匹配 | 用串口Debug输出,查看训练状态码 |
| DXE阶段找不到硬盘 | PCI枚举失败,或驱动依赖未满足 | 检查PCIe链路状态,确认驱动加载顺序 |
| BDS阶段启动项为空 | NVRAM损坏,或BootOrder被清空 | 恢复默认启动项,检查CMOS电池 |
| ExitBootServices后死机 | DMA未关闭,或中断未屏蔽 | 在退出前停掉所有Bus Master设备 |
好了,这就是UEFI启动的完整路径。从按下电源键到操作系统加载,每一步都有它的设计逻辑。你掌握了这个框架,后面再深入每个阶段时,就不会迷路了。