1. UEFI 启动概述:UEFI 与 BIOS 的区别、启动流程与固件架构

大家好,我是这次课程的主讲。在底层固件这行摸爬滚打了十几年,我见过太多工程师一上来就扎进代码,结果被各种奇怪的问题卡住。其实,搞 UEFI 启动优化,第一步不是调参数,而是把 UEFI 到底是什么、它和 BIOS 有什么不同、它怎么跑起来的,彻底搞清楚。

说白了,UEFI 就是 BIOS 的现代替代品。但替代的不只是界面,而是整个启动哲学。今天这一章,我们就从这三个核心问题入手,把地基打牢。

1.1 UEFI 与 BIOS 的区别:从实模式到保护模式

传统 BIOS 启动,我习惯叫它“实模式下的苦力活”。CPU 一上电就进入 16 位实模式,只有 1MB 寻址空间,BIOS 代码就在这 1MB 里折腾。它先做 POST(上电自检),然后去磁盘的 0 扇区找 512 字节的 MBR 引导代码。MBR 再去找活动分区,加载 VBR,最后启动操作系统。整个过程就像接力赛,每一棒都很脆弱。

UEFI 则完全不同。它一上来就进入 32 位或 64 位保护模式,甚至长模式。这意味着什么?意味着 CPU 可以直接访问全部内存,可以用 C 语言写驱动,可以用网络协议栈。我当年第一次在 UEFI Shell 里敲命令时,感觉就像从算盘换成了计算器。

下面这个表格,是我在实际项目中总结的核心差异,你可以直接拿去用:

对比维度 传统 BIOS UEFI
CPU 模式 16 位实模式 32/64 位保护模式/长模式
寻址空间 1MB(受限严重) 4GB 或更大(平坦内存模型)
引导方式 MBR + 活动分区 GPT + EFI 系统分区(ESP)
磁盘分区 MBR(最大 2TB,4 个主分区) GPT(最大 9.4ZB,128 个分区)
驱动模型 Option ROM,16 位代码 EFI 驱动,C 语言编写,可加载/卸载
用户界面 文本菜单,键盘操作 图形界面,支持鼠标、触摸
网络支持 基本没有 内置 HTTP、iSCSI、PXE 协议栈
安全性 无 Secure Boot、TPM 集成

核心观点:UEFI 不是 BIOS 的“升级版”,而是一个全新的固件接口规范。它把操作系统从底层硬件细节中解放出来,让启动过程变得模块化、可扩展、可优化。

1.2 UEFI 启动流程:从按下电源键到操作系统接管

UEFI 的启动流程,我把它拆成 7 个阶段。每个阶段都有明确的入口和出口,这也是我们后续做性能优化的切入点。

  1. SEC(安全验证阶段):CPU 复位后的第一段代码。它初始化缓存作为临时内存(CAR),验证下一个阶段(PEI)的镜像是否可信。嗯,这里要注意,SEC 阶段通常只有几百条指令,但它是整个信任链的根。
  2. PEI(EFI 前初始化阶段):主要做两件事——初始化内存控制器,让主存可用;发现并启动 CPU、芯片组等核心硬件。PEI 阶段运行在临时内存上,代码量很小,但很关键。
  3. DXE(驱动执行环境阶段):这是 UEFI 固件的主体。DXE 调度器会遍历所有固件卷(FV),加载并执行各种驱动:PCI 枚举、SATA 控制器、USB 控制器、网络协议栈……说白了,这个阶段把系统从“裸机”变成了“可用的平台”。
  4. BDS(启动设备选择阶段):DXE 完成后,控制权交给 BDS。BDS 负责加载启动管理器,显示启动菜单(如果有),然后按照启动顺序尝试加载操作系统引导器。
  5. TSL(临时系统加载阶段):操作系统引导器(如 Windows Boot Manager、GRUB)开始运行。它加载内核和初始驱动,然后准备退出 UEFI 环境。
  6. RT(运行时阶段):操作系统内核接管硬件控制权,UEFI 进入运行时模式。此时 UEFI 只提供一组运行时服务(如设置变量、获取时间),不再控制硬件。
  7. AL(灾难恢复阶段):如果系统在启动过程中发生严重错误,会进入此阶段。通常表现为固件恢复界面或错误提示。

为了让你更直观地理解这个流程,我画了一张图:

UEFI 启动流程 7 阶段 UEFI 启动流程(7 阶段) SEC 安全验证 PEI 内存初始化 DXE 驱动执行 BDS 启动选择 TSL OS 加载 RT 运行时 AL(灾难恢复) 说明: • SEC→PEI→DXE→BDS→TSL→RT 是正常启动路径。 • 任何阶段发生不可恢复错误,都会跳转到 AL 阶段。 • 性能优化的重点在 PEI、DXE、BDS 三个阶段。 • 我建议你记住这个流程,后面每一章都会用到。

我的经验:在 DXE 阶段,驱动加载顺序对启动时间影响巨大。我曾经在一个项目中,把某个 USB 驱动从“按需加载”改为“延迟加载”,启动时间直接缩短了 1.2 秒。你想想看,1.2 秒在用户体验上就是“快”和“慢”的区别。

1.3 UEFI 固件架构概览:模块化与协议驱动

UEFI 固件的架构,说白了就是一套“模块化 + 协议驱动”的框架。它不像 BIOS 那样把所有代码揉在一起,而是把功能拆成一个个独立的模块,模块之间通过协议(Protocol)通信。

我习惯把 UEFI 固件分成三层:

  • 硬件抽象层(HAL):直接操作 CPU、芯片组、内存控制器。这一层通常由芯片厂商(Intel、AMD)提供,我们一般不动它。
  • 核心服务层:包括启动服务(Boot Services)和运行时服务(Runtime Services)。启动服务只在操作系统加载前可用,运行时服务则一直可用。比如分配内存、加载驱动、设置变量,都是通过这两组服务完成的。
  • 驱动与应用层:各种设备驱动(磁盘、网络、显示)和 UEFI 应用(Shell、启动管理器、固件设置界面)。这一层是我们做优化时最常接触的。

下面这个代码片段,展示了一个最简单的 UEFI 驱动入口。你一看就明白:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiDriverEntryPoint.h>

EFI_STATUS
EFIAPI
MyDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  // 打印一条消息到控制台
  Print(L"Hello from my UEFI driver!\n");

  // 注册一个协议,让其他模块可以找到我
  // 这里省略了具体协议 GUID 和接口

  return EFI_SUCCESS;
}

你可能会问:“为什么 UEFI 要用协议?” 我举个例子。在传统 BIOS 里,如果你想访问磁盘,你得知道中断向量 0x13 怎么用。但在 UEFI 里,你只需要找到 EFI_DISK_IO_PROTOCOL 这个协议,调用它的 ReadDisk() 函数就行。协议就是接口,模块就是实现。这种设计让代码复用变得极其简单。

避坑指南:我曾经在调试一个启动慢的问题时,发现某个驱动在 DXE 阶段反复加载了 3 次。原因就是它注册的协议 GUID 写错了,导致调度器认为它是不同的驱动。记住:协议 GUID 必须全局唯一,不能随便复制粘贴。

最后,我想强调一点:UEFI 固件不是操作系统,但它提供了操作系统所需的所有基础设施。理解这一点,你就能明白为什么 UEFI 启动优化如此重要——你优化的不只是固件本身,而是整个系统的启动体验。