读设置画面时,BIOS、UEFI、引导程序、管理引擎经常被说成同一个东西。它们处在交接链的不同位置。下面先给时间线,再按出现顺序写每条分支:它解决什么,把机器交给谁,今天还在哪。
1. 时间线
| 年份 | 事件 |
|---|---|
| 1974 | CP/M 把 BIOS 定为每台机器重写的硬件层 |
| 1981 | IBM PC 的 ROM(Read-Only Memory,只读存储器,出厂写好、掉电还在)BIOS 成为兼容标准 |
| 1983 | Compaq Portable 带着干净房间 BIOS 出货 |
| 1984 | Phoenix 出售可授权 BIOS;PC/AT(AT,Advanced Technology,IBM 的 286 个人计算机,比初代 PC 多了保护模式和实时钟)带来 CMOS(Complementary Metal-Oxide-Semiconductor,这里指主板上靠纽扣电池保住的配置存储器)和 A20(地址总线第 21 根线,打开后才能访问 1MB 以上的内存) |
| 1985 | AMI(American Megatrends Incorporated,安迈,后来主要的 PC 固件供应商之一)成立 |
| 1987 | PS/2(Personal System/2,IBM 想用来取代 PC/AT 的个人计算机)的 ABIOS(Advanced BIOS,给保护模式用的高级 BIOS,没有成为行业标准)试图离开实模式,市场留在 AT 兼容 |
| 1990 | 386SL 引入 SMM(System Management Mode,系统管理模式,CPU 进入操作系统看不见的状态去跑固件) |
| 1994 | 即插即用 BIOS 规范 |
| 1995 | El Torito:从光盘启动 |
| 1996 | ACPI(Advanced Configuration and Power Interface,高级配置与电源接口,固件用一组表把硬件交给操作系统)1.0;启动顺序写成 BIOS Boot Specification |
| 1998 | Phoenix 收购 Award;CIH(作者陈盈豪名字的缩写,一种会从操作系统里擦掉主板闪存的病毒)能从系统里抹掉闪存 |
| 1999 | LinuxBIOS;PXE(Preboot Execution Environment,预启动执行环境,操作系统还没有时从网络把系统拉起来)2.1 |
| 2000 | EFI(Extensible Firmware Interface,可扩展固件接口,UEFI 的前身,最初做给安腾服务器)1.02;U-Boot |
| 2004 | Intel 公开 Tiano,后来成为 EDK II(EFI Development Kit II,实现 UEFI 和平台初始化的开源开发包) |
| 2006 | UEFI 2.0;Intel Mac 改用 EFI |
| 2008 | LinuxBIOS 改名 coreboot;SeaBIOS(开源的 16 位传统 BIOS,用软件中断把控制权交给引导扇区) |
| 2011 | UEFI 2.3.1 写入 Secure Boot(安全启动,固件只加载签名校验通过的引导程序) |
| 2012 | Windows 8 让 Secure Boot 成为出厂默认 |
| 2013 | ARM Trusted Firmware 公开;Libreboot(coreboot 的一种发行,用来发布尽量不带专有二进制的固件) |
| 2017 | LinuxBoot(用一个 Linux 内核换掉 UEFI 后半段成堆的驱动)公开;Intel 给出去掉 CSM(Compatibility Support Module,兼容支持模块,在 UEFI 里模拟传统 BIOS 的中断和引导扇区)的时间表 |
| 2020 | 苹果芯片改走 iBoot(苹果从 iPhone 沿用的引导程序,从片上只读存储器逐级验签名直到内核),离开 EFI |
| 2022–2024 | BlackLotus、LogoFAIL、PKFail |
2. 这个词比 PC 更早
1974 年前后 Gary Kildall 写 CP/M 时,系统分成两块:与机器无关的 BDOS(Basic Disk Operating System,基本磁盘操作系统,管文件和程序,不认识具体的磁盘控制器),以及每台机器都要重写的 BIOS。换一台磁盘控制器或显示终端,改 BIOS,上面的程序不用重写。
所以这个词的原意是硬件抽象层,给已经跑起来的操作系统调用。它还不是「上电后 CPU 执行的第一段固件」。后一层意思是 IBM PC 加上去的,而且加完之后把前一层意思盖住了。很多人现在说的 BIOS,指的是后一层。
3. IBM 把它做成门槛
1981 年 8 月 12 日,IBM PC(5150)出货。ROM 里的 BIOS 做三件事:POST(Power-On Self-Test,上电自检,加载系统之前逐项确认处理器、内存、显卡和键盘控制器能用),找到可启动的设备并读入引导扇区,以及在实模式下提供一组软件中断(INT,Interrupt,用一个编号把控制权交给 BIOS 里的服务程序)。显示走 INT 10h,磁盘走 INT 13h,键盘走 INT 16h,引导本身是 INT 19h。操作系统和 DOS(Disk Operating System,磁盘操作系统,早期 PC 上直接调用这些中断的系统)程序都靠这些入口碰硬件。
原装 IBM PC 找不到磁盘时,会掉进 ROM 里的 Cassette BASIC(BASIC,Beginner's All-purpose Symbolic Instruction Code,初学者通用符号指令码,一种解释型编程语言。这份解释器焊在 ROM 里,没有磁盘也能进入)。兼容机通常没有这份 BASIC 的授权,所以同一条失败路径变成「没有系统盘」。这是最早的一处分叉:接口看起来兼容,ROM 里的内容已经不是同一份。
BIOS 有版权。想卖兼容机,不能把 IBM 的 ROM 拷走。1982 年 Columbia Data Products 做出早期兼容机;1982 年 Compaq 用干净房间写自己的 BIOS,1983 年的 Compaq Portable 把它卖出去。干净房间的做法是:读过 IBM 代码的人只写规格,写代码的人只看规格、不看 IBM 的实现。这样做出的 ROM 行为兼容,代码来源独立。
1984 年的 PC/AT 把这套接口又推了一步:CMOS 里保存配置,实时钟在掉电后还能走,A20 地址线要由固件打开,机器才能用到 1MB 以上的内存。设置程序一开始在软盘上,后来才做进 ROM。
1987 年 IBM 用 PS/2 想把平台重新收拢。Micro Channel(微通道,IBM 在 PS/2 上推行的新总线,接口和 ISA 不兼容。ISA,Industry Standard Architecture,工业标准结构,是 AT 时代插扩展卡的总线)之外,还有一套 ABIOS,给保护模式用,旁边留着 CBIOS(Compatibility BIOS,兼容 BIOS,继续提供旧的实模式中断,好让 DOS 软件还能跑)。克隆机产业没有跟过去。大家留在 AT 兼容 BIOS 上,总线从 ISA 走到 EISA(Extended ISA,扩展 ISA,做成 32 位来对抗微通道)、PCI(Peripheral Component Interconnect,外围组件互连,1990 年代起取代 ISA 的主板总线)。ABIOS 是第一条死掉的分支:方向对(离开 16 位实模式),绑定的整机平台没有赢。
4. 干净房间,以及活下来的三家
Compaq 证明干净房间走得通,但不是每家装机厂都养得起这样一支队伍。1984 年 Phoenix 开始出售可授权的兼容 BIOS。小厂买一份 ROM,就能进兼容机市场。这是 BIOS 从「每家自己的秘密」变成「供应商产品」的转折。
随后站住的名字主要是三家:AMI、Award 和 Phoenix。Award 的模块化 BIOS 在 386、486 时代非常常见。Phoenix 在 1998 年收购 Award,零售市场上长期是 AMI 和 Phoenix-Award 两套。笔记本固件里后来长出 Insyde,UEFI 时代以 H2O(Insyde 的商用 UEFI 产品线,商标写成水的分子式,和化学无关)成为第三家主要供应商。
Quadtel、MR BIOS(Microid Research BIOS,微识别研究公司的 BIOS,1990 年代的小供应商)这些名字存在过,没有留成今天的产品线。主板上印的是华硕、技嘉、联想、戴尔,设置程序的引擎常常是 AMI Aptio(AMI 基于 EDK II 的商用 UEFI)、Insyde H2O 或 Phoenix SecureCore(Phoenix 的商用 UEFI)之一。厂商品牌画在界面上,固件代码来自这三家的参考实现,再加主板厂自己的板级配置。
三家共用参考代码这件事,到安全事故里会再出现一次:一个 SMM 缺陷可以同时出现在好几种 logo 下面。
5. 经典 BIOS 在机器里做什么
上电到交棒
CPU 从上电后的复位向量取第一条指令,这时内存还没训练好,只能执行闪存或 ROM 里的代码。经典 BIOS 接着做 POST:CPU、内存、芯片组、显卡、键盘控制器。失败时用蜂鸣码报告,AMI、Award、Phoenix 的蜂鸣含义各写各的。
显卡和磁盘控制器可以自带 Option ROM(扩展卡上的只读固件,BIOS 在实模式地址空间里扫描到它,就调用它的初始化,显卡、磁盘阵列卡和网卡靠这个把自己挂进启动)。BIOS 在 C0000h 到系统 ROM 之间扫描这些扩展,调用它们的初始化入口。RAID(Redundant Array of Independent Disks,独立磁盘冗余阵列,把多块磁盘组织成一块逻辑盘的控制卡)卡、网卡的 PXE、独立显卡,都靠这块窗口把自己挂进启动过程。
然后按 CMOS 里的顺序找启动设备,把第一个扇区读到 0000:7C00h,跳进去。引导扇区再去找分区里的引导程序。GRUB(GRand Unified Bootloader,大一统引导加载程序,Linux 常用,由固件或引导扇区加载,再由它加载内核)、Windows Boot Manager 是这一步之后的程序,它们读内核。把 GRUB 叫成 BIOS,会把固件和引导程序叠在一起。
386 时代还有一个加速习惯:把 ROM 的内容拷进 RAM(Random Access Memory,随机存储器,掉电就丢,读起来比 ROM 快)再执行,叫 shadow。ROM 的访问太慢,拷完之后那一段地址看到的是内存。
中断服务
交棒之后,经典 BIOS 还留在机器里。DOS 和早期操作系统继续用那些软件中断读写磁盘和键盘。固件既是开机程序,也是运行时的驱动程序。
这套调用约定绑在 16 位实模式上。实模式能直接看见的常规内存是 640KB,上面是显存和 ROM 留下的洞。A20 被键盘控制器(8042)闸住,是为了让 286 在 1MB 边界的回绕行为和 8086 一致。要使用扩展内存,得先打开 A20。这些都是 BIOS 要处理的兼容债,不是业务功能。
Option ROM 和 CMOS
配置存在 CMOS RAM 里,靠主板上的纽扣电池保住。清 CMOS、拔电池,是那个年代恢复默认设置和清掉固件口令的办法。口令存在 CMOS 里,所以断电能清掉;后来有的口令和启动策略写进闪存,清 CMOS 不再等于恢复出厂。
Option ROM 必须放进 1MB 以下那一小段地址。系统 BIOS 自己占住高处,留给扩展卡的往往只有一两百 KB。一张 RAID 卡加一张网卡再加一张显卡,初始化时会报 Option ROM 空间不够。这是经典 BIOS 结构上的天花板,不是某一版软件的 bug。UEFI 把驱动程序放进固件卷和 EFI 系统分区,就是为了离开这个窗口。
6. 地址空间一次次不够用
磁盘容量的上限也是同一类问题:调用约定里的位数先用完了。
| 大约上限 | 卡在哪 |
|---|---|
| 528 MB | BIOS 的柱面数和 IDE(Integrated Drive Electronics,把控制器做进磁盘的接口。后来的标准名叫 ATA,AT Attachment,挂在 AT 总线上的磁盘附件)的磁头数取交集,没有翻译 |
| 8.4 GB | 翻译之后仍是 1024 柱面 × 255 磁头 × 63 扇区 |
| 137 GB | 28 位 LBA(Logical Block Addressing,逻辑块寻址,用扇区编号代替柱面、磁头、扇区),228 个 512 字节扇区 |
| 2 TiB | MBR(Master Boot Record,主引导记录,磁盘第一个扇区里的引导代码和分区表)分区项用 32 位扇区号 |
前两档是 CHS(Cylinder-Head-Sector,柱面-磁头-扇区,用磁盘的物理几何来定位)几何。Phoenix 的扩展磁盘服务 EDD(Enhanced Disk Drive,增强型磁盘驱动,把 INT 13h 扩成能传递更大的扇区号)让 BIOS 能传递更大的 LBA,所以 8.4GB 并不是 BIOS 的最终磁盘能力。28 位 LBA 是 ATA 接口自己的位数,48 位 LBA 在 2000 年代初把它打开。2 TiB 则是 MBR 这张分区表的位数。GPT(GUID Partition Table,GUID 分区表。GUID,Globally Unique Identifier,全局唯一标识符,一串用来给分区命名、几乎不会重复的号码)把扇区号加宽到 64 位。配套的启动方式是 UEFI 去读 EFI 系统分区,而不是再读一个 512 字节的引导扇区。
每一档上限都靠给旧接口打补丁撑过几年。补丁叠到实模式、Option ROM 窗口和 MBR 上之后,继续加调用号的收益已经很小。
7. 九十年代补上的规范
即插即用和 PCI
1994 年 5 月,Compaq、Phoenix、Intel 发布即插即用 BIOS 规范(1.0A)。ISA 时代中断和 I/O(Input/Output,输入/输出,这里指设备占用的端口地址)地址靠跳线。PnP(Plug and Play,即插即用)BIOS 在开机时枚举设备,把中断和端口分配好,省掉跳线。PCI 从 1992 年的总线规范开始进 PC,BIOS 用 INT 1Ah 提供访问 PCI 配置空间的服务。操作系统仍可能再分配一次,但开机时总要有人先把总线点到能用。
ACPI
1996 年 12 月 22 日,Intel、微软、东芝发布 ACPI 1.0。这是职责的分界,不只是又一份表格。
经典 BIOS 的运行时模型是:操作系统调用固件来碰设备。ACPI 的模型是:固件用几张表把机器描述清楚。DSDT(Differentiated System Description Table,差异化系统描述表,用一套字节码写出这台主板有哪些设备)、FADT(Fixed ACPI Description Table,固定描述表,给出硬件寄存器的地址)和 MADT(Multiple APIC Description Table,多 APIC 描述表。APIC,Advanced Programmable Interrupt Controller,高级可编程中断控制器,负责把中断送到某一颗处理器。MADT 告诉操作系统有多少颗处理器、中断怎么接)是其中最常读到的三张。电源状态、中断路由和可热插拔的设备由操作系统自己的驱动接管。固件的运行时职责收窄成早期初始化、睡眠唤醒(尤其是 S3,挂起到内存:状态留在 RAM 里,唤醒时由固件配合把机器拉回来)和仍需留在 SMM 里的那些事件。
UEFI 没有取代 ACPI。今天的 PC 仍用 ACPI 告诉操作系统「这台机器长什么样」。SMBIOS(System Management BIOS,系统管理 BIOS,记录厂商、型号、序列号和固件版本)则告诉你这些字段。它从 DMTF(Distributed Management Task Force,分布式管理任务组,制定桌面管理标准的行业组织)的 DMI(Desktop Management Interface,桌面管理接口,用表列出机器的厂商和配置,SMBIOS 是它的后继)长出来。dmidecode(把 DMI / SMBIOS 表解码打印出来的 Linux 命令)读的就是它。两套表都比 UEFI 这个名字更老,也都还在。
光盘、网络和启动顺序
1995 年 1 月 25 日,Phoenix 和 IBM 发布可启动光盘规范,名字 El Torito 来自他们开会的那家餐厅。光盘可以模拟软盘或硬盘,也可以不模拟,直接按光盘自己的方式启动。1996 年的 BIOS Boot Specification 把软盘、硬盘、光盘、网络的启动优先级写成统一规格,厂商不必再各自发明一套菜单。
网络启动是 Intel 的 PXE。2.1 规范在 1999 年。网卡 Option ROM 里的 PXE 代码在操作系统出现之前发 DHCP(Dynamic Host Configuration Protocol,动态主机配置协议,先向网络要一个地址),传文件、交出控制权。后来的 UEFI 网络栈、HTTP Boot(HTTP,Hypertext Transfer Protocol,超文本传输协议。HTTP Boot 用它把引导文件下载下来)、iPXE(开源网络引导固件,能走 HTTP 等 PXE 原先没有的方式)都是这条需求的后继:机器还没有系统时,就要能从网上把系统拉起来。
同一时期,ROM 从紫外线擦除的 EPROM(Erasable Programmable ROM,可擦除可编程只读存储器,要拆下来用紫外线擦,不能在系统运行时改)换成可在板上重写的闪存。升级不用拔芯片,也意味着写错或写到一半断电,机器会起不来。固件里留一块受保护的启动块,主镜像坏了由它做恢复。CIH(1998 年写出,1999 年 4 月 26 日触发)从操作系统直接写闪存,把不少机器的 BIOS 抹掉。启动块,以及技嘉在世纪之交做的双芯片 DualBIOS,都是对这件事的产品反应。
8. SMM:操作系统看不见的运行时
1990 年的 386SL 加入系统管理模式。系统管理中断 SMI(System Management Interrupt,把 CPU 拉进 SMM 的那根中断)进来时,CPU 切到一块操作系统看不见的内存 SMRAM(System Management RAM,专供 SMM 使用的内存,操作系统的页表看不到),执行固件留下的处理程序,再用 RSM(Resume from System Management mode,从系统管理模式返回被打断的程序)回来。设计目的是笔记本的电源管理:盖上盖子、休眠、调频。
后来堆进去的东西远多过休眠。USB(Universal Serial Bus,通用串行总线)键盘要在「没有 USB 驱动的设置界面和旧系统」里工作,就在 SMM 里把 USB 模拟成 PS/2 键盘口(和前面的 PS/2 计算机不是同一件事:这是圆口键盘和鼠标的接口标准,设置程序只认识这种旧设备)。笔记本的 Fn 键、风扇、一些纠错和厂商自定义按键,也走这里。操作系统看到的是一个普通中断或什么都没看到,时间已经被 SMM 用掉了。
安全讨论里把 SMM 放在环 −2:权限在操作系统内核下面。它能读内存、能维持一份操作系统删不掉的代码。第 15 节里的 ThinkPwn 出在这一层。UEFI 把引导服务拆掉了,SMM 作为运行时仍然留在现代 PC 上。
9. 从 EFI 到 UEFI
安腾上的起点
安腾(IA-64,Intel Architecture 64,安腾的 64 位指令集,和后来 PC 上的 x86-64 不是同一套。x86-64 是把 PC 用的 x86 指令集扩展到 64 位,现在的个人计算机跑的是这一套)不准备再从 16 位实模式启动。1990 年代末 Intel 的 Boot Initiative 为此设计 EFI。EFI 1.02 在 2000 年 12 月 12 日,EFI 1.10 在 2002 年 12 月 1 日。第一批真正用它启动的是安腾服务器。
和经典 BIOS 相比,交给操作系统的不再是一组软件中断,而是一张表里的引导服务和运行时服务。可执行文件是 PE/COFF(Portable Executable / Common Object File Format,可移植执行文件 / 通用对象文件格式,Windows 程序也用这套容器)的 .efi,放在一个 FAT(File Allocation Table,文件分配表,一种简单文件系统)格式的 EFI 系统分区里。驱动程序以协议(用 GUID 命名的接口)登记。图形输出走 GOP(Graphics Output Protocol,图形输出协议,用来画设置界面和启动画面,取代 INT 10h),磁盘访问走对应的块设备协议。引导程序可以是一个正常的可执行文件,不必再把自己压进 512 字节。
论坛和消费级切换
2005 年成立 UEFI 论坛,成员包括 AMD、AMI、苹果、戴尔、惠普、IBM、Insyde、Intel、联想、微软、Phoenix。名字里的 U 表示这套规范交给论坛,不再是 Intel 单独的 EFI。UEFI 2.0 于 2006 年 1 月 31 日发布。
2006 年苹果换到 Intel CPU 时用的是 EFI 1.10,带自己的启动选择和 bless(苹果用来指定从哪块盘启动的命令,不是 UEFI 标准接口),没有 PC 上那块「按 Del 进入设置」的兼容 BIOS。PowerPC Mac 在此之前用的是 Open Firmware,见第 13 节。苹果因此跳过了经典 PC BIOS 的后半段。
PC 侧慢得多。64 位 Windows Vista 能从 UEFI 启动,市面上的机器大多仍走传统 BIOS。UEFI 2.3.1(2011)把 Secure Boot 写进规范。Windows 8(2012)的 OEM(Original Equipment Manufacturer,原始设备制造商,这里指戴尔、联想这类整机厂)认证要求出厂打开 Secure Boot。x86 机器仍须允许关掉它、允许注册别的钥匙;当时的 Windows RT(跑在 ARM 上的 Windows 8,RT 是这个版本的名字)不允许关掉。消费级 PC 从这一年才大面积把默认启动路径换成 UEFI。
CSM 和 Class(固件类别,用来标明这台机器还能不能按传统 BIOS 启动)
已经卖出去的系统、老的 Option ROM、MBR 安装的操作系统还要能启动。兼容支持模块在 UEFI 固件里嵌一份 16 位 BIOS 的行为:传统中断、传统 Option ROM、传统引导扇区。它是桥,也是经典 BIOS 多活了十几年的原因。
| Class | 能怎么启动 |
|---|---|
| 0 | 只有传统 BIOS |
| 1 | 固件含 UEFI 和 CSM,实际只走传统启动 |
| 2 | UEFI 和传统启动都能选 |
| 3 | 只有 UEFI,没有 CSM,没有传统 Option ROM 接口 |
2017 年 Intel 在 UEFI Plugfest(厂商集中演示和核对 UEFI 实现的集会)上给出时间表,准备到 2020 年去掉传统 BIOS 兼容。落地拖了几年。2020 年代前半,新的客户端平台陆续变成 Class 3。还有人把 Secure Boot 不能关闭的机器叫 Class 3+。Windows 11 要求 UEFI、具备 Secure Boot、TPM(Trusted Platform Module,可信平台模块,主板上单独的安全芯片,用来保存启动度量和钥匙)2.0;很多主板上 Secure Boot 仍然能关,那种机器是 Class 3,还不到 3+。
10. UEFI 固件内部的阶段
UEFI 规范规定的是操作系统加载器能调用什么。PI(Platform Initialization,平台初始化。它管 UEFI 的调用表还没准备好的那一段:先把处理器、内存和芯片组点到能加载驱动)规定这一段怎么写。EDK II 两套都实现。主板上电之后大致经过这些阶段:
| 阶段 | 做什么 |
|---|---|
| SEC(Security,安全阶段) | 复位后的第一段。用缓存当临时内存,测固件 |
| PEI(Pre-EFI Initialization,EFI 之前的初始化) | 小模块初始化永久内存和基础芯片组,交出 HOB(Hand-Off Block,移交块,下一阶段用来接着干活的数据包) |
| DXE(Driver Execution Environment,驱动执行环境) | 加载驱动,登记协议,形成引导服务和运行时服务 |
| BDS(Boot Device Selection,启动设备选择) | 按 NVRAM(Non-Volatile RAM,非易失存储器,掉电后启动项还在)里的启动项选择设备 |
| TSL(Transient System Load,瞬态系统加载) | 操作系统加载器运行,然后调用 ExitBootServices |
| RT(Runtime,运行时) | 引导服务销毁。变量、时间、复位、胶囊更新还在 |
| AL(After Life,运行期之后) | 睡眠唤醒和 SMM 这一类「系统已经跑起来之后」的固件 |
ExitBootServices(退出引导服务,加载器通知固件:操作系统要自己管硬件了,引导期的那些服务必须停掉)是分界。在此之前,加载器可以随便用引导服务:读 ESP(EFI System Partition,EFI 系统分区,FAT 格式,.efi 文件放在这里)、找协议、拉文件。调用之后,这些服务不再存在,操作系统使用自己的驱动。仍然可以调用的运行时服务包括读写 UEFI 变量、时钟、复位系统,以及胶囊更新(把新固件交给下次启动去刷)。
启动项存在 NVRAM 变量里:BootOrder 决定顺序,Boot#### 指向 ESP 上的某个 .efi。找不到厂商条目时,约定路径是 \EFI\BOOT\BOOTX64.EFI。Secure Boot 的钥匙也是变量,见第 15 节。内存训练在 Intel 上常交给 FSP(Firmware Support Package,固件支持包,闭源二进制,负责把内存颗粒训练到能用,并做芯片组最早一段初始化),在 AMD 上常交给 AGESA(AMD Generic Encapsulated Software Architecture,AMD 通用封装软件架构,角色和 FSP 相同,也是闭源的硅初始化代码)。
复位向量
→ SEC(缓存当内存)
→ PEI(内存训练,FSP 或 AGESA 常在这里)
→ DXE(驱动、ACPI、SMBIOS)
→ BDS(按 BootOrder 找 .efi)
→ 加载器
→ ExitBootServices
→ 操作系统;SMM 和运行时服务留下
11. 闪存里不叫 BIOS 的区域
现在说「刷 BIOS」,写进去的是整颗 SPI(Serial Peripheral Interface,串行外设接口。主板上那颗保存固件的小闪存用这种串行总线来读)闪存上的镜像,不只是设置程序。Intel 平台上常见的布局是几块区域拼在一起:
| 区域 | 是什么 |
|---|---|
| 描述符 | 规定后面各区域的边界和读写权限 |
| ME(Management Engine,管理引擎)/ CSME(Converged Security and Management Engine,收敛安全与管理引擎,ME 的后继名字) | 芯片组里那颗独立控制器的固件,主处理器关机时它仍可能在运行 |
| GbE(Gigabit Ethernet,千兆以太网) | 板载网卡的配置,可有可无 |
| BIOS 区 | UEFI 代码、NVRAM、微码更新通常在这里 |
管理引擎从 2000 年代后半进芯片组。带 vPro(Intel 面向企业的平台品牌)的机器上它支持带外管理 AMT(Active Management Technology,主动管理技术,操作系统没起来甚至主机关机时,仍可能通过网线被管理),主 CPU 关机时它仍可能在跑。2017 年研究者公开 ME 11 里跑着 MINIX(一种用于教学的小型 Unix 操作系统),Intel 随后承认。消费级主板上这块区域通常也在,即使你用不到 AMT。它和 BIOS 区共用一颗闪存,ME 区坏了,机器一样无法完成上电。AMD 侧对应的是平台安全处理器 PSP(Platform Security Processor,焊在 CPU 里的安全处理器,负责校验早期固件)。
笔记本还有一颗嵌入式控制器 EC(Embedded Controller,管键盘矩阵、电池、盖子和上电时序,有时比主 CPU 更早醒来)。Chromebook 的 CrOS EC(Chrome OS 的嵌入式控制器固件,源码公开)是这条线上公开源码的实现。服务器旁边则是 BMC(Baseboard Management Controller,基板管理控制器,服务器上独立于主机的一台小电脑),上面跑的常常是 OpenBMC 这种 Linux。BMC 能给主机上下电、刷新主机固件。它是邻居,不是主机 BIOS。
容量也跟着变。1990 年代闪存是几百 KB 到 1MB 量级,UEFI 加上 ME 之后常见 16MB、32MB,更大的镜像也有。清 CMOS 清的是其中的变量或电池维持的那一点状态,不是这整颗芯片。
12. 开源分支
商用三家把 UEFI 做成产品。另一边有人重写「上电之后、操作系统之前」这一段,原因通常是:启动太慢、不能审计、集群里每台都等几分钟不可接受,或者想换成自己的引导策略。
EDK II 和 OVMF(Open Virtual Machine Firmware,开放虚拟机固件)
2004 年 Intel 把 EFI 的实现以 Tiano 的名字公开,放在 tianocore.org。随后的开发包是 EDK(EFI Development Kit,第一代 EFI 开发包),再往后是 EDK II。它是 UEFI 加 PI 的参考代码,使用广泛的开源许可证。
AMI Aptio、Insyde H2O、Phoenix SecureCore 都以某一版 EDK II 为底,加上闭源的硅初始化和自己的设置界面。板上跑的那份是厂商分叉,而且常常停在几年前的快照上。能编译上游 EDK II,不等于能编出你主板上的那颗镜像。
OVMF 是 EDK II 里给 QEMU(Quick Emulator,开源的机器模拟器)、KVM(Kernel-based Virtual Machine,嵌在 Linux 内核里的虚拟化)用的目标,也就是虚拟机选择 UEFI 启动时加载的那份固件。云镜像和较新的虚拟化默认值大多走这条,传统 BIOS 虚拟机则走下一节的 SeaBIOS。
coreboot
1999 年 Ron Minnich 在洛斯阿拉莫斯国家实验室做 LinuxBIOS。集群节点不该把时间花在一份封闭 BIOS 上,他们希望上电后尽快进入 Linux,并且源代码在自己手里。2008 年改名 coreboot,因为下一段程序不一定是 Linux。
coreboot 的阶段是 bootblock(启动块,复位后最先执行的一小段)、romstage(还在闪存里跑的阶段,负责把内存训练出来)、ramstage(拷进内存之后跑的阶段,准备硬件表并选择 payload),然后跳到 payload。ChromeOS 还会在前面加一个 verstage(验证阶段,在往下执行之前检查固件签名),做验证启动。ramstage 结束时内存可用、必要的表已经准备好。它不提供 INT 13h,也不提供 UEFI 引导服务。那些由 payload 提供:
| payload | 交出去的接口 |
|---|---|
| SeaBIOS | 传统 BIOS |
| EDK II / Tianocore | UEFI |
| depthcharge | ChromeOS 的验证启动 |
| Linux 内核 | 直接进入 Linux |
| Heads、GRUB、FILO(FIrst LOader,最早的加载器,coreboot 早期用的简单引导程序) | 各自的引导或度量启动 |
出货量最大的 coreboot 在 Chromebook 上:Google 维护自己的树,并把大量板级代码送回上游。System76、Purism、StarLabs 以及 3mdeb 的 Dasharo 也用它。现代 Intel 主板上,内存训练往往还是调用一块闭源的 FSP,见第 14 节。coreboot 打开的是硅初始化之后的策略,不是整颗芯片的全部代码。
SeaBIOS
SeaBIOS 由 Kevin O'Connor 从 2008 年前后开始写,是一份开源的 16 位 BIOS:POST 之后的传统行为、软件中断、引导扇区。QEMU 的传统固件用它,coreboot 需要「看起来像旧 BIOS」时也用它当 payload。
它不负责现代平台上最难的那一步,也就是内存训练和芯片组早期初始化。前面必须已经有别人把 RAM 准备好。虚拟机里这个「别人」是 QEMU 提供的虚拟硬件,物理机上则是 coreboot 或一份完整的商用固件。
LinuxBoot、Heads、depthcharge
2017 年 Google、Facebook 等公开 LinuxBoot。想法是把 UEFI 后半段里成堆的 DXE 驱动换成一个 Linux 内核加小的 initramfs(initial RAM filesystem,初始内存文件系统,内核刚起来时在内存里用的根目录)。用户态工具用 Go 写成 u-root(用 Go 写的根环境,里面放引导、解锁磁盘和跳转内核的命令)。引导管理、磁盘解锁、kexec(从当前正在运行的 Linux 直接跳进另一个内核,中间不再复位固件)到真正的系统内核,都在这个 Linux 里做。超大规模机房用它,是因为这份代码可以阅读、可以打补丁,启动时间也更可预期。相关的早期说法叫 NERF(Non-Extensible Reduced Firmware,不可扩展的精简固件,指用 Linux 换掉 UEFI 里网络和引导那一段)。
Heads(Trammell Hudson,2010 年代中期)也是 coreboot 的 Linux payload,目标放在度量启动上。固件和内核的度量进 TPM,屏幕上给出一个只有这台机器知道的秘密,用来发现闪存被换过。用户群主要是一部分 ThinkPad。
depthcharge 是 ChromeOS 的 payload。它按 ChromeOS 的验证启动去加载已签名的内核。Chromebook 的启动链因此是:coreboot 做硅初始化,depthcharge 做验证,然后才是系统。
Slim Bootloader 和 oreboot
Slim Bootloader 是 Intel 在 2018 年公开的轻量引导程序,面向部分物联网和特定 Intel 平台。阶段比完整 UEFI 短,硅初始化仍调用 FSP。它是「UEFI 太重」时的一条产品分支,不是 coreboot 的改名。
oreboot 从 2019 年开始,用 Rust 重写 coreboot 那种「初始化然后跳到 payload」的结构,C 不出现在这份固件里。它跑在一部分 RISC-V(第五代精简指令集,指令集规范开放,谁都可以依规范做处理器)和少量板上,是新硅片和新语言的试验线,不是笔记本的出厂固件。
13. 没走过 IBM 这条河的
PC BIOS 的历史几乎全是 x86。路由器、手机、单板计算机和苹果后来的机器有自己的起点。其中几条后来又和 UEFI 接上,接法不一样。
Open Firmware 和设备树
IEEE(Institute of Electrical and Electronics Engineers,电气电子工程师学会)1275(1994 年发布的开放固件标准)定义的 Open Firmware 用 Forth(一种堆栈式的编程语言,Open Firmware 的驱动和命令用它写)写可移植的引导和设备描述。Sun 的 OpenBoot、1994 到 2006 年的 PowerPC Mac 都用它。苹果转到 Intel 时,PowerPC 上的 Open Firmware 被 EFI 替换。
它描述硬件的那棵设备树留了下来。Linux 在嵌入式上用的扁平设备树 FDT(Flattened Device Tree,把那棵树压成一块二进制,引导程序把它和内核一起交出去)从这里演化。今天 ARM 和 RISC-V 开发板把硬件信息放进设备树,服务器和 PC 把硬件信息放进 ACPI。这是「操作系统如何认识这台机器」的分叉,和用哪一套引导接口是两件事。QEMU 的 POWER(IBM 的一种处理器架构,用在服务器上,不是 PC 的 x86)虚拟机仍能用一份开源的 SLOF(Slimline Open Firmware,精简开放固件,体积比完整的 Open Firmware 小)。
U-Boot
2000 年 Wolfgang Denk(DENX,维护 U-Boot 的德国嵌入式公司)发布 Das U-Boot,从 PPCBoot(PowerPC Boot,只服务 PowerPC 的引导程序,U-Boot 把它做成多架构)长出来。它是 ARM 嵌入式、大量路由器和单板计算机的引导程序:初始化足够跑起来的硬件,从闪存、SD(Secure Digital,一种存储卡)、网络或磁盘加载内核和设备树,跳进去。
后来 U-Boot 加上一层 UEFI 接口。发行版的 GRUB、systemd-boot(systemd 附带的 UEFI 引导管理器,按启动项去加载内核或另一个 .efi)和安装器的 .efi 可以在这块板子上启动。EBBR(Embedded Base Boot Requirements,嵌入式基础启动要求,规定这类板子至少提供哪一层 UEFI)把「U-Boot 或同等程序 + UEFI 接口」写成最低约定。SBBR(Server Base Boot Requirements,服务器基础启动要求,规定 ARM 服务器要有 ACPI 和更完整的 UEFI)则要求服务器能像 PC 一样安装操作系统。同一套 UEFI 接口,底下的描述表可以是设备树,也可以是 ACPI。
TF-A(Trusted Firmware-A,ARM 可信固件)和 OpenSBI(Open Supervisor Binary Interface,RISC-V 的开源监管者接口)
TF-A 2013 年公开。手机和 ARM 服务器上电后,先在安全世界里走完几级,再进入普通世界:
BL1(Boot Loader stage 1,第一级引导,通常放在芯片出厂就写死的只读存储器里,只负责加载下一级)BL2(第二级,从闪存或磁盘里把后面的镜像读出来)BL31(留在 EL3 的监视器。EL3,Exception Level 3,ARM 权限最高的一档,操作系统进不去)BL32(可选的安全操作系统,常见是 OP-TEE,Open Portable Trusted Execution Environment,开源可信执行环境;也可以是 Trusty,Android 用的安全操作系统)BL33(普通世界的引导程序,U-Boot 或 UEFI,从这里起才是人们熟悉的「加载内核」)
这条链没有经典 BIOS。和 PC 的对应关系是:TF-A 做 PC 上 SEC 加部分安全启动的事,BL33 才是人们认识的引导程序。
RISC-V 上对应的开源固件是 OpenSBI。它待在 M 态(Machine mode,机器态,RISC-V 权限最高的一档,操作系统进不去),向后一级提供监管者二进制接口。约 2019 年由西数等推动。后一级同样常常是 U-Boot 或 EDK II。RISC-V 的板子因此经常是 OpenSBI → U-Boot → 内核,或者 OpenSBI → UEFI → .efi。
苹果和手机
苹果有三段。1994 到 2006 年的 PowerPC Mac 用 Open Firmware。2006 到 2020 年的 Intel Mac 用 EFI,界面和变量都不跟 PC 的 UEFI 设置混用。2020 年的 Apple 芯片改用 iBoot。iBoot 从 2007 年的 iPhone 就在:片上 Boot ROM 验下一级,逐级验到内核。没有 CSM,没有 PC 式的 SPI 设置画面,恢复用的是另一套系统。这是从 IBM 那条河完全离开的分支。
Android 手机长期使用高通的 Little Kernel(常被写成 LK,一个很小的引导内核)作为引导程序,配套 fastboot(安卓的线刷协议,开机停在引导程序里时用 USB 刷写分区)和后来的 Android 验证启动 AVB(Android Verified Boot,从引导程序到系统分区逐级核对签名)。较新的高通平台改用 ABL(Android Boot Loader,安卓引导程序,代码基于 EDK II)。手机因此在十几年后部分接回 UEFI 代码,接回来的是引导程序实现,不是 PC 的 BIOS 兼容层。
树莓派这一类单板机又是一条短链:GPU(Graphics Processing Unit,图形处理器。树莓派上电后先由这颗芯片里的闭源程序跑起来)侧的闭源 Boot ROM 先跑,再从 EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程只读存储器,板上的一小颗配置芯片)或 SD 卡加载后续固件,然后才放开 ARM 核。中间没有 BIOS 这个角色。
14. 硅初始化为什么还是闭源的
内存训练是现代固件里最难替换的一段。频率、时序、颗粒和主板走线都相关,训练代码由 CPU 厂商掌握。开源引导程序要在这些平台上工作,就得调用厂商给出的二进制。
Intel 的固件支持包 FSP 大约在 2014 年随低功耗平台公开接口。coreboot 的 romstage 和 Slim Bootloader 都可以把内存和芯片组的早期初始化交给它。FSP 是闭源的。所以「这台机器跑 coreboot」和「闪存里没有专有代码」是两句不同的话。
AMD 长期把对应的代码放在 AGESA 里,商用 BIOS 和 coreboot 都调用它。2023 年 AMD 公布 openSIL(Open-Source Silicon Initialization Library,开源硅初始化库,打算用可编译的代码替换封闭的 AGESA),公布时从服务器平台做起,客户端是后一步。
Intel Boot Guard(启动防护,CPU 在执行闪存里的第一段代码之前先核对 OEM 签名)从 Haswell(英特尔第四代酷睿那一代平台)开始出现。验证模式打开时,没有 OEM 签名的镜像不会被执行。夹住闪存芯片写一份未签名的 coreboot,机器会在复位之后停住。AMD 的 PSB(Platform Secure Boot,平台安全启动,把处理器熔丝绑到某一家 OEM 的公钥上)会让从品牌服务器上拆下的 CPU 拒绝另一家的固件。这两件事决定了「源代码公开」之后,某块具体的板子仍可能换不了固件。
同一套 coreboot,在发布政策上又分成几条。Libreboot 从 2013 年开始,作为去掉专有二进制的 coreboot 发行。后来它允许带上启动所必需的专有二进制,以便覆盖更新的主板。Canoeboot 把不含这些二进制的发布留在另一条线上,能支持的机器更老、更少。GNU Boot(GNU's Not Unix,GNU 是一套自由软件操作系统工程的名字。GNU Boot 是挂在这个名字下的固件发行,只收不含专有二进制的 coreboot)是 2023 年出现的另一条无专有二进制发行。三者的启动架构仍是 coreboot,分开的是发布物里允不允许 FSP、微码这一类二进制。
15. 安全启动,以及几次公开事故
Secure Boot 用 UEFI 变量里的钥匙决定哪份 .efi 可以跑。
PK(Platform Key,平台钥匙,这台机器的信任根,持有它才能更换下面的钥匙)KEK(Key Exchange Key,密钥交换密钥,被平台钥匙授权、可以更新签名数据库的钥匙)db(signature database,允许执行的签名数据库)dbx(forbidden signature database,已吊销签名的数据库,名单里的引导程序即使曾经签过名也不能再跑)
出厂的 PC 通常信任微软的钥匙。Windows 引导程序由其中一把签过名。主流 Linux 发行版用一份微软签过名的 shim(垫在最前面的小引导程序,微软的签名让它能通过出厂的 Secure Boot,它再信任该发行版自己的钥匙),shim 再信任该发行版自己的钥匙。发行版不必让每台机器的主人手工登记钥匙,也能在默认的 Secure Boot 下启动。吊销通过更新 dbx 完成,是全局名单:作废一个有问题的引导程序,旧的安装 U 盘可能一起无法启动。
下面几件事改变的是人们对这套机制的预期。这里只记后果,不写利用过程。
- CIH,1998–1999。 从操作系统写闪存,主板变砖。启动块和双 BIOS 成为产品功能。
- ThinkPwn,2016。 SMM 里的缺陷来自多家厂商共用的参考代码。同一个洞出现在不同品牌的设置界面后面。
- LoJax,2018。 野外见到的持久化直接写在 SPI 闪存里,待在过去合法防盗模块也使用的那一类位置。重装系统不会把它去掉。
- BlackLotus,2022。 引导套件在 Secure Boot 打开的机器上仍能留下,利用的是当时 Windows 引导程序的漏洞(CVE-2022-21894。CVE,Common Vulnerabilities and Exposures,公开漏洞的统一编号)。微软用
dbx吊销。Secure Boot 是一张要更新的名单,不是出厂时拨一次的开关。 - LogoFAIL,2023。 AMI、Insyde、Phoenix 用来绘制开机 logo 的图片解析器跑在固件权限里,解析器自身出过漏洞。logo 处在引导链上。
- PKFail,2024。 一批设备出厂时带的平台钥匙是样本钥匙,对应的私钥早已泄露。这台机器的信任根从出厂起就不是厂商独有的。
Linux 上现在常见的刷新路径是 fwupd(给 Linux 刷新固件的后台程序)和 LVFS(Linux Vendor Firmware Service,Linux 厂商固件服务,厂商把签过名的固件包放在这里):操作系统把厂商签名的胶囊交给 UEFI 的胶囊更新,下次启动再写进闪存。1990 年代那种「做一张 DOS 软盘去刷」的路径还在极老的板上,新机器的标准路径是胶囊或厂商自己的更新器。刷新写的是第 11 节那整颗镜像里的若干区域。写到一半断电时,启动块或双镜像决定机器还能不能回来。
16. 今天这句话指的是哪一层
设置界面仍可能把自己叫 BIOS。2012 年之后的 PC 上,那个界面是 UEFI 应用。按 Del 或 F2 看到的菜单改的是 NVRAM 变量、启动顺序和 Secure Boot 策略。真正上电后先跑的,是 SEC、内存训练和 ME 已经在做的事。
| 听到的名字 | 它实际是 |
|---|---|
| BIOS | 口语里的固件设置;狭义上是 1981 年那套实模式固件 |
| UEFI | 引导服务、运行时服务、ESP 上的 .efi |
| CSM | 嵌在 UEFI 里的传统 BIOS 行为,新平台在去掉 |
| ACPI / SMBIOS | 描述硬件和库存信息的表,操作系统启动后仍在读 |
| GRUB、systemd-boot | 固件加载的引导程序,再由它们加载内核 |
| coreboot | 做到 payload 为止的初始化,接口由 payload 决定 |
| EDK II | UEFI 的参考实现;出厂机器跑的是它的厂商分叉 |
| FSP、AGESA | 内存和硅初始化的厂商二进制 |
| ME、PSP、EC、BMC | 主 CPU 之外的控制器,常和 BIOS 区焊在同一次刷新里 |
一条 PC 的交接可以收成这样:Boot Guard 或 PSB 先决定第一段代码能不能跑;内存训练把 RAM 准备好;UEFI 或 coreboot 的 payload 准备 ACPI、SMBIOS 和启动项;ESP 上的 .efi 加载操作系统;ExitBootServices 之后,驱动在操作系统里。SMM、ME 或 PSP 留在旁边。
其他机器不必经过其中任何一截。路由器可以是 U-Boot 直接加载内核。ARM 服务器是 TF-A 然后 UEFI 然后 ACPI。Apple 芯片是 iBoot 的签名链。它们和 BIOS 共享的只有一件事:上电之后、操作系统之前,有一段你通常不每天修改的程序,负责把机器交到下一段。
17. 规范名字
正文里的年份对应这些文档和项目,便于接着核对。
- Plug and Play BIOS Specification 1.0A(1994,Compaq、Phoenix、Intel)
- El Torito 可启动光盘规范(1995,Phoenix、IBM)
- BIOS Boot Specification 1.01(1996)
- ACPI Specification,从 1.0(1996)起
- PXE Specification 2.1(1999,Intel)
- UEFI Specification,从 2.0(2006)起;Secure Boot 在 2.3.1
- Platform Initialization Specification(SEC、PEI、DXE、BDS)
- 项目:EDK II / TianoCore、coreboot、SeaBIOS、LinuxBoot、U-Boot、Trusted Firmware-A、OpenSBI