起因

上一篇整理了从加电到长模式的经典引导原理——BIOS 自检、512 字节引导扇区、13 号中断读盘、实模式段寻址、GDT/LDT。写完之后越想越觉得不踏实:这套东西教材里教、网上教程也在教,但它描述的其实是正在退场的一条路径。现在随便一台新买的机器,主板上大概率根本没有传统意义上的 BIOS 了,CSM(兼容模式)在新平台上要么默认关闭要么直接被砍掉,Intel 从多年前就公开说要在自家平台上清除所有 CSM 痕迹。真机器的引导链路早就换了一套完全不同的骨架:不读 512 字节找 0x55AA,不靠 13 号中断读盘,甚至不存在"从实模式开始"这个起点。

学校教材普遍还停留在这套几十年前的旧模型上,这不是教材的错——保护模式、段描述符这些底层机制现在照样在用,是理解内存管理绕不开的基础。但只学旧模型,会让人对"现在的电脑到底是怎么启动的"这件事产生一个过时的印象。这篇就是想补上这一块空当:不是否定上一篇的内容,是把参照系摆正——旧知识要懂,同时也要知道现在真实跑在你电脑里的是哪一套。

一、UEFI 固件自己的引导,压根不是"实模式起步"

先纠正一个常见的想法误区:UEFI 不是"披着新皮的 BIOS",它的固件本身也有一套自己的分阶段引导流程,和"实模式→保护模式→长模式"这套操作系统引导的叙事完全是两件事,讲的是固件自己怎么把自己跑起来。UEFI 的行业规范(PI,Platform Initialization)把固件启动分成几个阶段:

  • SEC(Security)阶段:CPU 刚上电,处理器架构相关的最早期初始化,同时承担安全信任根的职责——现代平台上这一步往往还叠了硬件级的信任根(比如 Intel Boot Guard),先验证固件本身没被篡改,再往下走
  • PEI(Pre-EFI Initialization)阶段:找到并验证 PEI 核心代码,初始化内存控制器,这一步之后系统才第一次有能完整寻址的内存可用(早期靠 CPU Cache 模拟出临时的栈空间顶替内存)
  • DXE(Driver Execution Environment)阶段:真正的重头戏,加载和运行各种驱动,PCIe 总线枚举、USB、显卡、存储控制器的初始化都在这一阶段完成,UEFI 的协议(Protocol)体系也是在这里搭起来的
  • BDS(Boot Device Selection)阶段:DXE 建好环境之后,负责选择引导设备、加载操作系统的引导器

关键点在于:这几个阶段跟"实模式/保护模式/长模式"根本不是一回事。UEFI 规范明确要求 x86-64 平台的固件在真正干活(跑驱动、跑协议)的时候,处理器工作在64 位长模式,用的是扁平的段描述符(flat segmentation,段基址为 0、段限覆盖全部地址空间,等于把段机制"关"成透明状态),不再依赖上一篇讲的那套"段基址+段限做内存保护"的老机制。固件启动过程里确实会经过短暂的保护模式做中转(这是 x86 架构本身的硬性规定,从实模式切换到长模式必须先过一遍保护模式),但那只是几条指令级别的瞬间跳转,不是操作系统意义上"运行在保护模式下"。所以更准确的说法是:长模式才是现代 x86-64 UEFI 平台真正长期停留、干活的状态,保护模式只是路过的中间站,不是终点。

二、显示输出:GOP 不是"直接写显卡端口"

GOP(Graphics Output Protocol)常被误解成"操作系统绕过驱动直接操作显卡硬件端口",实际上刚好相反:GOP 是 UEFI 固件里的一层驱动协议,由固件里针对具体显卡型号写好的 UEFI 驱动实现,它的工作是初始化显卡、协商好一个分辨率,然后交出一块线性帧缓冲(Linear Framebuffer)——一段可以直接按像素地址写入颜色值的内存区域。引导器或者操作系统内核想在屏幕上画点东西,只需要往这块内存里写字节,不需要知道这张显卡具体是什么型号、寄存器怎么排列,这些细节全被 GOP 这层协议屏蔽掉了。

这跟传统 BIOS 时代做的事情本质上是一回事——BIOS 时代靠 VGA BIOS 提供的 int 10h 中断和 VBE(VESA BIOS Extension)标准,也是一样"初始化显卡+给一块可写的显存区域"的思路,GOP 官方文档里写的目标就是取代 legacy VGA BIOS。区别只在于 GOP 是通过 UEFI 的协议调用方式暴露接口,不是通过中断调用。

