固件基础:BIOS、UEFI、固件、引导加载程序的基本概念与启动流程

在深入 Coreboot 开发之前,必须厘清一组经常被混用的核心概念:固件(Firmware)、BIOS、UEFI 与引导加载程序(Bootloader)。它们处于不同的抽象层级,承担着截然不同的职责。理解这些概念的本质区别与协作关系,是理解 Coreboot 设计哲学——"只做必要初始化,然后尽快交出控制权"——的前提。本章将从硬件复位后的第一条指令开始,逐层拆解启动流程,并对比传统 BIOS 与现代 UEFI 的架构差异,为后续学习 Coreboot 的源码结构与移植工作奠定基础。

固件与启动流程分层架构图 硬件层(Hardware) CPU 复位向量(Reset Vector)· 芯片组(Chipset)· 内存控制器 · 存储控制器 · 外设控制器 固件层(Firmware)—— 本章核心 传统 BIOS(Legacy BIOS) 16位实模式 · 中断调用 MBR 引导 · 最大 2.2TB 磁盘 UEFI(统一可扩展固件接口) 32/64位模式 · 驱动模型 分区 · 安全启动(Secure Boot) Coreboot(开源固件) 极简初始化 · 快速启动 可加载 BIOS/UEFI 作为 Payload 引导加载程序层(Bootloader) GRUB 2 / Syslinux 传统 BIOS 引导链 GRUB 2 / systemd-boot UEFI 引导链 SeaBIOS / Tianocore / U-Boot Coreboot Payload 层 操作系统内核层(OS Kernel) Linux / Windows / BSD · 内核初始化 · 设备驱动加载 · 用户空间启动(init/systemd) ① CPU 复位 → 取第一条指令 ② 固件初始化硬件 → 加载 Bootloader ③ Bootloader 加载 OS 内核 硬件层 固件层 引导加载程序层 操作系统层

上图展示了从硬件复位到操作系统启动的完整分层结构。需要特别注意的是,BIOS 和 UEFI 本质上都是固件的一种具体实现,而 Coreboot 则是另一种固件实现。引导加载程序(如 GRUB)则位于固件之上,负责加载操作系统内核。在 Coreboot 的语境中,BIOS 或 UEFI 往往被当作 Coreboot 的"Payload"(负载)来使用——即 Coreboot 完成硬件初始化后,将控制权交给一个完整的 BIOS 或 UEFI 实现,再由后者引导操作系统。这种灵活的架构设计,正是 Coreboot 能够兼顾快速启动与兼容性的关键。

2.1 核心概念辨析:固件、BIOS、UEFI 与引导加载程序

2.1.1 固件(Firmware)的本质

固件是存储在非易失性存储器(如 SPI Flash、NOR Flash、EEPROM)中的软件,它直接与硬件交互,负责硬件的初始化和底层控制。固件介于硬件与操作系统之间,是"硬件与软件的桥梁"。与普通软件不同,固件通常不依赖操作系统运行,而是在系统上电后最先执行的代码。固件的核心特征包括:

  • 非易失性存储:固件存储在掉电不丢失的介质中,如 SPI Flash 芯片。
  • 直接硬件控制:固件直接操作寄存器、内存映射 I/O 和中断控制器。
  • 启动路径的起点:固件是 CPU 复位后执行的第一段代码。
  • 可更新性:现代固件支持通过刷写工具进行升级(如 flashrom 工具)。

在 x86 架构中,固件通常被存储在主板上的 SPI Flash 芯片中,容量从 8MB 到 32MB 不等。而在 ARM 架构中,固件可能存储在 SoC 内部的 ROM 或外部 SPI Flash 中。Coreboot 项目本身就是一个固件实现,它替代了传统的 BIOS/UEFI 固件,但保留了加载其他固件作为 Payload 的能力。

2.1.2 传统 BIOS 的架构与局限

BIOS(Basic Input/Output System)是 1980 年代 IBM PC 时代诞生的固件标准。它运行在 16 位实模式下,通过中断向量(如 INT 13h 用于磁盘访问、INT 10h 用于显示输出)提供基本的硬件抽象。BIOS 的启动流程遵循"INT 19h → MBR → Bootloader → OS"的经典链路。然而,传统 BIOS 存在诸多先天限制:

