从BIOS到UEFI再到EDK2:开源固件生态与实战解析
2026/9/7 10:43:45 网站建设 项目流程

前两天帮朋友抢救一台老联想H61,他拿着U盘反复进PE都看不到硬盘,最后发现问题是BIOS里SATA模式开着IDE、启动项又是Legacy优先,跟UEFI引导盘完全不搭。这类问题我在固件圈子里见得太多了,很多人一听到UEFI、BIOS、开源固件、EDK2这些词就觉得是只有搞底层驱动的工程师才用得上,其实从刷魔改固件、给老主板打NVMe补丁,到自己编译一份OVMF虚拟机固件,每一个圈子里能碰到的坑,背后都是同一套固件生态的逻辑。这篇不打算写成规范翻译稿,就按我自己从组装机折腾到固件开发的顺序,把从传统BIOS到UEFI,再到EDK2和整个开源固件生态的现状,用大白话拆一遍。

1. 从BIOS到UEFI,这三十年到底换了什么

1.1 传统BIOS的启动哲学:一条路走到黑

先回忆一下传统BIOS干了什么事。CPU上电后从复位向量0xFFFFFFF0开始执行,这段地址对应NOR Flash里的固件。BIOS做完整机自检(POST)后,会把内存初始化好、设置中断向量表、枚举PCI设备,最后通过INT 19h去读硬盘的第一个扇区(MBR,512字节),把引导代码加载到0x7C00,然后把控制权交出去。这套机制本身没什么问题,问题是它的天花板太明显:16位实模式、寻址空间1MB、MBR分区表最大支持2TB磁盘分区、引导扇区只有512字节,而且压根没有安全校验的概念。

我当年在实验室维护老服务器,最头疼的就是装系统前得手动把大硬盘做GPT格式,但GPT又没有BootSignature,老BIOS根本认不出来,只能靠grub的core.img塞进MBR和前后扇区里做“夹心饼干”。这种方案能用,但每次升级内核都战战兢兢,生怕grub重装的时候把链路写坏。说到底,BIOS不是一个平台,它是一套“能用就行”的约定,各家BIOS实现差异大,扩展全靠Option ROM往0xC0000段里塞驱动,冲突和兼容性问题跟家常便饭一样。

1.2 UEFI做了什么本质改变

UEFI的核心理念不是继续在16位实模式上打补丁,而是直接定义了一个“微型操作系统”:它有CPU驱动、内存驱动、总线驱动、文件系统驱动,能跑在32位或64位保护模式下,还带一个图形界面。启动介质从512字节的MBR变成EFI系统分区(ESP),这个分区用FAT格式,里面直接放.efi格式的引导文件,固件自己就能识别并加载。

这么做最直观的好处有三个:

  • 磁盘分区不到上限了。GPT支持128个分区,单盘容量理论上到ZB级别,MBR时代2TB的限制彻底作废。
  • 启动链变透明了。你可以用efibootmgr、bcfg命令直接查看和修改固件里的启动项,引导文件就是一个普通文件,坏了拷回去就行,不用再靠grub重装工具救MBR。
  • 安全启动成为可能。Secure Boot让固件只加载有合法签名的引导程序和内核,Windows和主流Linux发行版都默认开启,对防bootkit有帮助。

有一点容易混淆,很多人以为UEFI只是BIOS的换皮界面,其实UEFI固件里有一个完整的驱动执行环境(DXE),显卡驱动、NVMe驱动、USB驱动都是跑在这个阶段的。所以你会看到现代主板即使没进操作系统,也能用鼠标操作BIOS界面,放大字体、看网络启动画面,这在传统BIOS时代很难想象。

1.3 开机时中断向量表和自检到底谁先谁后

有朋友问过一个问题:“在操作系统的引导中,中断向量表的建立和BIOS的通电自检有先后顺序吗?”这个问法其实把两个阶段混在了一起。硬件上电后,CPU处于16位实模式,此时IDTR寄存器里默认的中断向量表地址是固定的0x00000000到0x000003FF,这块内存是硬件设计时就约定好的。传统BIOS在POST过程中会往这个区域填写自己的中断向量,也就是说“中断向量表区域”本身在CPU一上电就存在了,BIOS自检是对它进行初始化和填充。

