本节摘要:把 BIOS 与 UEFI 放在同一张桌子上对比,差异远不止界面新旧。本节沿三条主线展开:地址空间与磁盘布局、驱动与交互契约、信任的存续方式。结论先行——两者分属两个工程时代,BIOS 把兼容性当作最高纲领,UEFI 把可验证性当作第一原则。
读完这一节,请自查五项能力:能否默写出 8086 时代 1MB 空间的三块划分并说明每块的来历;能否推导 MBR 的 2.2TB 天花板从何而来、GPT 又如何拆掉它;能否把一次 INT 13h 调用与一次 Block I/O 协议调用逐参数对齐;能否解释 SEC→PEI→DXE 三级接力为什么天然构成信任链;能否脱稿向同事白板画出双列对比矩阵。
8086 的设计决定了此后二十余年的困局:20 根地址线,段寄存器左移四位与偏移相加,可表达的空间恰好 1MB。这个数字在 1981 年足够阔绰,到 1990 年代就成了镣铐。BIOS 只能在这个拥挤的空间里做文章,于是 PC 平台长年维持一幅奇特的地图——
这套分割带来两层后遗症。其一,实模式单个段至多 64KB,BIOS 扩展代码被迫拆成小段互相远跳,寄存器传参的约定既难维护也难审计,日后大量溢出类漏洞的根源都能追到此处。其二,磁盘侧同样保守:分区信息挤在 MBR 的 512 字节里,四个主分区表项、每项 16 字节、32 位 LBA,算术上封顶约 2.2TB。2005 年前后大容量盘上市,这条红线逼出了各种厂商私有的扩展方案,碎片化随之蔓延——BIOS 在存储这件事上,从摆渡人变成了拦路者。
UEFI 的答案很直接:不迁就历史。固件在 SEC 之后尽快切入保护模式乃至 IA-32e,主流平台提供 48 根地址线、约 256TB 的平坦空间,规范层面按 64 位规划。代码放哪里都行,驱动可装进任意物理页,调用全用指针,不存在段切换的开销与心智负担。
磁盘抽象同步换代。GPT 把表头放在 LBA1、把备份副本放在盘尾,每个分区表项 128 字节,容量与数量的理论上限足够用到大容量存储本身被淘汰。更进一步,GPT 不是孤立的格式,而是与启动路径绑定的:固件自带 GPT 解析器,认出 EFI 系统分区后直接把 .efi 文件当程序装载执行。BIOS 那条“读 MBR→跳引导扇区→引导扇区再去找下一级”的接力链条,每多一环就多一个被篡改的位置;UEFI 把链条截短成“固件直接取文件”,路径确定、结果可验证。
| 维度 | 传统 BIOS | UEFI |
|---|---|---|
| CPU 模式 | 16 位实模式起跳,需模式切换舞蹈 | 直接进入保护/长模式 |
| 寻址上限 | 1MB(20 根地址线) | 48 位总线约 256TB,规范 64 位 |
| 内存布局 | 常规/UMA/扩展三段割裂 | 平坦空间统一编址 |
| 分区方案 | MBR,4 主分区,2.2TB 上限 | GPT,备份表头,容量近乎无限 |
| 引导方式 | MBR 链式跳转,逐级加载 | 固件直取 ESP 中的 efi 文件 |
| 配置存储 | CMOS RAM 128 字节 | SPI Flash 中的 GUID 变量 |
BIOS 对外提供能力的方式,是一张写死的中断向量表。操作系统想读盘,就往寄存器里塞好功能号与参数,触发 INT 13h,BIOS 在暗处干活,干完通过 CF 标志和 AH 回个结果。这份“合同”全部靠默记:参数顺序、段寄存器搭配、错误码含义,都约定在手册里而没有机器可读的形式。你想确认一次扩展读写是否真的原子、INT 10h 换模式时是否与别的核发生显示状态竞争?没有观测点,也无从验证。
驱动这件事在 BIOS 世界里干脆不存在。硬件初始化代码被编死进 ROM,与具体芯片组、Super I/O、南桥型号共生。想让一台老机器认得 NVMe 盘?找厂商重编整份固件、走烧录更换流程,周期以月计。用户侧既装不了也换不了任何“BIOS 驱动”——固件即硬件,出厂即终点。
UEFI 把这套暗语换成了显式的类型化接口,即 Protocol:一组带完整签名的函数指针,挂在系统句柄数据库里供检索。拿块设备协议举例,一次读操作的入参包括协议实例指针、介质 ID、LBA、缓冲长度与输出 buffer,语义白纸黑字,边界一目了然。任何驱动只要实现并注册了该协议,任何应用都能按标准流程查到它、调用它——耦合被接口切断,组件可以替换、可以单独测试。
交互形态因此升级。UEFI Shell 是个货真价实的命令行环境:跑 .efi 程序、遍历协议、查设备树、手工挂驱动、dump 内存,样样能做,早已超出 BIOS Setup 那种“改改数值”的维修面板定位。Setup 界面本身也变了底座——图形输出协议撑起鼠标、多语言、高分屏,而不是靠 VGA 中断的模拟硬撑。
安全能力同样是接口模型的副产品。Secure Boot 能落地,靠的不是给启动文件套一层加密,而是整个装载流程走协议化验证:固件装载 .efi 前先取其签名,再对照密钥库逐级核对签名链与吊销记录。每一步都是可审计的协议交互,而不是“把一坨机器码加载进来然后祈祷”。
💡 关键直觉:可以把 UEFI 驱动模型理解成“固件里的微内核”。DXE Core 只保留调度、内存与协议发布/查找这几件核心事;网络栈、文件系统、TPM 访问、图形输出统统是独立编译、可签名、可按需装载的 .efi 模块。BIOS 是琥珀,UEFI 是雨林。
| 维度 | BIOS 中断服务 | UEFI Protocol |
|---|---|---|
| 契约形式 | 中断号加寄存器约定,隐式默会 | 函数指针签名,显式声明 |
| 参数传递 | 寄存器(DL、CH、ES:BX 等) | 命名参数加输出缓冲 |
| 错误处理 | 进位标志加 AH 错误码 | 规范化状态码 |
| 可发现性 | 固定中断号,无运行时枚举 | 句柄数据库动态发现 |
| 可扩展性 | 出厂固化,无法追加 | efi 驱动按需加载与替换 |
| 可验证性 | 无签名机制 | 签名链逐级校验 |
最后一组差异藏在最深处:系统凭什么相信开机后执行的第一段代码之后的一切。BIOS 的做法是一锤子买卖——POST 里把 CPU、内存、显示做一轮粗检,然后把控制权抛给 MBR,自己功成身退。引导扇区后来有没有被人动过手脚、内核里是否潜伏 rootkit,它既看不见也管不着。像小区保安查过一次证件就永久下班,面对 Bootkit、恶意固件这类针对启动链的攻击,防御纵深为零。
UEFI 则把验证做成了贯穿始终的流水。固件执行被切进三个互相衔接又互相约束的阶段:SEC 阶段是 CPU 上电后最先跑的一小段代码,固化在处理器/芯片组内部,负责搭起最初的执行环境与临时 RAM,位置上不可覆盖,天然是信任链的第一环;PEI 阶段接手,在 SEC 铺好的安全上下文里点亮内存控制器与芯片组,模块本身可验签;DXE 阶段是主战场,装载并运行各 .efi 组件,维护句柄与协议数据库,Secure Boot 的校验逻辑在此启用——每一份 EFI 映像都要过 PK/KEK/db 签名链,任何一环核对失败即中止装载。
还有一块常被忽略:UEFI 运行时服务在 OS 起来之后仍驻留在受保护地址空间。哪怕内核被彻底攻陷,攻击者想改 dbx 吊销名单,也得拿出有效 KEK 签名,而 KEK 又被 PK 罩着。验证从按下电源键那刻起,一直延续到系统关机前。
产业侧早已按这个模型重整防线:微软 Device Guard、Intel Boot Guard、AMD 的平台密钥方案都是它的实现变体;MITRE ATT&CK 专门增设固件持久化技术条目,反证了固件层已是 APT 的必争之地,而 UEFI 的结构性验证是目前唯一的体系化对策。
⚠️ 常见坑:对比时别只盯理论模型。现实中的 UEFI 设备为兼容旧系统和双系统,普遍预装微软证书与主流 Linux 发行版证书,还留着 CSM 这个向后兼容通道——防火墙上留着窗是商业取舍。生产环境应关闭 CSM,并定期收紧 db 里的证书集合。
三条线索汇拢,分野其实只有一个:BIOS 是工业时代的兼容性工程,追求不动、能跑、别出事;UEFI 是数字时代的可信性工程,追求可组合、可验证、可持续演进。前者的信任寄托在厂商与物理封装上,后者的信任建立在开放规范与密码学契约上。
这层范式迁移的外溢远大于启动本身:虚拟化平台借 SMM 做安全监控,机密计算拿 TCG 2.0 当可信执行环境的起点,连现代沙箱设计里“明确接口、解耦实现”的思路,都与 Protocol 机制一脉相承。
第一章到此完成“概念—历史—对比”的闭环。下一章钻进机器内部,按时间轴拆一遍从加电自检到操作系统接管的全过程。
对比表背得再熟,不如在自己机器上敲一条命令。UEFI 启动的 Linux 会把 EFI 变量挂成 sysfs 下的 efivars 目录,传统 BIOS 启动则根本没有这套接口:
# UEFI 启动的机器 $ ls /sys/firmware/efi efivars fw_platform_size fw_vendor runtime runtime-map systab tpm2_log $ cat /sys/firmware/efi/fw_platform_size 64 $ cat /sys/firmware/efi/fw_vendor AMI # 传统 BIOS(Legacy/CSM)启动的同一台机器 $ ls /sys/firmware/efi ls: cannot access '/sys/firmware/efi': No such file or directory
fw_platform_size 为 64 说明 CPU 进入 OS 加载器时处于 64 位长模式,这正是 UFEI "先设好执行环境再交接"的直接证据;BIOS 交接时只会把 CPU 留在 16 位实模式,加载器还得自己开 A20、切保护模式。
差异的另一半在交接数据结构上。BIOS 时代的引导扇区约定是死板的 512 字节、末尾 0x55AA;UEFI 交接的则是 EFI_HANDOFF_PROTOCOL 里成体系的表指针。两种约定并排看:
/* 传统 BIOS 约定:引导扇区最后两字节的魔数,加载到 0x7C00 执行 */ /* 用 xxd 看一个 syslinux MBR 的结尾 */ $ xxd -s 510 -l 2 mbr.bin 000001fe: 55aa U. /* UEFI 约定:OS 加载器入口收到的 EFI_SYSTEM_TABLE 指针(EDK2 头文件节选) */ typedef struct { CHAR16 *FirmwareVendor; /* 如 L"AMI" */ UINT32 FirmwareRevision; EFI_TABLE_HEADER Hdr; EFI_HANDLE ConsoleInHandle; EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_RUNTIME_SERVICES *RuntimeServices; EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; EFI_CONFIGURATION_TABLE *ConfigurationTable; /* ACPI/SMBIOS 表入口 */ } EFI_SYSTEM_TABLE;
一边是两个字节的自证,一边是带 CRC32 校验头、串联 ACPI 与 SMBIOS 入口的整套目录,第 2 章的启动流程差异在这组对照里已经埋下伏笔。