限制维度具体表现对现代系统的影响
CPU 模式仅支持 16 位实模式无法直接访问 64 位地址空间,性能受限
磁盘寻址使用 CHS(柱面/磁头/扇区)寻址最大支持 2.2TB 磁盘(LBA 28 位)
分区表MBR(主引导记录)最多 4 个主分区,不支持
启动速度逐项初始化,无并行机制启动过程通常需要 3-10 秒
驱动模型无统一驱动接口每个硬件需要特定中断处理程序
安全性无安全启动机制无法防止恶意引导代码注入

尽管 BIOS 已经过时,但它在嵌入式系统、工业控制领域仍有广泛应用。Coreboot 的一个重要 Payload——SeaBIOS——就是一个开源的传统 BIOS 实现,它允许 Coreboot 在启动时模拟传统 BIOS 环境,从而兼容旧版操作系统和引导加载程序。

2.1.3 UEFI 的现代架构

UEFI(Unified Extensible Firmware Interface)由 Intel 于 2000 年代初提出,后由 UEFI 论坛标准化。UEFI 彻底重构了固件架构,引入了以下关键特性:

  • CPU 模式:支持 32 位和 64 位模式,可直接访问大内存和 64 位 I/O 空间。
  • 驱动模型:提供统一的驱动接口(EFI Driver Model),支持设备驱动在固件阶段加载。
  • 分区表:支持 GUID 分区表,突破 MBR 的 2.2TB 限制,支持最多 128 个分区。
  • 安全启动(Secure Boot):通过数字签名验证引导加载程序和内核的合法性,防止恶意代码注入。
  • 模块化设计:UEFI 固件由多个 DXE(Driver Execution Environment)驱动模块组成,可按需加载。
  • 启动管理器:UEFI 内置启动管理器(Boot Manager),支持从多个设备(NVMe、USB、网络)启动。

UEFI 的启动流程比 BIOS 复杂得多,涉及 SEC(安全验证)、PEI(EFI 前初始化)、DXE(驱动执行环境)、BDS(启动设备选择)等多个阶段。每个阶段都有明确的职责边界,这种分层设计提高了代码的可维护性和可扩展性。然而,UEFI 的复杂性也带来了启动时间较长的问题——这也是 Coreboot 存在的重要理由之一。

2.1.4 引导加载程序(Bootloader)的角色

引导加载程序是固件与操作系统之间的中间层。它的核心职责是:从存储设备读取操作系统内核镜像,将其加载到内存中,并跳转到内核入口点。常见的引导加载程序包括:

  • GRUB 2:Linux 世界最流行的引导加载程序,支持 BIOS 和 UEFI 两种模式,支持多内核选择、命令行交互。
  • systemd-boot:轻量级 UEFI 引导管理器,适合现代 Linux 发行版。
  • SeaBIOS:作为 Coreboot 的 Payload,提供传统 BIOS 兼容层。
  • Tianocore:开源的 UEFI 实现,可作为 Coreboot 的 Payload,提供完整的 UEFI 环境。
  • U-Boot:主要用于嵌入式 ARM 平台,支持多种文件系统和网络启动。

在 Coreboot 的架构中,引导加载程序通常被称为"Payload"。Coreboot 本身不包含完整的引导逻辑,而是将控制权交给一个 Payload。这种设计使得 Coreboot 可以灵活地适配不同的使用场景:需要传统 BIOS 兼容性时加载 SeaBIOS,需要 UEFI 环境时加载 Tianocore,需要快速启动时直接加载 Linux 内核(通过 LinuxBoot)。

核心理解:固件是"硬件初始化者",引导加载程序是"操作系统加载者"。Coreboot 的哲学是:固件只做必要的硬件初始化(内存、芯片组、CPU),然后立即将控制权交给 Payload。这种"极简固件"理念使得 Coreboot 的启动时间可以压缩到 1 秒以内,而传统 UEFI 固件通常需要 3-10 秒。

2.2 启动流程深度拆解:从复位向量到操作系统

2.2.1 x86 架构的复位向量与第一条指令

当 x86 CPU 上电或复位时,它会从一个固定的物理地址开始执行指令。对于现代 x86 处理器,这个地址是 0xFFFFFFF0(位于 4GB 地址空间的顶部)。这个地址被称为复位向量(Reset Vector)。CPU 在复位后处于 16 位实模式,但 CS(代码段)寄存器被设置为 0xF000,IP(指令指针)设置为 0xFFF0,因此实际物理地址为 0xFFFF0(即 0xF000 << 4 + 0xFFF0)。然而,由于地址线 A20M 和地址回绕机制,实际访问的地址是 0xFFFFFFF0。