到了UEFI时代,固件在DXE阶段会切到32位/64位模式,中断控制器(APIC)初始化后,中断描述符表(IDT)的地址就不是固定值了,而是由固件自己在内存里建立并加载到IDTR寄存器。到真正引导操作系统时,OS的内核又会重新设置一套自己私有的IDT,完全接管中断处理。所以严格来说,中断向量表的建立和上电自检不是二选一的先后关系,而是固件初始化的一部分,后面操作系统还要再覆盖一遍。理解这条链,对定位开机死机和中断异常的帮助很大。

2. UEFI核心机制拆解:从SEC到Runtime的完整链路

2.1 启动阶段的七个步骤,到底谁在干活

UEFI规范把固件运行过程分成几个阶段,我习惯按这个顺序记:

  • SEC(安全验证):CPU刚上电,执行的第一段固化代码,主要是验证后面代码的完整性,建立临时内存(Cache As RAM),然后把控制权交给PEI。
  • PEI(EFI前期初始化):这个阶段的活很脏很累。内存控制器还没初始化,只能用CPU Cache当临时栈,通过PEIM驱动探测内存、初始化芯片组、找到北桥和内存配置,最后把真正的内存打开。
  • DXE(驱动执行环境):内存可用后,DXE内核启动,加载一堆DXE驱动,枚举PCI设备、初始化显示、文件系统、磁盘控制器。你看到的固件界面基本都是DXE阶段跑起来的。
  • BDS(启动设备选择):DXE把驱动都挂好之后,BDS负责按BootOrder里的启动项去扫描设备,找EFI系统分区、加载.efi引导文件。这个阶段就是大家进BIOS设置界面、调启动顺序时面对的那个菜单。
  • TSL(临时系统加载):引导程序(如grubx64.efi、Windows Boot Manager)开始跑,准备退出启动服务。
  • RT(运行时):操作系统接管,固件只暴露少量Runtime Service(GetVariable、SetVariable、ResetSystem这类)给OS用。
  • AL(灾难恢复):固件兜底机制,比如找不到任何启动设备时,进入固件Shell或者网络启动界面。

理解这几个阶段,对排查固件问题特别有用。比如开机卡在厂商Logo处不动,多半是PEI阶段内存训练没过;如果能在Logo之后黑屏、键盘灯亮但没有画面,可能是DXE阶段某个PCIe设备初始化卡住;如果进了引导界面但找不到启动盘,就是BDS阶段没识别到ESP分区。不同阶段的故障,思路完全不同。

2.2 NVRAM、变量与启动项,UEFI的“注册表”

UEFI一个容易被忽略的设计是变量服务。固件把启动项、Secure Boot密钥、硬件配置这些信息存在NVRAM里,而不是像传统BIOS那样存几百字节的CMOS。UEFI变量有名字、GUID、属性三要素,其中属性里有非易失性和运行时访问两个关键位。这意味着操作系统也能通过Runtime Service读写变量,你看Linux下efibootmgr -v能直接改启动项,就是固件开放了SetVariable服务。

但这里有个经典坑:NVRAM空间很有限,一般只有64KB到256KB。Windows更新、grub安装、固件Capsule更新都会往里面写变量,一旦空间用完,轻则启动项丢失,重则连进BIOS设置都直接报错。我见过一台双系统笔记本,装完Linux后Windows引导坏了,一查就是NVRAM爆了。处理办法通常是用UEFI Shell里的dmpstore命令导出全部变量,清掉没用的再导回,或者直接复位CMOS清空,但代价是所有启动项和Secure Boot设置都要重来。

2.3 UEFI Shell是什么,为什么维护机房离不开它

UEFI Shell相当于固件里的一个命令行环境,类似DOS但比DOS底层得多。它不需要硬盘上有操作系统,只要固件里编译进了Shell组件,或者启动盘上有shellx64.efi,就能进到一个能看内存、看设备路径、操作NVRAM变量的环境。

平时大家问的“UEFI交互式Shell v2.2”就是Intel发布的标准Shell版本,现在TianoCore也维护了一份开源版本。我最常用的几条命令:

map -r # 重新扫描所有设备并显示映射 bcfg boot dump -b # 查看当前所有启动项 bcfg boot add 0 fs0:\EFI\BOOT\BOOTX64.EFI "My Boot" # 向启动项列表添加一个自定义启动项 dmpstore # 导出/查看NVRAM变量 memmap # 查看内存映射

有次帮人救一块启动项全丢的工控板,系统盘和ESP都完好,就是BootOrder空了。我U盘启动进Shell,先用map -r找到ESP所在分区,再bcfg boot add手动加回Windows Boot Manager路径,重启就恢复了。这种操作在BIOS时代完全没有对应物,也是UEFI最让人上瘾的地方。

3. EDK2现状与实战:自己编译一份UEFI固件

3.1 EDK2到底是什么,跟TianoCore什么关系

EDK2全称是EFI Development Kit II,最初由Intel开发,后来交给TianoCore社区维护开源版本。它是UEFI规范的主流参考实现,也是绝大多数商业BIOS(AMI Aptio、Insyde H2O、Phoenix SecureCore)的内部底座。市面上那些品牌机、组装机主板虽然显示的是AMI或Insyde的界面,但底下的DXE驱动框架、PEI初始化模块、Protocol接口,大量代码都来源于EDK2。

所以做固件开发的人,几乎绕不开EDK2。你在UEFI规范里读到某个Protocol,到EDK2代码里就能找到对应头文件和实现;你在主板BIOS里看到一个隐藏设置项,反编译固件卷后也常常能追溯到EDK2的某个模块。可以说,EDK2就是UEFI世界的Linux内核,只是它运行在开机阶段,没几个人直接感觉得到。

需要注意的是,EDK2不等于UEFI规范。规范是PDF文件,定义了接口和流程;EDK2是一坨能编译运行的代码。很多厂商基于EDK2搞商业化定制,又加入了自己的代码和编译器,所以你在网上看到某些“魔改BIOS”能解锁隐藏选项,本质就是有人用UEFITool打开固件卷,修改了EDK2模块里的设置字符串或变量。

3.2 手把手编译OVMF,体验从源码到固件

如果想入门EDK2,最好的练习不是找一块真主板刷机,而是编译OVMF(Open Virtual Machine Firmware)。OVMF是EDK2里针对QEMU/KVM虚拟机的UEFI固件,跑在普通PC上就能调试,随便折腾不担心变砖。

以Ubuntu 22.04为例,完整流程是这样的:

# 安装依赖 sudo apt install git build-essential nasm python3 uuid-dev # 拉取EDK2源码及其子模块 git clone https://github.com/tianocore/edk2.git cd edk2 git submodule update --init # 编译BaseTools,这是EDK2的构建工具链 make -C BaseTools # 初始化EDK2构建环境 source edksetup.sh # 编译OVMF固件(X64架构) build -a X64 -p OvmfPkg/OvmfPkgX64.dsc -b DEBUG -t GCC5

第一次编译会花不少时间,因为要全量编译MdePkg、MdeModulePkg、OvmfPkg里的大量模块。编译产物在Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fdOVMF_CODE.fd。前者是包含变量存储的完整固件,后者是纯代码固件,适合配合独立变量存储文件使用。

拿到OVMF后,可以用QEMU直接跑:

qemu-system-x86_64 -bios Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd \ -m 2048 -smp 2 -drive file=disk.img,format=raw,if=virtio

这时候你会看到一个标准的TianoCore图形界面,和真实主板的UEFI设置长得不太一样,但功能都齐全。可以在里面用UEFI Shell、设置启动顺序、体验Secure Boot开关,出了问题也不会把物理机弄坏。我在Windows上给VMware Player装Win10遇到“UEFI引导失败”这类问题时,也经常先用OVMF在QEMU里复现一遍固件层面的配置,再回真机操作。

3.3 EDK2开发的几个核心概念

如果打算深入,EDK2开发有几个绕过不去的概念。

首先是Package的概念。EDK2不是一个大杂烩目录,而是按功能拆成多个包:MdePkg装的是UEFI规范定义的基础头文件和库,MdeModulePkg是通用的模块实现,OvmfPkg是针对QEMU平台的包,ArmVirtPkg是针对ARM虚拟机的包。每个包有.dsc(平台描述文件)、.dec(包声明文件)、.inf(模块构建信息文件)三种关键文件。