这块帧缓冲只是引导阶段的过渡显示方案。操作系统真正跑起来之后(Windows 加载完 WDDM 驱动、Linux 加载完对应显卡的 DRM/KMS 驱动),会切换成显卡厂商写的原生驱动,那才是真正意义上直接和显卡通信、发挥全部 3D/计算性能的驱动栈。GOP 帧缓冲在这之后就基本退休了,它只负责撑起开机 Logo、BIOS 设置界面、引导菜单、内核启动早期的文字输出这几段"操作系统驱动还没接管"的空窗期。

三、PCIe、NVMe、SATA:谁负责枚举,谁负责通信

这块是教材里几乎不会讲,但现在特别现实的一层——存储和总线设备是怎么被固件认出来、变成"可引导设备"的。

PCIe 总线枚举发生在 DXE 阶段。UEFI 的驱动模型里有专门的"总线驱动"(Bus Driver)概念:PCI 总线驱动负责扫描 PCIe 拓扑,把每个发现的设备包装成一个 EFI_PCI_IO_PROTOCOL 实例挂在协议数据库里,其他驱动通过这个协议接口去访问对应的 PCI 设备(读写配置空间、映射 BAR 地址空间),不需要自己手写 PCI 配置空间访问代码。这是 UEFI 驱动模型的核心设计思路:总线驱动负责发现和枚举,设备驱动负责识别和使用,两者通过协议解耦。

NVMe SSD的情况最能说明"旧知识不够用"这一点:NVMe 盘挂在 PCIe 总线上,走的是专门的 NVMe 命令协议,跟老式 IDE/AHCI 那一套完全不是一个体系。系统能不能从 NVMe 盘启动,取决于 UEFI 固件本身有没有内置 NVMe 驱动模块——较老的主板固件如果没更新过,即使物理插上 NVMe 盘也认不出来,因为固件压根不知道怎么跟它说话;固件版本够新,DXE 阶段加载的 NVMe 驱动会把这块盘包装成标准的 Block I/O 协议,之后引导管理器和操作系统才能把它当成"一块可读写的存储设备"来用,全程不需要 13 号中断那套东西。

SATA/AHCI 设备同理,靠 AHCI 驱动在 DXE 阶段完成初始化和协议封装,也是通过标准化的 Block I/O 协议往上层暴露读写接口。这几类存储控制器的驱动,本质上都遵循同一套"UEFI 驱动模型":探测硬件→初始化控制器→包装成标准协议→挂到协议数据库供上层使用,跟"直接调 BIOS 中断传参数"这套上一篇讲的老思路,已经是完全不同的两套工程体系。

四、USB、A20 线:一个被协议取代,一个直接不存在了

USB 设备在 UEFI 体系里也是走"总线驱动+设备驱动"这套模型,USB 主机控制器驱动、USB Hub 驱动、USB 大容量存储驱动分层挂载,U 盘插上去能被固件识别成可引导设备,靠的是这条驱动链,不是靠什么特殊的中断调用。

A20 线这块可以说是彻底成为历史包袱了。上一篇提到打开 A20 线是实模式引导代码里的经典步骤,但在纯 UEFI 引导路径下,这个问题实际上不复存在——固件自己负责把 A20 打开(如果这条线在这个平台上还有意义的话),到操作系统接手的时候,CPU 已经在长模式下跑着扁平地址空间,压根不会再触及"实模式下 1MB 内存回绕"这个历史遗留问题。写纯 UEFI 引导程序的人,从头到尾不需要知道 A20 线是什么。

五、从固件到内核:ExitBootServices 与信任链

固件的 BDS 阶段选好引导设备、找到 ESP(EFI 系统分区)里的引导器(比如 bootx64.efi,或者 Linux 系统常见的 GRUB/systemd-boot、Windows 的 Boot Manager)之后,引导器作为一个符合 PE/COFF 格式的普通可执行文件被加载运行——不再需要 512 字节塞进引导扇区那种手法,可执行文件的大小、结构都是标准格式,跟平时写的应用程序编译产物没有本质区别。

引导器加载内核之前,会调用一个叫 ExitBootServices() 的 UEFI 接口,这一步是权力交接的分界线:调用之后,UEFI 固件提供的绝大部分服务(包括前面说的 GOP、各种 Protocol)都不再可用,取而代之的是操作系统自己的驱动栈全面接管硬件。这跟 BIOS 时代"BIOS 中断服务一直可以调"的模式也不一样——UEFI 更强调"固件负责把环境搭好、把控制权交接干净,剩下的事情操作系统自己全权负责"。

信任链方面,Secure Boot 是现在的默认状态:固件验证引导器的数字签名,引导器验证内核的数字签名,一级验证一级,任何一环签名不对整个链条就会拒绝往下走。这套机制在上一篇讲的传统 MBR 引导模型里根本不存在——0x55AA 签名只是"这是不是一个引导扇区"的格式校验,跟"这段代码有没有被篡改"的安全校验完全是两个维度的问题。

六、两套模型对照

