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

大家好,我是你们的UEFI讲师。今天我们来聊聊UEFI启动流程中最核心的四个阶段:PEI、DXE、BDS和RT。说白了,这就是从你按下电源键到操作系统完全接管硬件之间,固件到底干了些什么。

我刚开始接触UEFI时,总觉得这些阶段划分得很抽象。后来在项目中调试一次启动失败的问题,才真正理解了每个阶段存在的意义。嗯,咱们一个一个来看。

核心要点:UEFI启动流程分为四个阶段——PEI(平台初始化)、DXE(驱动执行环境)、BDS(启动设备选择)、RT(运行时服务)。每个阶段都有明确的分工和边界。

UEFI 启动流程四阶段 PEI 平台初始化 DXE 驱动执行环境 BDS 启动设备选择 RT 运行时服务 各阶段核心职责 • PEI:最小硬件初始化,发现内存并初始化CPU • DXE:加载并执行所有UEFI驱动,构建完整的系统服务 • BDS:枚举启动设备,根据BootOrder选择启动项 • RT:操作系统接管后,固件保留的服务接口

2.1 PEI阶段:从零开始的硬件发现

PEI(Pre-EFI Initialization)是整个启动流程的第一步。这时候内存还没初始化,CPU可能还处于复位状态。你想想看,固件代码本身都还在Flash里跑,连个像样的栈都没有。

PEI阶段的核心任务就两件:发现内存和初始化CPU。为什么内存这么重要?因为后续所有阶段都需要内存来运行代码。没有内存,DXE阶段的驱动根本加载不了。

个人经验:我在调试一块国产主板时,遇到过PEI阶段卡死的问题。最后发现是SPD(Serial Presence Detect)数据读取超时,导致内存参数获取失败。PEI阶段的容错处理其实很有限,一旦内存初始化失败,整个机器就变砖了。

PEI阶段的工作模式是这样的:

  • SEC(Security):安全验证,检查固件完整性。这部分其实在PEI之前,但通常和PEI一起讲。
  • PEI Core:加载PEIM(PEI Module),调度执行。
  • PEIM:具体的初始化模块,比如内存控制器初始化、CPU微码加载。
  • PEI Services:提供基础服务接口,比如HOB(Hand-Off Block)的创建。

PEI阶段结束后,会生成一个关键数据结构——HOB。HOB记录了所有已发现的硬件信息,传递给DXE阶段。我习惯把HOB比作「接力棒」,PEI跑完第一棒,把硬件信息写在HOB里,DXE接过来继续跑。

注意:PEI阶段的内存非常有限,通常只有几十KB的临时内存(Cache as RAM,简称CAR)。不要在PEI阶段做大量数据操作,否则很容易栈溢出。我曾经见过一个同事在PEIM里申请了8KB的缓冲区,直接把CAR撑爆了,机器反复重启。

2.2 DXE阶段:驱动的大舞台

DXE(Driver Execution Environment)阶段,说白了就是UEFI驱动的「主战场」。这时候内存已经可用,固件可以加载各种驱动了。

DXE阶段的核心是DXE Dispatcher,它负责扫描所有固件卷(FV,Firmware Volume)中的驱动,按照依赖关系依次加载执行。每个驱动注册自己的Protocol,供其他驱动调用。

我举个例子:

// 一个典型的DXE驱动入口
EFI_STATUS
EFIAPI
MyDriverEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  // 注册Protocol
  gBS->InstallProtocolInterface(
    &ImageHandle,
    &gMyProtocolGuid,
    EFI_NATIVE_INTERFACE,
    &mMyProtocolInstance
  );
  
  return EFI_SUCCESS;
}

这段代码看起来简单,但背后涉及很多细节。比如gBS是Boot Services表,它提供了内存分配、事件管理、Protocol注册等核心服务。DXE阶段的所有驱动都依赖这些服务。

DXE阶段结束时,系统应该已经具备了完整的硬件抽象层。所有关键硬件(磁盘、USB、PCIe、网络等)的驱动都已加载,Protocol都已注册。这时候,BDS阶段就可以上场了。

