UEFI固件基础:固件类型、存储布局与文件系统
好,我们正式开始第二讲。上一章我们聊了UEFI的宏观定位,这一章得扎到固件内部去看看。说白了,就是搞清楚UEFI固件到底长什么样,由哪些阶段组成,代码和数据又是怎么塞进Flash里的。
我个人习惯把UEFI固件理解成一个「微型操作系统」。它有自己的启动流程、内存管理、甚至文件系统。嗯,这个文件系统就是FFS(Firmware File System)。你想想看,BIOS的Flash空间就那么几兆到几十兆,怎么高效地组织成千上万个模块?这就是我们今天要讲的核心。
2.1 固件类型:PI、PEI、DXE、BDS
UEFI规范定义了一套标准的启动阶段。我当年刚接触时,总觉得这些阶段名字很绕。后来在项目里调过几次启动失败的问题,才真正理解它们各自的分工。
先看这张图,把整体流程串起来:
2.2 各阶段深度解析
SEC(安全验证阶段)
这是CPU复位后执行的第一段代码。空间极小,通常只有几个KB。它的任务很简单:验证下一个阶段(PEI)的镜像是否完整,然后跳转。我在一个国产平台项目里遇到过,SEC阶段代码里有个栈指针初始化错了,导致PEI阶段一调用函数就崩。查了两天才定位到,嗯,从那以后我写SEC代码都会反复检查栈布局。
PEI(EFI前初始化阶段)
PEI的核心目标是:把内存初始化出来。你想想看,没有内存,C语言跑不了,复杂逻辑写不了。PEI阶段运行在所谓的「临时内存」上——通常是CPU的Cache配置成RAM(CAR,Cache As RAM)。
- PEI Foundation:核心调度器,负责分发PPI(PEIM-to-PEIM Interface)
- PEIM(PEI Module):一个个功能模块,比如内存控制器初始化、CPU微码更新
- HOB(Hand-Off Block):PEI阶段产出的数据结构,传递给DXE。我建议你把它理解成「接力棒」
关键认知:PEI阶段结束时,系统必须拥有完整的系统内存。HOB列表里必须包含内存描述符,否则DXE无法启动。
DXE(驱动执行环境)
DXE是UEFI固件里最庞大的阶段。它运行在完整的内存环境中,可以加载复杂的驱动。DXE的核心机制是「协议(Protocol)」——每个驱动注册自己支持的协议,其他驱动通过协议来调用服务。
我记得有一次调试一个NVMe驱动加载失败的问题。DXE阶段枚举了所有设备,但NVMe控制器始终没有绑定上驱动。最后发现是某个Protocol的GUID在驱动里写错了。一个字节的差异,查了我整整一个下午。
| 阶段 | 内存状态 | 核心机制 | 产出物 |
|---|---|---|---|
| SEC | 无(直接操作寄存器) | 安全验证、跳转 | SEC映像哈希值 |
| PEI | Cache as RAM(CAR) | PPI、HOB | HOB列表 |
| DXE | 完整系统内存 | Protocol、Handle Database | Boot Manager就绪 |
| BDS | 完整系统内存 | Boot Policy、Driver Order | 启动项选择 |
BDS(启动设备选择阶段)
BDS阶段说白了就是「决定从谁启动」。它读取固件设置中的启动顺序,依次尝试每个启动项。如果某个启动项失败(比如硬盘没插好),就跳到下一个。
这里有个常见的坑:BDS阶段可能会因为某个USB设备枚举超时,导致整个启动流程卡住几十秒。我曾经在一个工控项目里遇到过,客户抱怨开机太慢。最后发现是BDS阶段枚举了一个坏掉的USB Hub,每次都要等到超时才会跳过。
避坑指南:BDS阶段的超时处理一定要做容错。我曾经见过一个固件,因为某个启动项的设备返回了错误状态,BDS直接死循环了。加一个「最大重试次数」就能解决。
2.3 固件存储布局:Firmware Volume(FV)
好,阶段讲完了。那这些代码和数据存在哪里?答案是Firmware Volume(固件卷,简称FV)。
FV是Flash上的一个逻辑容器。你可以把它想象成一个「分区」。一个Flash里可以有多个FV,每个FV有自己的GUID、大小、对齐方式。FV内部存放的是FFS文件。
FV的头部结构长这样:
typedef struct {
UINT8 ZeroVector[16]; // 必须全零
EFI_GUID FileSystemGuid; // 文件系统类型标识
UINT64 FvLength; // FV总长度
UINT32 Signature; // '_FVH' 签名
UINT32 Attributes; // 属性位(对齐、擦除策略等)
UINT16 HeaderLength; // 头部长度
UINT16 Checksum; // 校验和
UINT8 Reserved[1];
UINT8 Revision; // 版本号
// ... 更多字段
} EFI_FIRMWARE_VOLUME_HEADER;
我个人习惯把FV的布局画成下面这样:
2.4 固件文件系统:FFS
FFS(Firmware File System)是FV内部的组织方式。每个FFS文件包含一个头部和若干数据段(Section)。
FFS文件头结构:
typedef struct {
EFI_GUID NameGuid; // 文件唯一标识
UINT8 IntegrityCheck; // 完整性校验
UINT8 Type; // 文件类型(RAW、PE32、TE等)
UINT8 Attributes; // 属性(对齐、固定等)
UINT8 Size[3]; // 文件大小(24位)
UINT8 State; // 状态(擦除、写入、有效等)
} EFI_FFS_FILE_HEADER;
常见的文件类型:
- EFI_FV_FILETYPE_RAW:原始二进制数据,比如微码补丁
- EFI_FV_FILETYPE_PE32:PE32+格式的可执行镜像,DXE驱动常用
- EFI_FV_FILETYPE_TE:Terse Executable,一种精简的PE格式,PEI阶段常用
- EFI_FV_FILETYPE_FREEFORM:自由格式,用于存放ACPI表等
实用技巧:调试时可以用UEFI固件工具(比如UEFITool)直接打开BIOS镜像,查看FV内部的FFS文件结构。我曾经用这个工具定位过一个ACPI表被错误覆盖的问题——直接在工具里对比两个版本的固件,发现某个FFS文件的GUID重复了。
2.5 总结与核心要点
这一章的内容比较多,我帮你梳理几个必须记住的点:
- PI规范定义了框架:SEC → PEI → DXE → BDS,每个阶段有明确的分工和产出
- FV是存储容器:Flash上的逻辑分区,包含头部和FFS文件
- FFS是文件系统:每个文件有GUID、类型、状态,内部可以包含多个Section
- HOB是阶段间的桥梁:PEI产出的HOB列表,DXE和BDS都依赖它
嗯,这些概念听起来可能有点抽象。但等你真正开始写固件代码、调试启动流程时,你会发现这些设计其实非常合理。每个阶段解决一个特定问题,每个数据结构都有明确的用途。