从BIOS到UEFI与EDK2:开源固件生态与启动排查实战

发布时间:2026/9/4 10:36:48
从BIOS到UEFI与EDK2:开源固件生态与启动排查实战 我第一次意识到 BIOS 和 UEFI 不是一回事是在帮同事调一台老服务器。镜像没问题、U 盘也写好了可机器就是死活不起来。后来查资料才发现那台机器默认只在 UEFI 模式下认盘而我的启动盘还是 Legacy 时代的 MBR 布局——两边不在一个频道上自然谁也找不到谁。这件事之后我开始认真翻 EDK2 的源码也顺带把整个开源固件的版图摸了一遍。回头看很多人在装机、装系统、甚至做固件开发时踩的坑本质上都是没搞明白 BIOS 到 UEFI 这条线到底发生了什么变化。这篇文章就以我这几年踩坑的亲身经历为线索把从 BIOS 到 UEFI、再从 EDK2 到整个开源固件生态的现状完整捋一遍特别适合对启动流程好奇的运维、想入行固件开发的工程师以及那些天天被“进 BIOS 设置”折磨的普通用户。1. 被骂了二十年“BIOS”其实早就换了内核1.1 从实模式到保护模式UEFI 解决的核心痛点老 BIOS 的全称是 Basic Input/Output System它的历史可以追溯到 IBM PC 时代。CPU 上电之后处于 16 位实模式BIOS 要在这个极度受限的环境里完成硬件自检、初始化中断向量表、为操作系统提供最基本的磁盘和显示服务。它调用硬件的方式非常“原始”靠的是 int 10h、int 13h 这类软件中断硬件厂商往固定位置塞驱动OS 来调用各管一段谁也别想越界。这套机制在软驱和 IDE 硬盘时代没什么大问题但到了 SATA、NVMe、USB 3.0、千兆网卡普及之后就开始力不从心了。BIOS 不认识 NVMe 盘、不支持从 GPT 分区引导、固件升级靠的是厂商自己的 DOS 程序安全和调试能力几乎为零。最典型的场景就是老主板想在 BIOS 层面认出 NVMe 固态硬盘来引导系统基本不可能要么靠 Clover 这类模拟器骗过去要么就把引导交给 grub 的手动命令。UEFI 的出现在根子上解决了两个问题第一固件本身跑在 32 位或 64 位保护模式下可以访问大内存、加载大驱动第二它把“初始化硬件”和“提供启动服务”做成了标准的 C 语言接口而不是一堆相互不兼容的中断。Intel 最早是为了安腾服务器设计 EFI2005 年把规范捐给 Unified EFI Forum 后改名 UEFI2006 年发布 2.0 规范到现在 UEFI 2.10 都出来了。1.2 UEFI 到底改了什么不是界面是接口很多人以为 UEFI 就是那个“可以用鼠标点的图形界面”这是最大的误解。图形界面只是 UEFI 规范里很表层的一部分真正的变化在于整套接口模型。UEFI 固件启动后会先完成平台初始化然后建立 Boot Services 和 Runtime Services 两套服务。Boot Services 在操作系统接管前有效负责加载驱动、管理协议、分配内存Runtime Services 在系统启动后依然保留其中最常用的就是变量服务 GetVariable/SetVariable——NVRAM 里的启动项、Secure Boot 的签名库都靠它。这里的“Protocol”机制特别值得一提。你可以把它理解成一套面向对象的驱动接口某个模块想使用某个功能就去系统里找对应 GUID 的 Protocol找到就能调用。这种解耦方式让厂商可以像搭积木一样拼固件模块也让第三方开发者可以独立写 UEFI 应用甚至可以绕过操作系统直接和硬件对话。我早期用过 UEFI Shell 里的 edit 命令改启动配置那种“还没进系统就能摸到所有硬件”的掌控感是 BIOS 时代完全没法想象的。1.3 为什么今天主板还叫它 BIOSOEM 的习惯陷阱现实里你开机按 Del 或 F2 进去那个界面几乎都是 UEFI 固件的设置界面但厂商仍然把它叫“BIOS Setup”。原因很简单用户习惯了这个词改了反而没人认识。这也导致一个非常普遍的认知错位——你在百度搜“戴尔 BIOS 设置”“华硕 BIOS 设置”搜出来的教程其实都指向 UEFI。这些界面里的选项也早就不是老 BIOS 的 IRQ、DMA 设置了而是 UEFI 的配置项启动模式UEFI/Legacy、安全启动Secure Boot、CSM 兼容模块、Resizable BAR、DVMT 预分配显存、TPM 开关等等。理解这一点之后很多问题就好解释了为什么同一张 U 盘在这台电脑能启动、换一台就报错、再换一台干脆黑屏大概率不是 U 盘坏了而是固件模式和分区表不匹配。后面我会专门用一整节讲这些排查方法。2. 启动链路拆解上电后到操作系统之间发生了什么2.1 SEC/PEI/DXE/BDS固件四大阶段UEFI 固件从 CPU 上电到交出控制权整个过程分为几个典型阶段虽然 EDK2 的具体实现里还会有细分但理解这四个阶段基本就够用SEC安全验证最开始的一段汇编代码负责 CPU 和系统最基本的初始化也是可信根Root of Trust的入口。PEIEFI 前初始化这个阶段最核心的任务是找出内存在哪里、容量多大、怎么配置。内存控制器一旦初始化好代码就能从 Cache-as-RAM 模式切换到真正的大内存环境。DXE驱动执行环境这是固件的主体。一大堆 DXE 驱动被加载提供磁盘、网络、显示、输入输出等服务协议库开始构建。BDS启动设备选择DXE 完成后进入 BDS固件根据 NVRAM 里的 BootOrder 依次尝试启动项最终跳转到操作系统的引导程序。我自己在调 UEFI 驱动时经常需要看串口日志日志前面的 CPU 型号、内存信息就是 PEI 阶段打出来的到了 DXE 阶段才会出现各种 Controller 和 Driver 的绑定信息。如果日志停在一个设备初始化上基本就是它的驱动出了问题。2.2 变量与 Boot Manager开机菜单是怎么存的老 BIOS 的启动顺序是靠 CMOS 里的几个字节控制的UEFI 则完全不一样。所有启动项都作为变量存放在 NVRAM 里变量名通常是Boot0000、Boot0001外加一个BootOrder变量决定优先级。这些变量可以在操作系统里看到和修改。Linux 下用efibootmgr -v就能列出当前所有启动项、Secure Boot 状态和启动超时时间Windows 下可以用bcdedit /enum firmware查看固件启动项。我之前遇到过“重装系统后启动菜单里出现两个 Windows”的情况根因就是旧引导项没被清掉用 efibootmgr 删掉对应 Boot000x 就干净了。顺带一提UEFI 的启动项并不直接指向硬盘分区而是指向分区上的一个 EFI 可执行文件路径比如\EFI\Microsoft\Boot\bootmgfw.efi。这也是为什么 UEFI 可以从 EFI Shell 里手动启动任意引导器——本质上就是执行一个 .efi 文件。2.3 安全启动不是“保险箱”PE 到 db 的签名链Secure Boot 是 UEFI 2.3.1 引入的机制目的是防止未签名或伪造的引导程序在启动链早期加载。它依赖三组密钥PK平台密钥最高权限管理 KEK 的更新。KEK密钥交换密钥管理 db 和 dbx 的更新。db允许数据库和 dbx禁止数据库决定哪些证书和镜像哈希被信任、哪些被拒绝。启动时固件要验证引导程序的签名是否在 db 里、且不在 dbx 里验证链一路传导到操作系统的引导加载器。Linux 发行版为了解决“我们不是微软签名怎么办”的问题普遍使用了 shim 方案——shim 由微软签名在 db 里它再去验证发行版自己的 MokList 数据库Machine Owner Key相当于在微软信任链下做了二次信任管理。很多 U 盘装系统失败就是卡在 Secure Boot 这一层你的镜像里的引导器没签名固件直接拒绝执行报Security Violation。解决办法要么进固件设置关掉 Secure Boot要么用 ubuntu、Fedora 这类带 shim 的官方镜像。2.4 GPT 与 ESPUEFI 只能认 FAT 的真相UEFI 规范规定固件必须能识别 GPT 分区表同时要求引导分区ESPEFI System Partition使用 FAT 文件系统。ESP 分区很小几百 MB 就够但必须是 FAT12/16/32。很多固件只认 FAT32NTFS 和 exFAT 默认不认。这个设计的原因很简单FAT 协议简单、驱动实现容易适合固化在固件里跑。代价是普通用户用 U 盘装系统时经常踩坑——NTFS 格式的 U 盘做启动盘固件根本看不到。GPT 分区表本身也承担了向下兼容的职责它在磁盘头部保留了一个保护性 MBR让老工具看起来像是一块已占用的 MBR 磁盘不至于误删数据。从 MBR 转向 GPT 并不是单纯换个分区表格式还要处理引导方式的变化这也是后面要讲的“磁盘布局不受 UEFI 支持”问题的最根本原因。3. EDK2开源固件的“标准答案”3.1 TianoCore 与 EDK2 的前世今生聊 UEFI 绕不开 EDK2EFI Development Kit II。它最早源自 Intel 的 Tiano 项目后来开源成 TianoCore 社区项目由 Tianocore 组织维护现托管在 GitHub 上是 UEFI 规范最完整的开源参考实现。EDK2 的价值可以这么理解UEFI 是一份规范文档告诉你怎么做EDK2 是一份能编译出固件的真实代码告诉你别人是怎么做的。它覆盖了从安全验证、内存初始化、驱动框架、启动管理器、Secure Boot 到 UEFI Shell 的完整实现。AMI、Insyde、Phoenix 这些商业固件虽然不完全开源但整体结构、模块划分和接口设计都能看到 EDK2 的影子因为整个行业都是从同一个技术源流长出来的。3.2 从源码到固件镜像DSC/DEC/INF/FDF 构建体系EDK2 的构建系统和普通 Linux 内核完全是两套思路。它由一堆“包”Package组成每个包里又有模块Module和库Library构建时通过四种描述文件组织DSC 文件平台级别的描述决定了要编译哪些模块、引用哪些库。DEC 文件包的声明定义 GUID、PCD可配置平台数据库、协议接口。INF 文件每个模块的构建说明声明源文件、依赖库、编译选项。FDF 文件Flash 设备布局决定哪些模块被塞进固件镜像的哪个位置。第一次接触 EDK2 的人最容易晕的就是这堆文件。我的建议是别急着逐行啃先从一个现成平台跑起来比如 OVMF。改配置再编译看产物变化比单纯读文档高效得多。3.3 OVMF跑在虚拟机里的 UEFIOVMFOpen Virtual Machine Firmware是 EDK2 针对 QEMU/KVM 虚拟机的固件移植也是学习 UEFI 和 EDK2 的最佳入口。你不需要真实主板在普通 Linux 虚拟机里就能构建、运行、调试一份完整的 UEFI 固件。OVMF 编译出来后会产生几个关键文件OVMF_CODE.fd固件镜像、OVMF_VARS.fdNVRAM 变量模板和合并版的OVMF.fd。调试 UEFI Shell 脚本、测试 Secure Boot、研究 Boot Manager 行为都可以在 QEMU 里做还能用 GDB 断点调试这在真实主板上几乎不可能。后面我会给完整的编译和运行命令。3.4 EDK2 的现实影响力从 AMI 到定制平台现在主流的台式机、笔记本主板固件不管界面上写着 AMI Aptio 还是 Insyde H2O底层走的都是 UEFI 规范这套体系很多模块甚至直接源自 TianoCore/EDK2 的开源代码只是做了闭源封装和厂商定制。在非 x86 平台EDK2 同样占据重要位置。ARM 服务器的固件、国产 CPU 平台比如龙芯、飞腾、兆芯的适配很多都是在 EDK2 基础上做板级移植。OpenBMC 社区维护的虚拟固件、树莓派的 UEFI 固件pftf/RPi4也都是 EDK2 的分支或移植。可以说想绕开 EDK2 谈现代固件开发几乎不可能。4. 开源固件生态不只是 EDK2 一家4.1 coreboot先初始化再交给谁如果 EDK2 是 UEFI 世界的“操作系统”那 coreboot 可以理解成更底层的“硬件初始化器”。coreboot 的思路非常激进它只负责把 CPU、内存、外设初始化到可用状态然后立刻把一个“payload”负载加载到内存并跳转过去。这个 payload 可以是 SeaBIOS也可以是 EDK2也可以是 U-Boot。coreboot 最大的优势是启动极快、代码精简、可审计性强。Chromebook 和不少开源硬件项目System76 笔记本、某些工作站主板都用它做底层。不过它对主板的适配工作量很大所以一直没有大规模渗透到消费级主板市场。如果你问“coreboot 生态里怎么用 UEFI”答案通常是把 EDK2 编译成一个 payload 模块加载到 coreboot 之后继续走 UEFI 启动流程。4.2 U-Boot嵌入式世界的 UEFI 翻译官U-Boot 是嵌入式领域最流行的 bootloader主要跑在 ARM、RISC-V 这些平台上。以前它的职责是在板子上初始化内存和外设、引导 Linux 内核和 UEFI 没什么关系。但从 U-Boot 2020 年左右开始它的 UEFI 实现越来越完善很多开发板的 U-Boot 可以直接启动标准的 UEFI 引导程序甚至运行 Windows on ARM 的启动链。这种“翻译官”位置让嵌入式开发和 PC 生态有了交集跑 U-Boot 的开发板也能用 systemd-boot、grub 这些工具也能读取 GPT 分区、识别 ESP 分区文件。对开发者来说这意味着统一的启动体验不需要为每个板子重新发明轮子。4.3 LinuxBoot 与 OpenBMC数据中心里的新变量数据中心场景对固件的要求和消费级 PC 完全不同更快启动、更容易调试、更方便自动化。LinuxBoot 的思路是把整个 Linux 内核直接变成固件 payload开机后 Linux 内核先起来再加载 UEFI 兼容层或其他引导逻辑。这样固件层就拥有了完整的 Linux 驱动和网络协议栈BMC 管理、远程刷新固件都变得非常灵活。OpenBMC 则是服务器 BMC基板管理控制器领域的开源固件。它负责机器带外管理也就是即使主机关机你也能远程查看硬件状态、控制电源、挂载虚拟介质装系统。OpenBMC 用 Linux Yocto 构建管理层逻辑完全可编程现在 Facebook、Google、IBM 等超大规模数据中心都在推。这些项目交织在一起构成了今天开源固件生态的骨架底层初始化有 coreboot、U-BootUEFI 标准实现有 EDK2服务器管理有 OpenBMC快速启动定制有 LinuxBoot。每一个层次都有可替代方案也有不同社区在推动。4.4 生态格局小结谁在哪个环节说了算用一句话概括现在固件生态的格局规范统一、实现多元。UEFI 规范本身是统一标准但上层实现五花八门。消费级市场基本被商业固件和 EDK2 主导嵌入式市场 U-Boot 占据半壁江山开源硬件和服务器领域 coreboot、LinuxBoot、OpenBMC 的声量越来越大。从影响范围来看这股开源固件浪潮已经不只是技术爱好者的玩具。安全合规要求让越来越多企业审查固件代码供应链透明也让开源固件从“可选项”变成“刚需”。我个人的判断是未来几年固件工程师的岗位需求会持续上升而 EDK2 作为底层参考实现的地位短期不会被撼动。5. 动手实战在 Linux 下编译并运行一份 EDK25.1 环境准备与依赖安装我平时的实验环境是 Ubuntu 22.04这些命令在 Debian 系发行版上基本通用。首先安装编译工具链和 EDK2 需要的依赖sudo apt update sudo apt install git build-essential uuid-dev iasl nasm python3 python3-distutils python3-venv如果是想跑 EmulatorPkg后面会讲再额外装一下图形库sudo apt install libx11-dev libxext-dev这里有个新手特别容易忽略的依赖nasm。EDK2 的一些汇编文件需要 NASM 编译不装会在构建时报nasm not found。另外 Python 环境也要干净建议用系统自带的 Python 3不要在主环境里搞一堆 conda 路径EDK2 的 BaseTools 对 Python 路径很敏感。5.2 拉取源码并初始化子模块EDK2 依赖少量子模块比如 BaseTools 的一些第三方库一定要先拉全git clone https://github.com/tianocore/edk2.git cd edk2 git submodule update --init --recursive然后编译 BaseTools 工具链并加载构建环境变量make -C BaseTools source edksetup.sh执行edksetup.sh后环境变量WORKSPACE和PACKAGES_PATH会被设置。构建系统会读取Conf/target.txt但通常我们直接通过命令行参数指定平台更直观也避免污染全局配置。5.3 编译 OVMF 固件OVMF 是最容易跑通的目标平台。执行build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -t GCC5 -b DEBUG解释一下参数含义-p指定平台 DSC 文件-a指定架构-t指定工具链-b指定构建类型。GCC5 是 EDK2 对 GCC 工具链的统称不是说你必须装 GCC 5。首次构建可能需要几分钟日志最后出现Successfully generated就说明成功了。产物在Build/OvmfX64/DEBUG_GCC5/FV/目录下。其中OVMF.fd是完整固件OVMF_CODE.fd是纯代码镜像OVMF_VARS.fd是空的 NVRAM 变量模板。真机刷写固件一般不直接刷 OVMF但在虚拟机里测试完全够用。5.4 用 QEMU 启动自己编译的 UEFI 固件编译好固件之后最快验证方式是用 QEMU 启动qemu-system-x86_64 -m 2048 -cpu qemu64 \ -drive ifpflash,formatraw,readonlyon,fileBuild/OvmfX64/DEBUG_GCC5/FV/OVMF_CODE.fd \ -drive ifpflash,formatraw,fileBuild/OvmfX64/DEBUG_GCC5/FV/OVMF_VARS.fd第一块 pflash 是固件代码只读第二块是 NVRAM 变量存储可写。这样启动后你会看到 UEFI 界面甚至直接进 UEFI ShellDEBUG 版 OVMF 默认会尝试进入 Shell如果没有 Shell 会提示找不到启动项。如果想进真实 UEFI Shell 环境可以单独编译 Shellbuild -p ShellPkg/ShellPkg.dsc -a X64 -t GCC5产物通常在Build/Shell/DEBUG_GCC5/X64/Shell.efi然后通过 QEMU 的-drive或 FAT 目录把它作为文件暴露给虚拟机在固件里手动执行。UEFI Shell 里最常用的命令是map -r重新扫描设备、ls、edit、bcfg管理启动项我第一次在 Shell 里看到固件设备映射关系时对 UEFI 的理解一下子通透了。5.5 进阶玩法EmulatorPkg 桌面模拟OVMF 需要 QEMU 来运行EmulatorPkg 则更进一步——它直接把 UEFI 固件作为一个普通 Linux/Windows/macOS 程序跑在桌面上。编译命令build -p EmulatorPkg/EmulatorPkg.dsc -a X64 -t GCC5运行./Build/EmulatorX64/DEBUG_GCC5_X64/Emulator会弹出一个带图形界面的窗口里面就是完整的 UEFI Shell。不用虚拟机、不用真实硬件非常适合快速测试 UEFI 应用、调试 Shell 脚本、验证 Secure Boot 策略。我在给别人讲固件开发时特别爱用这个演示因为它能让初学者在“普通 PC”上零成本看到固件长什么样。6. 常见坑位与排查实录启动问题的速查手册6.1 安全启动拦路Secure Boot 导致 U 盘引导失败故障现象很经典U 盘插上选择从 U 盘启动黑屏或者直接提示Security Violation/Verification failed。原因几乎都是 Secure Boot 开启而你的 U 盘引导程序和内核没有合法签名。排查步骤进入固件设置确认 Secure Boot 状态。Linux 下可以用mokutil --sb-state查看。如果确实开启关掉它位置一般在 Security 或 Boot 标签下不同 OEM 路径不同。如果你的镜像本身支持 Secure Boot比如 Ubuntu 官方镜像确认使用的是官方引导而不是第三方魔改工具。我个人在 U 盘装机时通常直接关闭 Secure Boot装完系统后再重新打开。Windows 11 安装要求开启 Secure Boot但安装介质本身能正常引导关掉只影响安装校验不影响启动盘制作。6.2 “BIOS/legacy boot of UEFI-only media”模式不匹配的最典型报错这个报错信息非常直白error: BIOS/legacy boot of uefi-only media。意思是当前固件被设置为 LegacyBIOS启动模式但你的启动介质只支持 UEFI 引导固件没办法在旧模式下读取它。反过来还有一种情况固件是纯 UEFI 模式但你的 U 盘还是 MBR legacy 引导启动时会直接跳过甚至提示找不到启动设备。两种模式不匹配是装机时出问题的最常见原因。排查思路确认主板当前启动模式UEFI 还是 Legacy有的叫 CSM 开启/关闭。确认启动介质分区表UEFI 需要 GPTLegacy 需要 MBR。查看启动盘 ESP 分区里有没有\EFI\BOOT\bootx64.efi这个默认引导文件。如果虚拟机里遇到这个问题记住 VMware/VirtualBox 都能在虚拟机设置里切换固件类型选对 UEFI 或 BIOS 就能解决一半的问题。6.3 U 盘用 FAT32 还是 NTFS固件只认识它想认识的这个坑几乎每个装机人都踩过。Windows 下用 Rufus 或官方 Media Creation Tool 做启动盘没问题但如果手动往 U 盘灌镜像经常顺手就把 U 盘格式化成 NTFS。结果 UEFI 固件根本不认 NTFS怎么设置都无法引导。原因前面已经说了UEFI 规范要求 ESP 分区是 FAT。解决方法是把 U 盘重新分区为 GPT并建立一个 FAT32 的 ESP 分区。把 EFI 引导文件放到\EFI\BOOT\bootx64.efi。或者用 Ventoy 这类工具它自己管理 FAT 启动分区不需要你手动处理格式。有一个细节要注意Windows 的diskpart里创建分区默认可能不是 FAT32 或容量超过 32GB 时格式化选项里没有 FAT32需要用第三方工具或者命令行指定才行。但启动介质本身用 FAT32容量大小并不影响。6.4 “磁盘布局不受 UEFI 支持”MBR/GPT 切换安装 Windows 时经常遇到这个提示“Windows 无法安装到这个磁盘。选中的磁盘采用 GPT 分区形式。” 或者反过来“该磁盘的布局不受 UEFI 支持”。本质都是固件模式和分区表格式不匹配。遇到“磁盘布局不受 UEFI 支持”一般意味着你处于 UEFI 固件模式但磁盘是 MBR 布局。Windows 安装程序要求 UEFI 模式必须搭配 GPTLegacy 模式搭配 MBR。解决方案有两条最省事在固件设置里切换成 Legacy/CSM 模式重启后重新安装。最干净把 MBR 磁盘转换为 GPT。Windows 10/11 自带mbr2gpt工具可以在现有系统里执行mbr2gpt /convert /allowFullOS注意转换前备份关键数据并且确保固件已经是 UEFI 模式。转换完成后最好手动进固件设置确认引导顺序。6.5 进不去固件设置、设置保存不了、Q-Flash 报错最后说几个和“进 BIOS 设置”本身相关的坑。进不去固件设置最常见的是 Fast Boot 开启后开机键响应窗口太短按键来不及。解决办法是重启时一直狂按 Del/F2或者在 Windows 里执行设置 → 系统 → 恢复 → 高级启动 → 立即重新启动 → 疑难解答 → 高级选项 → UEFI 固件设置 → 重启。这个路径可以强制让系统在下次启动时直接进入固件设置界面。设置保存不了比如改了 SATA 模式重启又变回去多半是 NVRAM 写入失败或 CMOS 电池没电。先换一颗纽扣电池排除硬件问题如果还不行留意是不是固件设置界面里的某类选项比如由固件策略锁定本来就是只读的。Q-Flash、M-Flash 这类主板自带刷写工具报“无法成功更新 BIOS 档案”十有八九是文件系统问题刷写工具只认 FAT32 分区所以要在 FAT32 格式的 U 盘中存放固件文件文件名和目录结构要严格符合主板说明不能随意改名固件解压后要放在根目录目录嵌套太深也可能导致识别失败。个别主板还要先把固件文件重命名为特定名称才能识别务必先看官方说明。我自己现在排查启动问题的固定流程是先确认固件模式是 UEFI 还是 Legacy再看启动介质分区表和 ESP 分区里的引导文件然后才是 Secure Boot、CSM 这些开关。这个顺序能解决九成以上的启动故障。最后分享一个小技巧每次刷固件或大改设置之前先把当前固件配置备份到 U 盘或者至少拍照留底。很多 OEM 的 UEFI 设置项在不同版本里默认值和可选项都会变——比如 Resizable BAR、DVMT 预分配内存这些恢复默认之后跟出厂状态未必完全一致。有备份在手踩坑之后几分钟就能还原比反复试错省心太多。