在传统 BIOS 系统中,SPI Flash 的顶部 16 字节被映射到 0xFFFFFFF0。这里存放着一条跳转指令(通常是 JMP F000:E05B),跳转到 BIOS 的 POST(Power-On Self-Test)代码入口。在 Coreboot 中,复位向量指向 Coreboot 的启动代码(bootblock),这是 Coreboot 执行的第一段代码。

// 典型的复位向量代码(汇编)
.org 0xFFF0  // 位于 Flash 顶部偏移 0xFFF0
reset_vector:
    jmp bootblock_entry  // 跳转到 bootblock 入口
    .org 0xFFF5  // 填充剩余空间
    db 0x00
    // 最后两个字节是 BIOS 结束标志 0x55 0xAA
    .org 0xFFFE
    db 0x55
    db 0xAA

这段代码展示了复位向量的基本结构。最后两个字节 0x55 0xAA 是 BIOS 固件的有效标志,主板上的 Flash 控制器会检查这个标志以确认固件有效。

2.2.2 Coreboot 的启动阶段划分

Coreboot 的启动流程分为四个主要阶段,每个阶段都有明确的职责和入口函数:

阶段入口函数主要职责运行位置
bootblockbootblock_main()CPU 初始化、Flash 映射、加载 romstageFlash 或 Cache-as-RAM(CAR)
romstageromstage_main()内存初始化(DRAM)、芯片组早期设置Cache-as-RAM(CAR)
ramstageramstage_main()设备枚举、驱动加载、ACPI 表生成系统内存(RAM)
payloadPayload 入口(如 SeaBIOS 的 _start)加载操作系统或提供固件运行时服务系统内存(RAM)

这种分阶段设计的关键在于:在内存初始化之前,代码无法在 RAM 中运行。因此,bootblock 和 romstage 必须使用 CPU 的 Cache-as-RAM(CAR)技术,将 CPU 的 L2/L3 缓存临时当作内存使用。这是 Coreboot 实现快速启动的核心技巧之一。

2.2.3 传统 BIOS 与 UEFI 的启动流程对比

为了更清晰地理解 Coreboot 的优势,我们对比三种固件的启动流程:

启动步骤传统 BIOSUEFICoreboot
1. CPU 复位跳转到 BIOS POST 入口跳转到 SEC 阶段跳转到 bootblock
2. 早期初始化实模式初始化、中断向量设置SEC 验证、临时内存(CAR)设置bootblock 设置 CAR、加载 romstage
3. 内存初始化通过 INT 15h 探测内存PEI 阶段初始化 DRAMromstage 初始化 DRAM
4. 设备枚举逐项扫描 ISA/PCI 设备DXE 阶段加载驱动、枚举设备ramstage 枚举设备、加载驱动
5. 启动设备选择读取 MBR,加载 BootloaderBDS 阶段读取 Boot Manager 配置加载 Payload(如 SeaBIOS/Tianocore)
6. 操作系统加载Bootloader 加载内核Bootloader 加载内核Payload 加载内核
典型耗时3-8 秒2-5 秒0.5-2 秒

从上表可以看出,Coreboot 的启动路径最短,因为它跳过了 UEFI 的多个冗余阶段,也不像传统 BIOS 那样需要逐项扫描设备。Coreboot 的 ramstage 阶段会并行初始化设备,而传统 BIOS 是串行扫描。

2.2.4 案例拆解:一台 Chromebook 的 Coreboot 启动过程

Google 的 Chromebook 是 Coreboot 最成功的商业应用案例。以一台典型的 x86 Chromebook(如联想 ThinkPad C13 Yoga)为例,其启动流程如下:

  1. 上电复位:CPU 从 0xFFFFFFF0 执行跳转指令,进入 Coreboot 的 bootblock。
  2. bootblock 阶段:设置 Cache-as-RAM(CAR),初始化 SPI Flash 控制器,将 romstage 从 Flash 复制到 CAR 中执行。此阶段耗时约 50ms。
  3. romstage 阶段:初始化内存控制器(Memory Controller Hub),训练 DDR4 内存时序,设置内存映射。此阶段耗时约 200ms。关键日志:Memory: 16GB DDR4-3200, dual-channel。
  4. ramstage 阶段:枚举 PCIe 设备(NVMe SSD、Wi-Fi 模块)、初始化显示控制器(eDP 接口)、生成 ACPI 表。此阶段耗时约 300ms。
  5. Payload 加载:Chromebook 使用深度定制的 SeaBIOS 作为 Payload,它直接加载 Linux 内核(而不是 GRUB)。从按下电源键到看到登录界面,总耗时约 1.2 秒。

