2. UEFI 启动流程全景:从按下电源键到操作系统加载的完整旅程
大家好,我是你们的 UEFI 讲师。今天我们来聊聊整个启动流程的全景图。
很多人做了好几年 BIOS,却说不清按下电源键后到底发生了什么。我见过不少工程师,只知道改改 DXE 驱动,对 SEC 和 PEI 阶段几乎一无所知。说实话,这就像只会开车却不懂引擎原理——出了问题只能干瞪眼。
这一章,我会带你走一遍完整的启动旅程。从你按下电源键的那一刻,到操作系统拿到控制权,每一步我都会讲清楚:谁在干活、干了什么、为什么要这么干。
核心观点:UEFI 启动不是一蹴而就的。它被拆成了 7 个阶段,每个阶段都有明确的分工和边界。理解这些阶段,是裁剪和调试的基础。
2.1 启动流程的七个阶段
UEFI 规范把启动过程分成了 7 个阶段。我习惯把它们分成三组:
- 硬件初始化组:SEC(安全验证)、PEI(EFI 前期初始化)
- 固件核心组:DXE(驱动执行环境)、BDS(启动设备选择)
- 系统交接组:TSL(短暂系统加载)、RT(运行时)、AL(灾难恢复)
你想想看,为什么需要这么多阶段?说白了,因为硬件是从“死”到“活”的。刚上电时,CPU 还处于实模式,内存都没初始化,你拿什么跑 C 代码?所以必须一步步来。
我的经验:我在调试一个国产平台时,发现 PEI 阶段总是卡住。后来定位到是内存训练参数不对。如果当时没有分阶段调试的思路,我可能要在几万行代码里大海捞针。分阶段,本质上就是分而治之。
2.2 各阶段职责详解
2.2.1 SEC(安全验证阶段)
这是第一个阶段。CPU 上电后,第一条指令会跳到这里。
SEC 只做三件事:
- 建立临时内存(Cache as RAM,简称 CAR)——利用 CPU 的缓存当内存用
- 验证下一个阶段(PEI)的镜像签名——确保固件没被篡改
- 传递系统状态参数——把 CPU 的初始状态告诉 PEI
嗯,这里要注意:CAR 的大小通常只有 4KB 到 64KB。你想想看,这么点空间能干什么?所以 SEC 的代码必须极度精简,连全局变量都要省着用。
避坑指南:我曾经在一个项目中,因为 CAR 配置错误,导致 PEI 阶段栈溢出。查了整整两天才发现是 CAR 大小没算对。建议你在做移植时,第一件事就是确认 CAR 的起始地址和大小。
2.2.2 PEI(EFI 前期初始化阶段)
PEI 的核心任务就一个:把内存初始化好。
为什么这么重要?因为没有内存,你就没法解压、没法加载驱动、没法跑复杂的逻辑。PEI 阶段会做:
- 初始化内存控制器(MRC,Memory Reference Code)
- 检测并训练 DDR 颗粒
- 建立内存映射表
- 发现并启动 CPU 核心
PEI 的代码也是高度优化的。我记得有一次,为了把 PEI 体积压到 128KB 以内,我不得不手写汇编来替代 C 库函数。虽然累,但效果立竿见影。
2.2.3 DXE(驱动执行环境阶段)
内存有了,DXE 就可以大展拳脚了。这是 UEFI 最核心的阶段。
DXE 会做:
- 加载并执行各种驱动(PCI、USB、SATA、NVMe 等)
- 枚举总线设备
- 分配资源(MMIO、IRQ、DMA)
- 建立 UEFI 系统表(System Table)和启动服务(Boot Services)
我个人觉得,DXE 是 UEFI 的“灵魂”。你平时改的驱动、加的补丁,90% 都在这个阶段。
关键点:DXE 阶段结束后,系统已经具备了完整的硬件抽象层(HAL)。操作系统可以直接通过 UEFI 接口访问硬件,而不需要自己写驱动。
2.2.4 BDS(启动设备选择阶段)
BDS 负责一件事:决定从哪个设备启动。
它会:
- 读取启动顺序(Boot Order)——通常存在 NVRAM 里
- 尝试每个启动项,直到找到一个能启动的
- 加载启动项对应的启动映像(Boot Image)
你看到的“按 F12 选择启动设备”,就是 BDS 提供的功能。我建议你在调试时,先确认 Boot Order 是否正确。很多时候启动失败,只是因为 NVRAM 里的顺序乱了。
2.2.5 TSL(短暂系统加载阶段)
TSL 是 UEFI 和操作系统之间的“握手”阶段。
它会:
- 加载 OS 引导加载器(如 GRUB、Windows Boot Manager)
- 传递启动参数(如内核命令行、ACPI 表、SMBIOS 表)
- 调用 ExitBootServices()——把控制权交给 OS
这里有个坑:ExitBootServices() 一旦调用,UEFI 的启动服务就失效了。之后你不能再分配内存、不能加载驱动、不能访问 UEFI 协议。所以,所有必要的资源必须在调用前准备好。
我的习惯:在调试 TSL 阶段时,我会在 ExitBootServices() 前后各加一个串口打印。如果打印了前一句但没打印后一句,说明 OS 引导器有问题。这个方法帮我定位过不下 10 次启动失败。
2.2.6 RT(运行时阶段)
操作系统启动后,UEFI 进入运行时模式。此时,UEFI 只提供少量服务:
- 设置系统时间(GetTime/SetTime)
- 读写 NVRAM 变量
- 重置系统(ResetSystem)
说白了,UEFI 在 RT 阶段就是个“后台服务”。操作系统可以随时调用这些接口,但不能再访问固件驱动。
2.2.7 AL(灾难恢复阶段)
这是最后一道防线。如果系统启动失败,AL 会尝试恢复。
它通常:
- 显示错误信息
- 尝试从备份固件启动
- 进入恢复模式(如 BIOS 刷新模式)
说实话,AL 阶段我很少用到。但一旦用到,往往就是救命的场景。我建议你在产品中至少保留一个备份固件分区,否则刷坏了就只能拆芯片了。
2.3 启动流程全景图
下面这张图,是我自己画的启动流程全景图。它把 7 个阶段、输入输出、关键事件都串起来了。
2.4 各阶段职责对比表
为了方便你快速查阅,我把每个阶段的核心职责、关键输出、典型代码量整理成了表格。
| 阶段 | 核心职责 | 关键输出 | 典型代码量 |
|---|---|---|---|
| SEC | 建立 CAR、验证 PEI 签名 | 系统状态参数、CAR 区域 | 2-5 KB |
| PEI | 初始化内存、发现 CPU | 内存映射表、HOB 列表 | 50-200 KB |
| DXE | 加载驱动、枚举总线 | UEFI 系统表、启动服务 | 500 KB - 2 MB |
| BDS | 选择启动设备 | 启动项列表、Boot Image | 50-100 KB |
| TSL | 加载 OS 引导器 | 启动参数、ExitBootServices | 10-50 KB |
| RT | 提供运行时服务 | NVRAM、时间、重置 | 20-80 KB |
| AL | 灾难恢复 | 备份固件、恢复模式 | 10-30 KB |
我的建议:如果你在做裁剪,优先关注 DXE 和 PEI。这两个阶段占了 80% 以上的代码量。很多不必要的驱动(比如并口、软驱、旧式 USB 协议)都可以砍掉。我曾在一次裁剪中,把 DXE 从 1.8MB 压到了 600KB,启动时间缩短了 40%。
2.5 启动流程中的关键数据结构
在整个启动过程中,有几个数据结构贯穿始终。我挑三个最重要的说一下:
- HOB(Hand-Off Block):PEI 阶段产生的数据,传递给 DXE。包括内存信息、CPU 信息、启动参数等。
- UEFI System Table:DXE 阶段建立,包含所有启动服务和运行时服务的指针。操作系统通过它访问 UEFI。
- Boot Image:BDS 阶段加载的启动映像。可以是 EFI 应用(如 GRUB),也可以是传统 BIOS 的 MBR 引导。
你想想看,如果没有 HOB,PEI 和 DXE 之间怎么通信?内存信息怎么传递?所以这些数据结构,本质上就是阶段之间的“桥梁”。
避坑指南:我曾经遇到过一个 bug,DXE 阶段读取到的内存大小是错的。后来发现是 PEI 阶段生成的 HOB 里,内存描述符的 Type 字段写错了。如果你遇到类似问题,建议先 dump 一下 HOB 列表,看看数据对不对。
2.6 小结
这一章我们走完了 UEFI 启动的完整旅程。从 SEC 的 CAR 建立,到 PEI 的内存初始化,再到 DXE 的驱动加载,最后到 BDS 的启动选择、TSL 的 OS 交接,以及 RT 的运行时服务和 AL 的灾难恢复。
每个阶段都有明确的边界和职责。理解这些,你就能在遇到问题时快速定位——是内存没初始化好?还是驱动加载失败?还是启动顺序错了?