其次是Protocol,这是UEFI驱动之间通信的接口,有点像面向对象里的接口。比如你要在DXE阶段访问一个PCI设备,就去找gEfiPciIoProtocolGuid这个Protocol;要读写NVRAM变量,就用GetVariable/SetVariable运行时服务。写UEFI应用和写普通程序的感觉完全不同,因为没有一个libc在那里等你调,所有操作都要通过Protocol里的函数指针完成。

最后是构建系统。EDK2的构建器叫build,它通过解析.dsc.inf文件生成Makefile,再用GCC或MSVC编译。.dsc文件里可以控制要编哪些模块、开启哪些宏定义。比如编译OVMF时,通过-D SECURE_BOOT_ENABLE=TRUE就能把Secure Boot功能编进去,这个开关在很多厂商固件里也存在,只是默认被隐藏了。

这些年EDK2本身也在变。以前编译要依赖Subversion子模块,现在Git子模块用得多;BaseTools在向Python重写迁移,EDK2的代码风格和效率都比早年规范了许多。但整体上,固件开发依然是嵌入式领域里门槛极高的方向,需要同时懂硬件时序、芯片组寄存器和软件架构,做BIOS开发工程师的薪资高是有道理的。

4. 开源固件生态格局:不只是EDK2

4.1 一张图看清主流的开源固件项目

很多人以为开源固件就是EDK2,其实整个生态比想象中丰富得多。我按场景梳理几个最值得关注的项目,方便大家按需选择。

项目定位代表用法适用场景
TianoCore EDK2UEFI参考实现OVMF、各大厂商BIOS底座标准PC、服务器、虚拟机
coreboot高性能开源固件ChromeOS设备、社区主板追求快速启动、可定制启动链
U-Boot嵌入式引导加载器路由器、开发板、SoC平台ARM/嵌入式系统
LinuxBoot用Linux内核做固件数据中心服务器大规模服务器启动优化
OpenBMC服务器管理固件众多服务器BMC带外管理、远程运维

我自己最关注的组合是coreboot + EDK2。coreboot负责早期硬件初始化和快速启动,引导到EDK2的UEFI Payload,再由EDK2提供UEFI兼容层和ACPI表。这样既享受了coreboot的启动速度,又不牺牲标准UEFI的兼容性,很多开源主板项目都是这条路线。

4.2 coreboot的思路和争议

coreboot的历史比UEFI规范还早,它最初的核心理念是“尽量少干活,然后迅速把控制权交给下一个阶段”。所以coreboot不包含完整的驱动层和Shell,它做完内存训练、PCIe枚举后,直接加载一个Payload(可以是SeaBIOS、U-Boot、EDK2、Linux内核)。因为代码路径短,开机到Payload的时间可以压到几百毫秒级别。

但coreboot有一个绕不开的点:硬件初始化代码高度依赖芯片组厂商的二进制块(FSP/AGESA),很多核心初始化流程并不能完全开源。所以并不是所有主板都能跑coreboot,支持列表里主要是Intel 4代到12代左右的主流平台,以及一部分AMD平台。想玩coreboot之前,先上对应项目官网查一下自己主板的支持状态,别盲刷。我在折腾老旧ThinkPad时试过coreboot,确实能明显缩短开机时间,还能解锁很多原厂BIOS里被隐藏的选项,但对动手能力的要求不低。

4.3 LinuxBoot和未来方向

LinuxBoot的核心想法很激进:既然Linux内核本身就能驱动几乎一切硬件,那为什么引导阶段还要用一堆DXE驱动?它直接把一个精简的Linux内核编译进固件里,让内核完成硬件初始化,然后把启动参数交给系统引导。在数据中心场景,这能省掉大量重复的固件驱动,也让硬件初始化逻辑可以复用内核社区的维护能力。

不过LinuxBoot目前主要用于服务器领域,普通PC端还不太现实。更大的趋势是固件生态在向Rust这类内存安全语言靠拢,比如Rust UEFI工具链、rust-firmware社区等,EDK2本身也有新项目在尝试用Rust改造部分模块。但坦白说,固件行业的包袱很重,存量代码和硬件兼容性都是巨大的约束,指望一夜之间全面替换不现实。普通用户能感知到的变化,更多是Secure Boot更严格、远程固件更新(Capsule Update)更普及、以及越来越多的机器默认把CSM关掉,逼着大家一起拥抱纯UEFI引导。