这个案例展示了 Coreboot 的三大优势:启动速度快(1.2 秒 vs 传统 PC 的 5-10 秒)、代码精简(Coreboot 固件体积约 4MB,而 UEFI 固件通常 8-16MB)、可定制性强(Google 可以完全控制固件代码,实现快速安全更新)。

经验提醒:在 Coreboot 开发中,调试启动流程最常用的工具是 post codes(通过 LPC 总线输出到调试卡)和 serial console(串口日志)。建议在开发早期就启用串口日志,并设置 CONFIG_DEBUG_CAR 和 CONFIG_DEBUG_RAMSTAGE 选项,以便观察每个阶段的执行情况。

2.3 固件开发中的关键判断方法与实战场景

2.3.1 如何判断当前系统使用 BIOS 还是 UEFI

在固件开发或系统维护中,首先需要确认目标系统运行的是传统 BIOS 还是 UEFI。以下是几种可靠的判断方法:

  • 检查 /sys/firmware/efi 目录(Linux):如果存在该目录,则系统通过 UEFI 启动。
  • 使用 efibootmgr 命令:如果命令可用且能列出启动项,则系统是 UEFI 模式。
  • 检查分区表:使用 fdisk -l 查看磁盘分区,如果存在 EFI 系统分区(ESP,类型为 EFI),则系统支持 UEFI。
  • 查看 dmesg 日志:dmesg | grep -i "efi\|bios" 可以查看固件类型。
  • Windows 系统:运行 msinfo32,查看"BIOS 模式"字段。

在 Coreboot 开发中,还需要确认目标主板是否支持 Coreboot(查看 coreboot.org 的 Supported Hardware 列表),以及需要哪种 Payload 来满足使用需求。

2.3.2 固件刷写与备份的工程实践

固件开发过程中,刷写和备份是高风险操作。以下是推荐的工程流程:

  1. 备份原始固件:使用 flashrom 工具读取当前固件:flashrom -p internal -r backup.rom。务必保存原始固件,以便恢复。
  2. 验证备份完整性:计算备份文件的 SHA-256 哈希值,并与原始固件对比。
  3. 准备新固件:编译 Coreboot 后,使用 build/coreboot.rom 文件。
  4. 刷写新固件:flashrom -p internal -w coreboot.rom。如果主板不支持内部刷写,需要使用外部编程器(如 CH341A)连接 SPI Flash 芯片。
  5. 验证刷写结果:刷写后重新读取固件,对比哈希值。
  6. 测试启动:如果系统无法启动,使用外部编程器恢复备份固件。

风险提示:固件刷写失败可能导致主板变砖(无法启动)。务必确保:① 使用正确的固件文件(匹配主板型号和 Flash 芯片);② 刷写过程中不要断电;③ 准备外部编程器和备份固件作为应急方案。对于初学者,建议先在虚拟机或 QEMU 中测试 Coreboot 编译结果,再在真实硬件上刷写。

2.3.3 常见误区与总结复盘

在理解固件概念时,初学者常犯以下错误:

误区正确理解
认为 BIOS 和 UEFI 是两种不同的硬件BIOS 和 UEFI 都是固件软件,存储在 Flash 芯片中,只是架构和规范不同
认为固件 = BIOS固件是一个大类,BIOS 只是其中一种实现;Coreboot、U-Boot、Tianocore 都是固件
认为引导加载程序 = 固件引导加载程序(如 GRUB)运行在固件之上,依赖固件提供的服务
认为 Coreboot 是引导加载程序Coreboot 是固件,它加载引导加载程序(Payload)来启动操作系统
忽略 Cache-as-RAM 的重要性CAR 是 Coreboot 在内存初始化前运行代码的关键技术,没有 CAR 就无法实现快速启动