1. UEFI基础概念:UEFI与BIOS的区别、UEFI启动流程概览、UEFI固件架构
大家好,我是老周。做固件这行十几年了,今天咱们聊聊UEFI。说实话,刚入行那会儿我也觉得BIOS挺好的,干嘛要换?直到第一次被UEFI的启动速度惊到,才明白时代真的变了。
1.1 UEFI与BIOS的区别
先说说最核心的问题:UEFI和BIOS到底差在哪?
BIOS(Basic Input/Output System)是上世纪80年代的东西了。它跑在16位实模式下,只能访问1MB内存。你想想看,现在随便一个UEFI固件都几十MB,BIOS那点空间根本不够用。
UEFI(Unified Extensible Firmware Interface)是Intel在2005年左右推出来的。我参与的第一个UEFI项目是2010年,当时客户要求支持2TB以上的硬盘。BIOS的MBR分区表最大只支持2TB,而UEFI的GPT分区表可以支持到9.4ZB——这差距不是一星半点。
| 对比项 | 传统BIOS | UEFI |
|---|---|---|
| 运行模式 | 16位实模式 | 32/64位保护模式 |
| 寻址空间 | 1MB | 理论无限制 |
| 磁盘分区 | MBR(最大2TB) | GPT(最大9.4ZB) |
| 启动速度 | 慢(逐扇区扫描) | 快(直接加载EFI文件) |
| 驱动模型 | 无标准驱动接口 | EFI Driver Model |
| 网络支持 | 基本没有 | 原生UEFI网络栈 |
| 安全性 | 无 | Secure Boot |
关键点:UEFI本质上是一个微型操作系统。它有自己的驱动模型、协议栈、甚至图形界面。BIOS只是个固件,UEFI是个平台。
1.2 UEFI启动流程概览
UEFI的启动流程,说白了就是七个阶段。我习惯用SEC、PEI、DXE、BDS、TSL、RT、AL这七个缩写来记。嗯,这里要注意,不是所有平台都会走完所有阶段,但主流平台基本都这样。
我的经验:排查启动故障时,先看卡在哪个阶段。如果连PEI都没过,多半是内存问题。如果卡在BDS,检查启动设备配置。我曾经遇到一个案例,客户说机器开机黑屏,结果发现是PEI阶段内存时序配置错了。
1.3 UEFI固件架构
UEFI固件架构,我习惯把它分成三层来看:
- 底层:硬件抽象层(HAL),直接跟CPU、芯片组打交道
- 中间层:EFI协议栈,包括各种驱动和服务
- 上层:UEFI应用,比如Shell、Boot Manager、Setup界面
说白了,UEFI固件就是一个微内核操作系统。它有自己的内存管理、任务调度、协议通信机制。我刚开始学UEFI时,最不习惯的就是它的"Protocol"概念——其实就是面向对象里的接口,只不过用C语言实现了。
1.3.1 核心组件
| 组件 | 功能 | 常见问题 |
|---|---|---|
| SEC Core | 安全验证、CPU初始化 | Reset Vector配置错误 |
| PEI Core | 内存初始化、PPI调度 | 内存时序参数错误 |
| DXE Foundation | 驱动加载、协议注册 | 驱动依赖顺序问题 |
| Boot Manager | 启动项管理、设备选择 | BootOrder变量损坏 |
| Runtime Services | 操作系统运行时接口 | GetVariable返回错误 |
避坑指南:我曾经在DXE阶段踩过一个坑——驱动加载顺序错了。两个驱动都依赖同一个Protocol,但A驱动先注册了Protocol,B驱动才加载。结果A驱动在注册Protocol时还没初始化完成,B驱动拿到的是个空指针。排查了两天才找到原因。所以,驱动依赖关系一定要理清楚。
1.3.2 UEFI变量存储
UEFI变量是固件和操作系统之间的桥梁。我经常用dmpstore命令来查看变量。举个例子:
Shell> dmpstore -all
Variable: BootOrder
GUID: 8BE4DF61-93CA-11D2-AA0D-00E098032B8C
Attributes: BS|RT|NV
Data: 0000 0001 0002
Variable: Boot0000
GUID: 8BE4DF61-93CA-11D2-AA0D-00E098032B8C
Attributes: BS|RT|NV
Data: "Windows Boot Manager"
看到Attributes里的NV了吗?这表示变量是非易失的,断电后还在。如果BootOrder变量损坏了,启动就会出问题。我遇到过一台机器,每次重启都进Shell,查了半天发现是BootOrder被写成了乱码。
1.3.3 UEFI驱动模型
UEFI驱动模型跟Windows的WDM有点像。驱动通过EFI_DRIVER_BINDING_PROTOCOL来绑定设备。我简单写个驱动入口的例子:
EFI_STATUS
EFIAPI
MyDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
// 注册驱动绑定协议
Status = gBS->InstallMultipleProtocolInterfaces (
&ImageHandle,
&gEfiDriverBindingProtocolGuid,
&gMyDriverBinding,
NULL
);
return Status;
}
这段代码看着简单,但实际项目中坑不少。比如gBS是Boot Services表,只能在DXE和BDS阶段用。到了RT阶段,Boot Services就关闭了,再调用就会崩溃。
核心要点:UEFI固件架构的精髓在于"协议"和"句柄"这两个概念。协议就是接口,句柄就是对象。理解了这两个,UEFI开发就入门了。