5. 日常高频问题速查:从启动模式到BIOS魔改的实操经验

5.1 怎么判断电脑是UEFI还是Legacy启动

这是最基础也最容易踩坑的问题。Windows系统里,最简单的方法是运行msinfo32,看“BIOS模式”那一项是“UEFI”还是“传统”。也可以开命令行跑:

bcdedit /enum {current}

如果path里带\EFI\...,那就是UEFI启动;如果路径指向\Windows\system32\winload.exe,大概率是Legacy启动。

Linux下更直接,看/sys/firmware/efi目录存不存在,存在就是UEFI模式:

ls /sys/firmware/efi efibootmgr -v

这里要提醒一个现象:同一个硬盘,如果你在BIOS里开着CSM(Compatibility Support Module),系统既可能用Legacy引导也可能用UEFI引导,取决于启动介质里先找到了哪个入口。很多人装完系统之后想不起来当时用的是哪种模式,等Windows更新或想开Secure Boot时才发现模式不对,只能把CSM关掉重装引导加载器。最省事的做法是装系统前就确认好模式,纯UEFI机器就直接关CSM,别留混合模式给自己埋雷。

5.2 UEFI引导U盘:FAT32还是NTFS

这个问题大概每一个装机的人都会遇到。UEFI规范里固件原生支持的文件系统就是FAT系列(FAT16/FAT32),所以你的ESP分区和UEFI启动U盘必须是FAT32。如果你把Windows安装盘写成了NTFS格式,固件里的FAT驱动压根不认识,Boot Manager就找不到\EFI\BOOT\BOOTX64.EFI,直接跳下一条启动项。

但FAT32有一个硬伤:单个文件不能超过4GB。Windows 10/11的install.wim经常超过4GB,所以直接用官方ISO里的文件塞进FAT32启动U盘会提示文件过大。常规解法有两种:一是把install.wim拆成install.swm,二是先用UEFI启动U盘把系统装上PE环境,再从NTFS分区读取完整镜像安装。简单说就是“启动分区用FAT32,镜像文件放NTFS分区”这种组合方式。网上很多“UEFI引导U盘制作教程”里讲的其实就是这个道理。

这次顺手回答另一个高频问题:PE里看不到硬盘、BIOS里又有一块盘状态是Unconfigured Good。这个状态常见于带RAID卡或HBA卡的服务器上。Unconfigured Good表示磁盘物理上正常,但还没有被配置进逻辑卷或者没有做成直通盘,操作系统自然看不到。处理方式是进RAID卡的WebBIOS或storcli命令行,把这块盘创建成虚盘(VD)或者设置为直通(JBOD),系统才能识别。如果只是单盘不做RAID,用storcli /c0/e0/s0 set jbod=on之类命令直接切换模式就行。至于台式机主板,PE看不到硬盘则要优先查SATA模式是不是开了IDE、有没有开Intel VMD控制器,这两项都是硬盘识别失败的重灾区。

5.3 老主板不支持NVMe/不支持UEFI,魔改BIOS是怎么一回事

这几年“魔改BIOS”在DIY圈热度一直很高,核心诉求往往是两类:一是老主板没有NVMe引导模块,想用M.2 NVMe固态做系统盘;二是厂商把高级菜单隐藏了,想解锁功耗墙、调内存时序或者开Resizable BAR。本质上这些都是在改UEFI固件的镜像。

一般流程是这样的:先用编程器或软件方案把当前BIOS完整Dump出来,再用UEFITool(热词里那个UEFITool 0.28.0就是干这个的)打开固件卷,在DXE模块里找到CSMCORE、CpuDxe之类的位置,插入NVMe.ffs模块,或者修改Setup相关的IFR字符串来显示隐藏选项。改完重新封装后再刷回去。社区里流传的“D大魔改BIOS”之类资源,通常就是有人针对特定型号主板制作好的成品,直接刷了就能得到NVMe支持或解锁选项。