DXE核心组件 功能描述 我踩过的坑
DXE Dispatcher 按依赖顺序加载驱动 依赖循环导致死锁,需要手动调整DEPEX
Boot Services 内存、事件、Protocol管理 忘记释放内存导致SMM泄露
Runtime Services 操作系统可调用的固件接口 SetVariable在RT阶段调用失败
Protocol 驱动间通信的接口标准 Protocol GUID写错,驱动找不到对方

2.3 BDS阶段:选谁启动?

BDS(Boot Device Selection)阶段,名字已经说得很清楚了——选择启动设备。DXE阶段把所有驱动都准备好了,BDS阶段要做的就是枚举所有可启动设备,然后按照用户配置的启动顺序去尝试启动。

BDS阶段的核心流程:

  1. 枚举启动设备:扫描所有磁盘、USB、网络等设备,检查是否有可启动的EFI分区。
  2. 读取BootOrder:从NVRAM中读取用户配置的启动顺序。
  3. 尝试启动:按顺序尝试每个启动项,直到成功或全部失败。
  4. 加载Boot Manager:如果找到可启动设备,加载其EFI应用程序(比如bootmgfw.efi)。

你可能会问:如果所有启动项都失败了呢?嗯,这时候BDS会进入Boot Manager Menu,也就是我们常说的BIOS设置界面。用户可以在里面调整启动顺序、修改硬件配置。

关键概念:BDS阶段结束后,控制权会交给操作系统的Boot Loader。此时UEFI固件的大部分功能会退出,只保留Runtime Services供操作系统使用。

2.4 RT阶段:固件的「售后服务」

RT(Runtime Services)阶段,是UEFI固件在操作系统运行期间提供的「售后服务」。操作系统可以通过UEFI Runtime Services接口,调用固件的某些功能。

常见的Runtime Services包括:

  • GetVariable / SetVariable:读写NVRAM中的UEFI变量。
  • GetTime / SetTime:读取/设置系统时间。
  • ResetSystem:重启或关机。
  • UpdateCapsule:固件更新。

我个人觉得,RT阶段最容易出问题的地方是内存映射。操作系统启动后,固件代码和数据必须保留在物理内存中,但操作系统可能会重新映射内存。如果固件代码访问了错误的地址,系统就会崩溃。

避坑指南:我曾经在开发一个UEFI驱动时,在RT阶段调用了Boot Services的AllocatePool函数。结果操作系统直接蓝屏了。原因很简单——RT阶段只能调用Runtime Services,Boot Services已经不可用了。这个错误在调试时很难发现,因为模拟器里可能不会触发。

RT阶段的另一个特点是虚拟地址转换。操作系统启动后,会把物理地址映射到虚拟地址空间。UEFI固件需要支持这种转换,否则无法在操作系统环境下正常工作。这部分涉及SetVirtualAddressMap()函数,我们后面会专门讲。

2.5 四个阶段的衔接与数据传递

四个阶段不是孤立的,它们通过HOB和System Table传递数据。PEI阶段生成HOB,DXE阶段读取HOB并构建System Table,BDS阶段使用System Table中的Protocol,RT阶段保留System Table中的Runtime Services。

我画了一张图来展示这个数据流:

阶段间数据传递 PEI 生成HOB DXE 构建System Table BDS 使用Protocol RT 保留Runtime HOB → System Table → Protocol → Runtime Services

理解这个数据流很重要。我在调试一个启动失败的问题时,发现PEI阶段生成的HOB中,内存描述符的地址范围写错了。DXE阶段读取后,分配内存时直接覆盖了固件代码区,导致系统崩溃。这种问题很难定位,因为错误发生在PEI阶段,但症状在DXE阶段才显现。

2.6 小结

UEFI启动流程的四个阶段,每个阶段都有明确的职责:

  • PEI:从零开始,初始化内存和CPU,生成HOB。
  • DXE:加载所有驱动,构建完整的系统服务。
  • BDS:选择启动设备,加载操作系统Boot Loader。
  • RT:操作系统运行期间,提供固件服务接口。

我个人觉得,初学者最容易犯的错误是把四个阶段混为一谈,在PEI阶段调用DXE阶段的API,或者在RT阶段使用Boot Services。记住一句话:每个阶段只能使用本阶段及之前阶段提供的服务。这个原则贯穿整个UEFI开发,务必牢记。