UEFI 固件基础:规范、启动流程与架构
各位同学,欢迎来到第二章。上一章我们聊了安全启动为什么重要,这一章咱们得先把地基打牢——UEFI 到底是什么?它怎么启动的?内部长什么样?
说实话,我早年做 BIOS 开发时,UEFI 还是个新鲜玩意儿。那时候大家还在争论 Legacy BIOS 够不够用,结果 Intel 一推,整个行业就转过来了。现在回头看,UEFI 的设计确实比传统 BIOS 高明太多。
UEFI 规范简介
UEFI,全称 Unified Extensible Firmware Interface,统一可扩展固件接口。它不是一套代码,而是一份规范——定义了操作系统和固件之间的接口标准。
为什么要搞这么个东西?传统 BIOS 有几个硬伤:
- 16 位实模式:只能访问 1MB 内存,启动慢得要命
- MBR 分区:最大支持 2TB 磁盘,现在随便一个硬盘就超了
- 没有标准驱动模型:每个厂商自己玩自己的,兼容性全靠运气
- 安全机制为零:谁都能改启动项,病毒笑了
UEFI 把这些痛点全解决了。它运行在 64 位模式下,支持 GPT 分区表,有完整的驱动框架,还内置了安全启动(Secure Boot)——这也是咱们这门课的核心。
关键点:UEFI 规范由 UEFI 论坛维护,目前最新版本是 2.10。但别被版本号吓到,实际产品中 2.3.1 到 2.8 之间的版本最常见。我见过不少厂商还在用 2.4 的底子打补丁,稳定第一嘛。
UEFI 规范涵盖的内容很广,包括:
- 启动管理器(Boot Manager)
- 运行时服务(Runtime Services)
- 启动服务(Boot Services)
- 协议接口(Protocols)
- 固件文件系统(Firmware File System)
- 安全启动(Secure Boot)
嗯,这里要注意:安全启动只是 UEFI 规范里的一章,但这一章能决定整个系统的生死。
UEFI 启动流程
咱们来看看一台 UEFI 机器从按下电源键到进入操作系统,到底经历了什么。我画了一张流程图,方便你理解。
这张图里每个阶段都有明确的分工:
| 阶段 | 全称 | 主要工作 | 安全启动相关 |
|---|---|---|---|
| SEC | Security | CPU 初始化,建立临时内存 | 验证固件卷的完整性 |
| PEI | Pre-EFI Initialization | 检测内存、芯片组早期配置 | 验证 DXE 基础模块签名 |
| DXE | Driver Execution Environment | 加载驱动、枚举设备 | 验证所有 DXE 驱动签名 |
| BDS | Boot Device Selection | 选择启动设备,加载启动项 | 验证启动管理器签名 |
| TSL | Transient System Load | 加载 OS Loader | 验证 OS Loader 签名 |
| RT | Runtime | 操作系统运行,UEFI 进入后台 | 运行时服务仍可调用 |
个人经验:我在调试某款国产服务器时,遇到过 PEI 阶段卡死的问题。查了两天才发现是内存 SPD 数据读取超时,导致 PEI 模块无法完成初始化。这种问题在 UEFI 开发中很常见——硬件时序问题往往比软件 bug 更难定位。
UEFI 固件架构
UEFI 固件不是一个大泥球,它是有层次结构的。我习惯把它分成三层来看:
- 硬件抽象层:封装 CPU、芯片组、主板的差异
- 核心框架层:提供启动服务、运行时服务、协议管理
- 应用/驱动层:UEFI 应用、Option ROM、OS Loader
说白了,UEFI 就是一个微型操作系统。它有内核(DXE Core)、有驱动模型(UEFI Drivers)、有文件系统(FAT12/16/32),甚至还有网络栈。
咱们重点看几个关键组件:
1. 启动服务(Boot Services)
启动服务在操作系统接管前可用,包括:
- 内存分配与释放
- 事件与定时器管理
- 协议接口的安装与查找
- 映像加载与启动
操作系统启动后,这些服务就失效了。ExitBootServices() 一调用,UEFI 就把控制权交出去了。
2. 运行时服务(Runtime Services)
运行时服务在操作系统运行期间仍然可用,包括:
- 获取时间与设置时间
- 重置系统
- 变量存储(NVRAM)
- 安全启动策略查询
这里有个坑:运行时服务必须用虚拟地址映射,操作系统会通过 SetVirtualAddressMap() 告诉 UEFI 新的地址映射关系。我见过不少固件在这个环节出问题,导致系统休眠唤醒后蓝屏。
3. 协议(Protocols)
UEFI 协议就是接口契约。你想访问某个硬件?先找到对应的协议。比如:
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL— 文件系统访问EFI_BLOCK_IO_PROTOCOL— 块设备读写EFI_LOADED_IMAGE_PROTOCOL— 已加载映像的信息EFI_SECURE_BOOT_PROTOCOL— 安全启动策略控制
避坑指南:我曾经在移植 UEFI 网络栈时,发现某个网卡驱动没有正确安装 EFI_SIMPLE_NETWORK_PROTOCOL,导致 PXE 启动失败。排查了半天,最后发现是驱动里少了一句 gBS->InstallProtocolInterface() 调用。这种低级错误在开发初期特别容易犯,建议每次写完驱动都检查协议是否安装成功。
4. 固件文件系统(Firmware File System, FFS)
UEFI 固件本身存储在 SPI Flash 中,文件系统格式是 FFS。每个文件有 GUID 标识,分为多种类型:
| 文件类型 | GUID 示例 | 说明 |
|---|---|---|
| PEIM | PEI 模块 | PEI 阶段执行的代码 |
| DXE | DXE 驱动 | DXE 阶段加载的驱动 |
| FV | 固件卷 | 包含多个文件的容器 |
| RAW | 原始数据 | Logo、证书、配置数据 |
| SECURITY_CORE | 安全核心 | 安全启动相关模块 |
嗯,这里要注意:安全启动的证书和签名数据库(db、dbx、KEK、PK)就存储在 FFS 的 RAW 文件中,或者放在 NVRAM 变量里。具体位置取决于厂商实现,但标准做法是两者都存——NVRAM 用于运行时查询,FFS 用于固件更新时的备份。
UEFI 与安全启动的关系
安全启动不是独立于 UEFI 的附加功能,它深深嵌入了 UEFI 的每个阶段。从 SEC 阶段验证固件卷的哈希,到 BDS 阶段验证启动管理器的签名,再到 TSL 阶段验证 OS Loader 的证书——安全启动贯穿了整个启动链。
我个人的理解是:UEFI 提供了舞台,安全启动是舞台上的保安。保安从演员入场就开始检查,直到演出结束。任何一个环节出问题,保安都会拉闸。
核心观点:没有 UEFI 的标准化接口,安全启动就无从谈起。反过来,没有安全启动,UEFI 的灵活性反而成了攻击面。两者相辅相成,缺一不可。