做固件方向这些年,隔三差五就会有人拿同一类问题来问我:老电脑开机卡在LOGO,是不是BIOS坏了?服务器不支持UEFI引导怎么办?EDK2编译出来的固件能直接刷到主板上吗?
这些问题看似零散,背后却是一条越来越重要的主线——你每天开机那一两秒里运行的“BIOS”,早就不再是三十年前那个16位实模式小程序了。今天几乎所有x86主板、一大批ARM设备,跑的都是UEFI固件,而其中相当一部分底层实现都源自同一个开源项目:EDK2。
这篇文章我想用这几年做固件开发的视角,把从传统BIOS到UEFI、再到EDK2和整个开源固件生态的脉络串起来聊一遍。不管你是想搞明白“为什么UEFI引导U盘非得用FAT32”,还是准备入门固件开发、想看看EDK2编译到底是什么流程,这篇都能给你一个可以直接落地参考的底稿。
1. 别再叫它BIOS了:固件世界的两次换代
1.1 传统BIOS是怎么一步步走到尽头的
很多人习惯把所有主板固件都叫“BIOS”,这本身没什么问题,但从技术上较真,传统BIOS和今天的UEFI固件完全是两代人。
传统BIOS的历史可以追溯到1981年IBM PC时代,它的核心思路是把最基础的硬件初始化、中断服务、磁盘读写封装成一套固件例程。从它出生起,就有几个绕不开的硬伤:第一,它活在16位实模式下,地址空间被限制在1MB内,操作系统的引导最终只能靠一条简单的int 13h磁盘中断搞定;第二,它依赖MBR分区表,主分区最多4个,启动盘超过2TB就开始出各种幺蛾子;第三,它的驱动模型基本是“一次性买卖”,BIOS结束后系统就再也用不到它,也没有标准的安全验证机制。
后来厂商们在传统BIOS上打了各种补丁,包括加入ACPI、SMBIOS、DMI,甚至是漂亮的图形界面,但底子还是那套实模式框架。真正让它退出历史舞台的,是Intel在90年代末为安腾处理器提出EFI规范,再到2005年UEFI论坛成立、UEFI 2.x规范逐步完善。UEFI不是给BIOS换个皮,而是把整个固件层重写成了类似“微型操作系统”的架构。
1.2 UEFI到底改了什么:从启动协议到微型运行时
我第一次接触UEFI规范时,最直观的感受是:这玩意本质上不是BIOS,而是一套运行在硬件之上的接口标准,是“固件与操作系统之间的协议”。
UEFI的关键组件包括系统表(System Table)、启动服务(Boot Services)、运行时服务(Runtime Services)、协议(Protocol)和驱动模型。它用PE/COFF格式承载扩展程序,用Protocol的方式定义接口,启动时规范要求在硬盘的EFI系统分区(ESP)里找EFI\BOOT\BOOTX64.EFI之类的引导文件。相比MBR,UEFI几乎总是配GPT分区表,支持超大容量硬盘和更多分区。再加上Secure Boot和TPM,固件层第一次有了真正意义上的信任链验证。
这里有个经常被误解的点:UEFI设置界面只是它的一小部分。UEFI规范本身定义的是从复位向量到操作系统加载器之间的一整套环境,包括内存管理、事件、协议、驱动架构、网络栈、甚至命令行Shell。它在开机时把自己初始化成一个小型运行时,再把控制权交给OS,然后留一部分Runtime Services常驻内存供OS调用。这也是为什么现在的固件能支持鼠标操作、网络引导、图形输出,还能跑各种诊断工具,传统BIOS根本做不到这些。
所以当你再听到“BIOS”,心里要清楚:大多数消费级主板上的所谓“BIOS”,其实已经是UEFI固件,只是界面里还留了个CSM兼容模块,用来伪装成传统BIOS引导老系统。
2. EDK2:开源固件的事实标准
2.1 TianoCore与EDK2的来龙去脉
说到UEFI的具体实现,绕不开TianoCore。这是Intel发起的开源固件项目,核心代码库叫EDK2,全称EFI Development Kit II。最初只有x86代码,现在已经扩展到ARM、ARM64、RISC-V等架构,成为整个开源固件生态里最基础也最活跃的一棵树。
EDK2的代码仓库归属TianoCore组织,里面除了主仓edk2,还有edk2-platforms(各种SoC和开发板的平台包)、edk2-staging(实验性代码)、edk2-libc(UEFI环境下的C库)等。主仓里的核心包包括MdePkg(基础类型和接口定义)、MdeModulePkg(核心模块)、SecurityPkg(安全与Secure Boot)、NetworkPkg(网络栈)、ShellPkg(UEFI Shell)、OvmfPkg(虚拟化固件)等等。
EDK2的迭代节奏通常以UDK(UEFI Development Kit)为里程碑,比如UDK2018、UDK2022、UDK202405。圈内人平常说的“更新到最新EDK2”,往往指的就是跟住master分支或某个UDK版本。它与商业固件的关系就像Linux内核与发行版一样——你拿到的最终固件,几乎都是某家厂商基于EDK2改出来的。
2.2 固件源码是怎么变成芯片上的二进制
我见过很多新人第一次拉EDK2源码时直接懵掉,因为它不像普通应用工程那样一个Makefile就能构建。EDK2用了一套自己的描述体系,核心是四个文件类型:.dsc描述整个平台构建哪些模块,.dec声明包内的Protocol、PPI、PCD和库类,.inf描述单个模块的编译信息,.fdf描述最终Flash镜像的布局。
从源码到固件的链路大致是:先构建BaseTools工具链,然后执行edksetup.sh初始化环境,再调用build命令根据.dsc把模块编译成FFS文件,最后通过GenFds等工具打包成完整的固件镜像。比如编译一个OVMF(虚拟化UEFI固件)的常规操作是:
git clone https://github.com/tianocore/edk2.git cd edk2 git submodule update --init make -C BaseTools . edksetup.sh build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -b RELEASE -t GCC5等到构建完成,Build/OvmfX64/RELEASE_GCC5/FV/OVMF_CODE.fd和OVMF_VARS.fd就是可以拿到QEMU里跑的固件镜像。这里有个很关键的概念叫PCD(Platform Configuration Database),它相当于固件里的“可配置开关”,同一个模块可以在编译期或运行期通过PCD切换功能。Protocol则类似C++里的接口,模块之间不直接依赖具体实现,而是通过Protocol握手通信。理解了PCD、Protocol和PPI(PEI阶段的Protocol),你基本就拿到了阅读EDK2代码的钥匙。
2.3 EDK2和你在主板上看到的“BIOS”还差多远
你从主板官网下载的固件,和EDK2仓库里的源码并不是一回事。商业固件通常是在EDK2或者类似框架的基础上,叠加了厂商自研的Setup页面、平台驱动、CPU微码、显卡GOP驱动、RAID OptionROM、管理引擎固件等,再打包成一个很大的二进制镜像。
所以你能说“我下载的固件是开源的”吗?很难。像AMI Aptio、Insyde H2O、Phoenix SecureCore这些商业固件,虽然核心框架里有EDK2的影子,但最终交付物里大量二进制仍然闭源。这也解释了为什么“开源固件”和“开源EDK2”是两个层次的话题——EDK2本身是开源的,但消费级主板上的最终固件往往是开源框架+闭源组件的混合体。
真正想上手EDK2开发,建议不要一上来就去研究某个主板固件包,而是先在QEMU里把OVMF跑起来,改一改它的PCD、加一个自己的DXE驱动,感受一下固件开发的编译和调试循环,远比对着几百兆的FD文件瞎猜要靠谱得多。
3. 开源固件生态格局:TianoCore之外还有谁
3.1 coreboot:万物皆可快速启动
EDK2不是唯一的选择。coreboot(前身叫LinuxBIOS)是另一条重要的技术路线,它的理念和EDK2正好相反:EDK2倾向于打造一个功能完整、抽象层丰富的微型OS式固件,而coreboot追求最小化初始化,把硬件带起来之后直接跳转到一个payload(负载程序),由payload负责引导操作系统。
coreboot的特点一是启动速度快,很多Chromebook、瘦客户机、服务器主板用coreboot能把开机到系统的时间压到几秒;二是代码简洁、可审计性高,安全研究员和云厂商比较喜欢它。但它也有代价:支持的硬件范围比EDK2窄得多,如果你用的是一块非常规主板,很可能需要自己移植代码。
coreboot的payload可以是SeaBIOS(用来兼容传统BIOS引导)、也可以是UEFI固件(直接把EDK2编译成payload加载,这叫coreboot+UEFI组合方案)、更可以是LinuxBoot。这就带出了第三条路线。
3.2 LinuxBoot与Slim Bootloader:把Linux塞进固件
LinuxBoot的思路很激进:与其在固件层重复造轮子,不如直接把Linux内核作为固件的一部分,用它来做硬件初始化和引导。这样固件里跑的是真正的Linux内核驱动、真正的文件系统和网络栈,启动速度飞快,而且能复用整个Linux生态的安全性维护。
LinuxBoot在大型数据中心的服务器上有不少落地案例,典型架构是“coreboot初始化少量硬件 + LinuxBoot作为payload + u-root或initramfs把内核加载到内存”。它把固件里的闭源组件尽量压缩,用Linux驱动代替厂商OptionROM,这让运维人员可以直接在固件阶段做诊断、网络加载镜像,甚至跑脚本。
Intel还搞了一个Slim Bootloader,面向边缘计算和物联网设备,特点是高度可裁剪、通过配置脚本生成固件、镜像可以做到很小。嵌入式领域里U-Boot也实现了CONFIG_EFI_LOADER,可以在U-Boot之上提供UEFI API,让GRUB和systemd-boot这些EFI应用直接跑起来。再加上RISC-V生态里的OpenSBI、rust-sbi,整个开源固件的版图已经非常多元。
3.3 OVMF:藏在虚拟机里的UEFI
如果你没条件碰物理机,又想要一个可以随便折腾的UEFI环境,OVMF是最好的入口。OVMF是EDK2里的OvmfPkg,专门面向QEMU/KVM虚拟机提供UEFI固件。它支持Secure Boot、UEFI Shell、VirtIO驱动、ACPI和SMBIOS等,几乎把完整UEFI体验搬进了虚拟机。
启动一台带UEFI固件的QEMU虚拟机,最简命令类似:
qemu-system-x86_64 \ -machine q35 \ -drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \ -drive if=pflash,format=raw,file=OVMF_VARS.fd \ -m 2048 \ -cdrom some.isoOVMF_CODE.fd是只读的代码区,OVMF_VARS.fd是存放变量的存储区,这种分离设计和真实主板上的写保护机制是一个思路。很多虚拟化平台默认给虚拟机提供的就是SeaBIOS(传统BIOS),想实验UEFI启动方式、测试安全启动,装一个OVMF就能搞定,这也是理解物理机UEFI引导最安全的方式。
4. 日常UEFI实操:从启动盘到Shell急救
4.1 3分钟判断电脑是UEFI还是Legacy启动
实操问题往往比理论更困扰人,第一个高频问题就是:我这台机器到底是用UEFI还是传统BIOS引导的?
Windows下最简单的方法是运行msinfo32,在系统信息里看“BIOS模式”这一项,显示“UEFI”就是UEFI启动,显示“传统”就是Legacy。Linux下可以用一行命令判断:
[ -d /sys/firmware/efi ] && echo "UEFI mode" || echo "Legacy BIOS mode"如果启动时能看见图形化鼠标界面、只显示厂商Logo而不是蓝底白字的设置页,基本能确定是UEFI。另一个辅助信号是磁盘分区表:UEFI启动一般对应GPT分区,盘上会有一个几百MB的EFI系统分区;传统启动则大概率是MBR分区表。用lsblk -o NAME,PARTTYPENAME看一下有没有EFI System分区,心里就有数了。
还有种特殊情况是启动U盘时遇到error: bios/legacy boot of uefi-only media这类报错,意思是安装介质明确要求UEFI方式,但你的启动项配置成了Legacy。解决思路很简单:确认安装盘是用UEFI方式制作的,同时在固件里关闭CSM、或选择带UEFI前缀的启动项。
4.2 UEFI引导U盘:FAT32还是NTFS
这个问题的标准答案是无脑FAT32,原因在于UEFI规范对ESP分区的要求就是FAT/FAT32文件系统。固件里的FAT驱动是不能省的基础组件,但NTFS驱动、exFAT驱动在大多数固件里并没有内置,即使有也可能因为实现不完整而翻车。
但有个实际矛盾:Windows安装镜像里的install.wim经常超过4GB,而FAT32单文件上限正好是4GB。这时候我常用的做法是准备一个双分区U盘:第一个小分区格式化成FAT32,专门放EFI引导文件和boot.wim,第二个分区用NTFS或exFAT放完整镜像。另一个更省事的方案是直接上Ventoy,它会创建兼容性最好的多分区布局,在启动时模拟出固件需要的FAT环境,Windows和Linux镜像丢进去都能引导。
更老的机型不支持从FAT32的U盘UEFI启动时,还会有各种奇葩表现,比如启动项认不到盘。这时候先进UEFI Shell,用map -r看设备映射,如果再不行就检查U盘分区是不是MBR格式、有没有激活标志。很多所谓“固件不识盘”,其实是分区表类型不对。
4.3 老主板没有NVMe引导怎么办
现在还能看到不少老主板,固件里没有NVMe驱动模块,导致NVMe固态硬盘可以当数据盘,但没法从它启动系统。这种情况圈内俗称“魔改BIOS”,做法是用UEFITool之类的工具打开原厂固件镜像,提取或注入NVMe驱动模块,再刷回去。
原理不复杂:UEFI是通过Protocol发现设备,固件里缺了NvmeDxe驱动,启动管理器就看不到NVMe盘。你可以从别的固件里提取NVMe模块,用MMTool或UEFITool把它插入到同平台的固件卷里。但我要泼一盆冷水:跨平台注入、Flash布局不匹配、固件卷空间不足,任何一个坑都可能导致刷黑。
更稳妥的替代方案是用Clover四叶草或OpenCore这类引导器:把它们写到一个小U盘或老硬盘的EFI分区里,作为“引导代理”,再由它们加载NVMe驱动并引导系统。这种方式不动原厂固件,风险低得多,也是我处理老本子“电容键盘卡BIOS”“NVMe不识别”时优先推荐的思路。真正要动固件本身的魔改操作,务必确保有编程器、有原厂备份、并且愿意承担变砖风险。
4.4 UEFI Shell:固件自带的命令行急救室
UEFI Shell是UEFI环境里特别实用的工具,相当于一个跑在固件层的命令行终端。很多主板固件里有内置Shell,也可以把Shell.efi放到U盘的EFI目录下,从固件启动菜单里直接加载。入门级操作包括:
map -r ls fs0:\EFI bcfg boot dump -v dmpstore -d ver memmapmap -r重新扫描设备映射,bcfg boot dump能查看当前固件启动项,dmpstore可以浏览UEFI变量,这在排查“启动项丢失”“Linux更新后Windows引导消失”的时候非常有用。比如想查看某个UEFI启动项的完整路径,直接bcfg boot dump -v一清二楚;清理多余启动变量也有对应的bcfg子命令。
不过要提醒一点:UEFI Shell再强,也做不到随意读写整个SPI Flash芯片。很多刷BIOS工具其实是厂商自己的Shell命令行程序,或者需要配合flashrom之类的工具在OS层操作。指望Shell万能备份固件,不如老老实实用flashrom加编程器。
5. 固件排障现场:那些折腾人的问题
5.1 磁盘时有时无:固件层与系统层的错位
我收到过很多类似的问题:BIOS固件里明明能看到硬盘,PE启动盘却认不出来;或者硬盘状态显示unconfigured good,但系统里就是找不到盘。
先解释unconfigured good这个状态,它常见于SAS/RAID控制器场景。磁盘本身物理是好的,但没有被加入任何虚拟磁盘(VD)或直通配置,所以控制器层不会把它的盘符暴露给系统。解决思路是进入RAID控制器配置界面,把盘做成阵列或直通(passthrough),这和主板BIOS本身关系不大。
PE看不到盘则是另一类问题。如果固件设置了SATA为RAID模式(比如Intel RST),PE里没有IRST驱动就会丢失所有盘;固件以UEFI模式启动,但PE是Legacy引导的,也会导致GPT磁盘在传统中断路径下不识别。常用的排查顺序是:先确认启动模式统一,再把SATA模式改AHCI试试,最后往PE里注入AHCI/NVMe驱动。很多时候不是硬盘坏了,而是固件和引导环境之间没对齐。
5.2 Recovery Mode果奔:先别急着拆芯片
有块主板开机直接提示warning BIOS recovery mode has...,很多人第一反应是BIOS芯片坏了,马上拆机找编程器。其实Recovery Mode是固件的一种自保护机制,说明引导模块检测到异常,正在尝试从备份区或外接介质恢复。
我的建议是先冷静,让机器把恢复流程走完。如果它试图从U盘的固件文件恢复,你需要准备一个存有官方固件文件的FAT32 U盘,按厂商指定的命名放到根目录或特定目录。反复卡在Recovery Mode也不一定是芯片挂了,CMOS电池没电、内存接触不良、电源供电不稳都可能触发固件保护。
等确认外设、内存、电源都没问题之后,再考虑刷写路线。这一步千万别手滑选了别人改过的魔改固件,先恢复原厂默认再说。很多“什么固件都刷不进去”的故障,最后发现是SPI Flash写保护位被置位,或者主板上有硬件写保护跳线,根本轮不到拆芯片。
5.3 设置存不住、密码忘不掉:CMOS/EC/安全芯片那些事
“BIOS设置保存不了”“断电就恢复默认”,这类问题绝大多数是CMOS电池没电了。主板上的RTC电池负责在断电后维持CMOS存储和时钟,一旦没电,固件配置丢失是必然的。换电池是最便宜的排查手段,别一上来就怀疑固件损坏。
但笔记本上还有一层更隐蔽的存在——EC(Embedded Controller,嵌入式控制器)。EC负责键盘矩阵、电源管理、风扇控制,也是笔记本固件启动流程里最早工作的部件之一。很多人问“BIOS和EC通信是什么”,简单说,EC通过特定的I/O端口和ACPI事件机制与固件及操作系统交互,EC固件和BIOS固件经常放在同一个Flash芯片的不同区域。笔记本清CMOS不是拔颗电池那么简单,有时需要拆后盖、断开内置电池、长按电源键放电,甚至把RTC插头拔掉等几分钟,才能让EC和PCH的状态彻底复位。
顺带说一句BIOS密码,这类密码一般存在RTC或安全芯片里,CMOS放电有时能清掉,但遇到独立安全芯片的方案就很难绕过。网络上那些标榜“密码破解工具”的东西,效果非常有限,而且安全隐患很大。忘记密码的正路是联系厂商售后或走官方解锁流程,不是相信旁门左道。
5.4 固件备份与差分升级:动手前先留后路
做固件改动之前,备份是唯一保命手段。最通用的方案是flashrom:
sudo flashrom -p internal -r backup.rom如果只是备份某部分区域,可以按IFD(Flash Descriptor)分区来指定,比如只读BIOS区域、ME区域等。备份出的镜像用UEFITool打开,能看到固件卷(FV)、模块(FFS)、PE32/PE32+镜像,这也是提取NVMe模块、分析固件差异的基本操作。UEFITool 0.28.0是目前用得比较多的一版,图形界面和命令行都有,适合做这类静态分析。
固件升级则不一定要整片重写。现代UEFI固件支持Capsule Update机制,操作系统把新固件打包成Capsule,运行时调用UpdateCapsule服务写入。Linux下用fwupd配合LVFS就能实现这类无感升级。嵌入式设备的固件差分升级有更成熟的A/B分区方案,例如RAUC、SWUpdate,通过bsdiff之类的工具生成补丁包,网络不好也能可靠升级。
备份固件时要特别留意一个隐私问题:固件镜像里往往包含BIOS Descriptor、GigE MAC地址、UUID、平台证书等本机唯一信息。直接丢到公开论坛求分析,等于把机器的身份信息送出去。打码、脱敏、只提取必要模块,是基本的自我保护。
6. 魔改BIOS与固件安全:几句大实话
6.1 为什么我不建议刷别人的“魔改版”
每次讨论NVMe模块注入、解锁隐藏设置、改启动Logo,总会有人问“XX大魔改BIOS下载地址有吗”。我的态度很明确:玩可以,但不要拿别人改好的固件直接刷进自己主板。
原因有二。第一,固件没有可靠的签名验证前,闭源魔改二进制里到底加了什么,你根本无从知晓。有人可能只是改了Logo,有人可能塞了后门,这不是危言耸听,固件层的恶意代码一旦运行,优先级比操作系统还高,普通杀毒软件根本管不到。第二,魔改固件往往基于某个特定BIOS版本,和你的主板Rev、内存颗粒、CPU微码、外围芯片未必匹配。别人刷了能开机,你刷了可能就是变砖送修。
如果你确实需要某个功能,优先考虑合法方案:官方发布了带NVMe的新BIOS就升级官方版;官方不支持就用Clover这类引导代理;只是想清理老机器,干脆降级用回传统BIOS模式。如果一定要动固件,请先备份原厂固件、确认编程器到手、再挑一块不重要的板子练手。一个连原厂固件都没备份过的人,是没有资格刷魔改版的。
6.2 开源固件未来的方向,其实和你有关
聊完风险,再往远看一点。固件正在从“不可见、不可改、不可审计”的黑盒,慢慢走向“可见、可改、可审计”的开源生态。EDK2、coreboot、LinuxBoot、OVMF这些项目,让越来越多人有能力理解底层启动流程,也让服务器和数据中心有机会把固件纳入统一的安全管理。
对我个人来说,这股趋势最大的价值不是“人人都能改BIOS”,而是“出了问题你知道去哪里查”。以前主板固件黑屏只能怀疑“BIOS坏了”,现在你可以用UEFITool分析镜像、用flashrom读完整芯片、用UEFI Shell检查启动项,甚至自己改几行EDK2代码编一个OVMF跑起来。这种掌控感,是任何一层应用开发都给不了的。
如果你也想入门,我的建议是先别碰物理机刷写,装好QEMU,把OVMF编译一遍,再尝试往固件里加一个打印Hello World的DXE驱动。把这一步走通,你对UEFI、EDK2和整个固件生态的理解,就已经超过绝大多数只会按Del进设置页的玩家了。我自己也是从这个起点开始的,这条路一点都不神秘,缺的只是一次动手。