3. UEFI启动流程总览:从按下电源键到操作系统加载的完整路径
各位同学,今天我们来聊聊UEFI启动的完整路径。说白了,就是从你按下电源键那一刻起,到操作系统跑起来之前,这中间到底发生了什么。
我刚开始做UEFI开发时,总觉得这玩意儿就是个黑盒子。后来踩了不少坑,才慢慢把这条路径摸清楚。今天我就把这条路径拆开给你看,每个阶段干什么、谁负责、容易出什么问题,咱们一次讲透。
核心观点:UEFI启动不是一蹴而就的,它是一条流水线。每个阶段都有明确的职责边界,上一个阶段干完活,把接力棒交给下一个阶段。理解这条流水线,你就能定位90%的启动问题。
3.1 启动流程的七个阶段
UEFI规范把启动过程分成了七个阶段,用首字母缩写叫 SEC-PEI-DXE-BDS-TSL-RT-AL。嗯,这串字母看着有点吓人,但你记住一个规律就行:越靠前的阶段,代码越简单、环境越原始;越靠后的阶段,功能越丰富、越接近操作系统。
我习惯把这七个阶段分成三组来理解:
- 硬件初始化组:SEC → PEI → DXE。负责把CPU、内存、芯片组这些基础硬件初始化好。
- 启动策略组:BDS → TSL。负责决定从哪个设备启动、怎么加载操作系统。
- 运行时组:RT → AL。负责把控制权交给操作系统,UEFI自己退居幕后。
下面这张图把整个流程串起来了,建议你多看几遍:
3.2 各阶段职责详解
3.2.1 SEC阶段——安全验证与初始环境
SEC是第一个执行的阶段。这时候CPU刚复位,内存还没初始化,代码只能在CPU的Cache里运行。你想想看,连内存都没有,能干什么?
SEC的主要任务就三件事:
- 验证Boot Firmware的完整性——检查固件有没有被篡改。我遇到过一台设备,SEC校验失败直接死循环,最后发现是Flash芯片坏了。
- 建立临时内存——利用CPU的Cache作为RAM(CAR,Cache As RAM)。没有这一步,PEI阶段连栈都建不起来。
- 找到PEI核心入口——把控制权交给PEI阶段。
避坑指南:我曾经在调试一块开发板时,SEC阶段总是卡住。后来发现是CAR配置错了——Cache大小没对齐,导致栈溢出。记住:CAR的大小必须是Cache Line的整数倍,通常是64字节对齐。
3.2.2 PEI阶段——内存初始化
PEI阶段的核心目标就一个:把内存(DRAM)初始化好。为什么这事儿这么重要?因为后面的DXE阶段需要大量内存来加载驱动,没有内存寸步难行。
PEI阶段的工作流程大致如下:
- PEI核心(PEI Core)被SEC加载,开始执行。
- PEI模块(PEIM)逐个执行,每个模块负责一小块硬件初始化。
- 最关键的是内存初始化模块——它会配置内存控制器、训练内存时序。
- 内存初始化完成后,PEI阶段把内存信息传递给DXE阶段。
这里有个细节:PEI阶段的代码仍然在Flash里执行,但有了CAR作为临时内存,可以调用函数、使用局部变量了。
注意:内存初始化是UEFI启动中最容易出问题的环节。我见过因为内存颗粒焊接不良导致PEI阶段反复重启的案例。如果你在调试时发现启动卡在PEI阶段,优先检查内存供电、时钟信号和时序参数。
3.2.3 DXE阶段——驱动执行
DXE阶段是UEFI启动中最热闹的阶段。内存有了,各种驱动开始粉墨登场。
DXE阶段的核心机制是:
- DXE核心(DXE Core)启动,建立系统服务表(System Table)。
- DXE驱动(DXE Driver)被逐个加载和执行。每个驱动负责初始化一个硬件设备,比如PCIe总线、USB控制器、SATA控制器等。
- 驱动之间通过协议(Protocol)通信。比如一个驱动初始化了PCIe总线,就会安装一个PCIe IO协议,其他驱动可以通过这个协议访问PCIe设备。
我习惯把DXE阶段比作「搭积木」——每个驱动是一块积木,积木之间通过协议接口拼接。这种设计的好处是模块化,坏处是……嗯,如果某个驱动没装好,后面的积木就搭不上。
| 阶段 | 内存状态 | 代码执行位置 | 主要功能 |
|---|---|---|---|
| SEC | 无(使用CAR) | Flash | 安全验证、建立临时内存 |
| PEI | CAR + 逐步初始化DRAM | Flash | 内存初始化、硬件早期配置 |
| DXE | DRAM可用 | Flash + DRAM | 驱动加载、协议安装、硬件枚举 |
| BDS | DRAM可用 | DRAM | 启动策略选择、设备枚举 |
| TSL | DRAM可用 | DRAM | OS加载器执行 |
| RT | DRAM可用 | DRAM | 退出启动服务、移交控制权 |
3.2.4 BDS阶段——启动策略
BDS阶段是UEFI启动的「决策者」。DXE阶段把所有硬件都初始化好了,BDS阶段来决定:从哪个设备启动?
BDS的工作流程:
- 读取UEFI启动管理器(Boot Manager)的配置。
- 按照启动顺序(Boot Order)逐个尝试启动设备。
- 对于每个设备,查找其上的EFI系统分区(ESP),寻找可启动的EFI应用。
- 找到第一个可启动的应用后,加载并执行它。
这里有个常见的坑:启动顺序配置错了,系统就会从错误的设备启动。我曾经帮一个客户排查问题,他的服务器每次启动都进Shell而不是进系统,最后发现是Boot Order里Shell排在了硬盘前面。
3.2.5 TSL阶段——OS加载器
TSL阶段就是操作系统加载器(比如Windows的bootmgfw.efi、Linux的grubx64.efi)执行的阶段。
OS加载器会做几件事:
- 调用UEFI的协议接口,读取文件系统、加载内核镜像。
- 准备操作系统需要的启动参数。
- 最后调用
ExitBootServices(),告诉UEFI:我要接管了,你可以退下了。
关键点:ExitBootServices() 是UEFI启动的分水岭。调用之前,UEFI还掌握着硬件控制权;调用之后,UEFI只保留运行时服务(RT Service),操作系统开始直接管理硬件。
3.2.6 RT阶段——运行时服务
RT阶段是UEFI的「退休生活」。操作系统接管后,UEFI的大部分服务都关闭了,只保留少数运行时服务:
- SetVariable / GetVariable——读写UEFI变量(比如启动配置)。
- GetTime / SetTime——读取和设置系统时间。
- ResetSystem——重启或关机。
这些服务在操作系统运行期间仍然可用。比如你在Windows里修改启动顺序,实际上就是在调用UEFI的 SetVariable。
3.2.7 AL阶段——操作系统加载完成
AL阶段严格来说不算UEFI阶段了,它是操作系统完全加载后的状态。UEFI的使命到此结束,剩下的都是操作系统的事了。
3.3 各阶段的时间分布
在实际项目中,启动时间是个敏感指标。我统计过一些典型设备的启动时间分布:
| 阶段 | 典型耗时 | 优化空间 |
|---|---|---|
| SEC | 10-50ms | 小(主要是硬件等待) |
| PEI | 100-500ms | 中(内存训练时间可优化) |
| DXE | 200-2000ms | 大(驱动加载顺序、并行化) |
| BDS | 50-300ms | 中(启动顺序、超时设置) |
| TSL | 500-5000ms | 大(OS加载器效率) |
你看,DXE和TSL是耗时大户。如果你需要优化启动时间,优先从这两个阶段下手。
个人经验:我优化过一台嵌入式设备的启动时间,从12秒压到了3秒。主要手段是:DXE阶段把不必要的驱动延迟加载,BDS阶段缩短超时时间,TSL阶段用更轻量的OS加载器。每一步都不难,但组合起来效果惊人。
3.4 总结
UEFI启动流程,说白了就是一条从硬件到软件的接力赛。SEC起跑,PEI热身,DXE加速,BDS选方向,TSL冲刺,RT交接,AL冲线。
每个阶段都有明确的职责,也有常见的坑。你只要记住:越早的阶段越接近硬件,越晚的阶段越接近操作系统。遇到启动问题,先判断卡在哪个阶段,然后对症下药。