拉一张简表把两套体系摆在一起看,差异会更直观:

环节 教材/传统 BIOS 模型 现代 UEFI 平台实际情况
起始 CPU 模式 实模式(16位,1MB 地址空间) 固件走 SEC/PEI/DXE/BDS 分阶段初始化,DXE 阶段之后长期停留在 64 位长模式
判断可引导设备 读 512 字节找结尾 0x55AA 签名 从 ESP(FAT32 分区)里找符合 PE/COFF 格式的 .efi 引导器
读磁盘方式 int 13h 中断,CHS/LBA 编址传参 NVMe/AHCI 驱动在 DXE 阶段初始化,封装成标准 Block I/O 协议
显示输出 int 10h / VBE,操作 VGA 寄存器 GOP 协议,固件初始化显卡后交出线性帧缓冲
内存保护 段模式(段基址+段限),真正意义上的分段保护 长模式下段机制基本被"拍平",保护靠分页机制
PCIe/USB 设备发现 没有统一模型,各设备各自为战 统一的 UEFI 驱动模型:总线驱动枚举,设备驱动挂协议
A20 线 需要手动打开,否则高位内存回绕 固件层处理完毕,操作系统开发者基本不需要关心
安全校验 无(0x55AA 只是格式标记) Secure Boot 逐级验证数字签名
移交操作系统的方式 直接跳转执行,BIOS 中断服务全程可用 调用 ExitBootServices(),固件服务谢幕,操作系统驱动全面接管

七、对"在现代硬件上做裸机/工控开发"这件事意味着什么

写这篇的一个直接动机,是给现代硬件上的裸机工控开发做个知识储备。传统 DOS 时代写工控程序,硬件环境相对简单:软盘或者小容量硬盘、标准 VGA 显示、串口/并口这类简单外设,BIOS 中断基本能覆盖大部分需求,驱动这层薄得几乎不用自己写。现在的硬件早就不是这个样子了:

  • 没有软盘,也基本没有传统意义的小容量磁盘:存储介质变成了 U 盘(USB 大容量存储)和 NVMe/SATA 大容量固态硬盘,想在这些设备上读写数据,靠的是前面说的 UEFI 驱动模型,不是 int 13h 传 CHS 坐标那套老办法
  • 显示器和显卡变了:不再是标准 VGA 分辨率,现代显卡通过 GOP 交出的是能匹配显示器原生分辨率的线性帧缓冲,分辨率、色彩格式(一般是 32 位 RGBA/BGRA)都要在初始化阶段读出来自己适配,不能再假设是固定的 640×480 或者 800×600
  • CPU 本身更复杂:多核心、长模式、更复杂的中断控制器(APIC 取代了老式 PIC)、ACPI 描述的电源管理和设备拓扑,这些都是传统 DOS 环境完全不用考虑的维度

如果目标是在这样的硬件上做一套新一代的工控程序,同时又想保留"裸机直接跑、没有厚重操作系统层"的 DOS 式设计哲学,现实是几乎所有驱动都得自己动手——U 盘/大容量磁盘的读写、高分辨率显示输出、复杂 CPU 拓扑下的中断和电源管理,这些在传统 BIOS+DOS 组合里被 BIOS 中断"包办"的功能,到了纯 UEFI 平台上要么得自己写 UEFI 驱动程序,要么得在 ExitBootServices() 之后自己实现一套等价的硬件访问逻辑。这也是本篇和上一篇放在一起看的意义:上一篇的段模式、GDT、特权级这些底层机制依然是绕不开的地基,这一篇讲的 UEFI 驱动模型、协议体系、长模式运行环境,才是决定"具体要对着哪套接口、哪种硬件行为去写驱动代码"的现实约束——两篇搭起来,才是在现代硬件上动手写裸机程序之前该有的完整知识准备。

总结

上一篇讲的实模式、13 号中断、A20 线、传统 GDT/LDT,作为理解"CPU 内存保护机制怎么从硬件层面实现"这件事的基础,依然值得学——段描述符、特权级检查这些概念,放到现在的分页保护机制里同样有影子。但如果只停在这一层,会对"我这台电脑现在到底怎么启动的"这件事产生一个已经不成立的印象。真实情况是:现代 x86-64 平台的 UEFI 固件有自己独立的分阶段引导体系(SEC/PEI/DXE/BDS),长期工作在 64 位长模式而不是保护模式;显示、存储、总线设备都走统一的驱动/协议模型,不再依赖那一串专用中断;A20 线这种历史包袱在纯 UEFI 路径下基本消失;安全校验也从"没有"变成了默认开启的逐级签名验证。教材受限于成书周期,很难跟上这些平台级的变化,想摸清楚现在真实跑在自己电脑里的是哪一套,还是要主动去查 UEFI 官方规范和硬件厂商的资料。

参考链接