但这里必须把丑话说在前面:魔改BIOS风险非常高。一旦改动破坏了固件卷完整性,轻则刷完开机黑屏,重则需要编程器刷Flash芯片才能救回来。而且模块插入位置不对还会导致启动阶段卡死,比如PCIe设备初始化顺序变了、ACPI表对不上。我的建议是:如果只是为了支持NVMe,先看有没有官方更新BIOS,没有再看有没有成熟社区方案,最后才考虑自己动手改;刷写之前务必用编程器备份原始固件,留好后路。插拔编程器夹子对新手不太友好,但不备份就去刷魔改固件,是纯赌运气。

5.4 BIOS电池、上电自启和密码问题

这几类问题在办公环境尤其常见。电脑一断电再开机就起不来,SSD热插拔丢盘,这些跟CMOS电池和AC恢复策略都有关。

  • 上电自启设置:在BIOS的电源管理菜单里找Restore AC Power LossAC Power Recovery断电恢复后电源状态之类的选项,把它设为Power OnLast State。注意很多品牌机笔记本没有这个选项,需要在Windows里设置“电源选项-选择电源按钮功能-启用快速启动”来配合。
  • BIOS密码忘了:老规矩,打开机箱找主板上的CMOS跳线,短接几秒清除;或者拔掉CR2032电池放电。品牌机和笔记本还有一套独立于CMOS的密码存储,比如Dell的机器可能要联系售后换服务标签或者用专属工具解绑,不能一概而论。这点需要留意的是,清除CMOS也会清掉启动顺序和TPM相关状态,如果开了BitLocker又没保存恢复密钥,清完CMOS可能进不了系统。
  • BIOS电池没电:很多人以为电池没电只是时间不准,实际上CMOS电池电压不足还会导致SATA设置、启动顺序保存不住。因为NVRAM虽然不在CMOS里,但很多平台的RTC和部分配置信息仍依赖电池供电。遇到“BIOS无法保存SATA设置,每次重启都还原”这类怪问题,第一件事就换一颗CR2032,五块钱的成本能省一堆排查时间。

5.5 虚拟机里的UEFI固件:KVM与VMware的差异

最后聊一聊虚拟机里的UEFI。KVM/QEMU环境一般直接用OVMF,Debian/Ubuntu上装ovmf包之后,固件在/usr/share/OVMF/OVMF_CODE.fd/usr/share/OVMF/OVMF_VARS.fd。启动VM时可以这样指定:

qemu-system-x86_64 \ -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd \ -drive if=pflash,format=raw,file=OVMF_VARS.fd \ -m 2048 -cpu host ...

注意OVMF_CODE.fd要设为只读,变量写入走OVMF_VARS.fd。如果你让两块pflash都可写,变量容易写进代码段,重启后固件就可能跑飞。

VMware Workstation/Player里则是在虚拟机设置里选“固件类型-UEFI”,装Win10/11建议选这个,并且虚拟机要设成64位操作系统,否则UEFI引导不起来。很多人装Win10虚拟机忽略这个细节,默认用BIOS固件,装完一切正常,但等想开Win11 TPM和Secure Boot需求时才发现又得重来一遍。道理和物理机一样:引导模式应该在最开始就定清楚,中途切换非常麻烦。

我自己在实际维护中还有一个习惯:拿到任何新机器或新虚拟机,第一件事就是进固件界面看一眼启动模式、SATA模式、Secure Boot状态,截图存档。等出了问题时翻记录,能省掉大量重复排查。固件这个领域,很多问题不是因为原理多高深,而是因为状态没记录、步骤没留底,最后只能靠重启和reset解决问题。

折腾固件这件事,说到底就是在硬件和软件之间做翻译。从传统BIOS到UEFI再到开源生态,底层逻辑没变:硬件需要一段最初始的代码把自己唤醒,然后才能把控制权交出去。EDK2也好,coreboot也好,都只是在用不同方式回答同一个问题。如果你也想深入这一行,建议先拿OVMF练手,在虚拟机里把启动流程、变量、Shell都玩明白了,再考虑碰真主板。至少我当年从刷坏一块H61开始,到如今能自己编译固件、看懂BIOS模块,一路踩过来的经验就是:先备份,再动手,最后才是追求快。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询