资讯动态

手写UEFI引导程序:从EDK2环境搭建到U盘启动盘实战

发布时间:2026/10/24 1:23:43 来源:尧图企业网站定制
简介这是一份《一个UEFI引导程序的实现》PDF电子书作者田宇以迭代式实例讲解了如何从零构建UEFI引导程序适合操作系统理论初学者、软件工程师及业余爱好者。书中先对比UEFI与BIOS的结构组成和启动流程说明UEFI取代BIOS的原因再基于TianoCore EDK2搭建开发环境讲解GPT磁盘布局、ESP文件系统、UEFI镜像与系统表、调用约定和协议最后逐步实现引导程序涵盖图形显示模式配置、物理内存获取、存储介质文件读取、64位处理器平台及系统内核引导启动并融入Linux内核精髓。资源为1个PDF文件压缩包2.36MB内容章节完整、目录清晰便于对照源码与运行示例研习。已有2379人学习下载适合想深入理解UEFI引导流程并动手实践操作系统的读者。1. UEFI引导程序为什么值得亲手写一遍现代计算机的引导过程早已不是BIOS时代的“读MBR、跳实模式”那么简单。只要你打算做一个自己的操作系统、想搞懂Linux内核是怎么被加载的或者被2TB磁盘容量限制、16位实模式这些BIOS遗留问题折磨过就绕不开UEFI引导程序。这本书从TianoCore EDK2环境搭建讲起手把手带你把一个可运行的UEFI引导程序写出来再把它放到U盘上真正启动。适合正在学习操作系统原理、准备阅读Linux内核源码或者想给现有系统增加UEFI引导支持的开发者。2. 搭建TianoCore EDK2开发环境先把编译器链条理顺2.1 环境选型与依赖包安装本书使用的开发环境是CentOS7 TianoCore EDK2的UDK2018分支。EDK2是Intel开源的UEFI开发环境它自带一套独立的编译系统支持GCC、MinGW、MSVC等工具链。之所以选CentOS7而不是更新的发行版一方面是因为UDK2018分支发布时CentOS7还是主流服务器系统GCC 4.8的工具链版本和EDK2的兼容性经过验证另一方面EDK2编译时对工具链版本有隐式依赖新版本GCC编译出的固件有时反而会在运行时出奇怪问题这点后面避坑章节细说。先执行依赖包安装yum install build-essential git uuid-dev iasl nasm libuuid-devel这里有个需要注意的问题build-essential和uuid-dev是Debian系发行版的包名在CentOS7的yum源里并不存在这个叫法。我一般会按CentOS的包名习惯拆开安装yum install -y make gcc gcc-c git iasl nasm libuuid-devel参数说明make、gcc、gcc-c编译BaseTools和EDK2模块组必需的基础编译链iaslIntel ACPI编译器编译ACPI相关的表和DSDT时要用nasm汇编器UEFI固件里少量汇编代码需要它完成编译libuuid-devel提供UUID相关头文件和库GPT分区操作和UEFI变量处理会用到装完后用gcc --version确认一下版本只要不报告找不到命令就算通过。EDK2对GCC版本的要求是能识别GCC48这个工具链标签即可新版本GCC也能用但需要在target.txt里换对应的TOOL_CHAIN_TAG新手不建议一上来就换。如果是在纯命令行的CentOS7上操作记得先把网络配好。yum安装时卡住最常见的就是源不可达国内网络环境下我一般会先替换成可用的yum镜像源再执行安装这一步不做的话后面git clone和yum install都可能在半路超时浪费时间又打击信心。2.2 克隆EDK2源码并切换UDK2018分支依赖装好之后从EDK2的git仓库拉取源码git clone https://github.com/tianocore/edk2.git cd edk2/ git checkout UDK2018说明clone下来默认是在master分支master的代码变化快和这本书配套的是UDK2018分支所以clone完成后立即切换分支。这里建议用git checkout而不是git branch加参数切换完用git branch确认当前位于UDK2018。这里有个细节后续编译OVMF或ShellPkg之前都需要在当前仓库目录下执行git submodule update --init --recursive。EDK2的部分模块依赖第三方子模块比如openssl、FatPkg等不先拉取子模块build阶段会直接报缺少头文件或目录不存在而且报错信息和具体原因往往对不上非常容易让人觉得是自己配置错了。2.3 编译BaseTools并初始化编译环境EDK2自带的编译工具链需要先编译一遍后续build命令才能正常工作make -C BaseTools/说明BaseTools目录里存放的是EDK2的固件编译工具包括GenFw镜像转换工具、GenFv固件卷生成工具、EfiRom等。make -C BaseTools/会编译出这些工具的本地可执行文件。编译时间取决于机器性能通常几分钟到十几分钟。BaseTools编译完成后初始化EDK2的编译环境BaseTools/BuildEnv . ./edksetup.sh这两条命令的作用是设置EDK2的环境变量和路径。注意中间的BaseTools/BuildEnv和edksetup.sh是两个可执行脚本前者设置工具链路径后者加载EDK2的编译环境变量。我习惯执行完建议用echo $EDK_TOOLS_PATH确认路径已正确设置如果输出为空说明脚本没有正常执行往往是当前目录不对——必须在edk2源码根目录下执行。如果你之前装过其他版本的EDK2Conf目录下会残留旧的环境配置文件。保险的做法是删除Conf目录里除了target.txt之外的历史配置或者直接执行make clean清掉BaseTools的旧编译产物再重新编译。旧产物版本不一致会引起各种莫名其妙的链接错误这类问题排查起来最费时间不如一开始就清干净。提示执行edksetup.sh之前务必确认当前shell在edk2源码根目录。脚本内部会相对路径定位Conf目录目录不对时环境变量设了个寂寞build命令会直接报找不到平台配置。2.4 target.txt三个关键配置项EDK2的模块组编译配置集中在Conf/target.txt文件里这个文件是build命令的输入配置文件。这本书的目标是编译64位OVMF固件和Shell应用对应的最小配置是ACTIVE_PLATFORM OvmfPkg/OvmfPkgX64.dsc TOOL_CHAIN_TAG GCC48 TARGET_ARCH X64参数说明ACTIVE_PLATFORM指定要编译的平台模块组OvmfPkgX64.dsc是OVMF开放虚拟机固件在X64架构下的平台描述文件TOOL_CHAIN_TAG告诉build命令用哪套工具链GCC48对应CentOS7默认自带的GCC 4.8TARGET_ARCH目标处理器架构这里X64就是64位模式这三个变量是联动的TARGET_ARCH决定了最终固件运行在什么模式TOOL_CHAIN_TAG必须和系统里实际安装的GCC版本对应。如果系统装的是GCC 8或更高版本可以试试改成GCC5/GCC7这类更新的工具链标签但可能需要在tools_def.txt里确认对应配置存在新手建议保持GCC48不动。配置好后执行build如果没有报错会在Build/OvmfX64/DEBUG_GCC48/FV目录下生成OVMF.fd固件文件。到这里EDK2的编译链就完全跑通了下一步就是编译真正的UEFI应用程序。3. 编译UEFI Shell并制作U盘启动盘做出第一个能跑的UEFI程序3.1 配置ShellPkg模块组UEFI Shell既是本书实验的验证工具也是后续排查引导问题的基础环境。EDK2里已经集成了ShellPkg模块组只需要把target.txt里的ACTIVE_PLATFORM换成ShellPkg/ShellPkg.dsc其他两个变量保持不变ACTIVE_PLATFORM ShellPkg/ShellPkg.dsc TOOL_CHAIN_TAG GCC48 TARGET_ARCH X64改完配置后执行git submodule update --init --recursive build说明build命令会读取Conf/target.txt按照ACTIVE_PLATFORM指定的模块组描述文件去编译。第一次编译ShellPkg会同时把MdePkg、MdeModulePkg等基础包编一遍所以输出信息会很长看到网络、文件系统、键盘鼠标等驱动模块一个个被编译属于正常现象。UEFI Shell默认会加载FAT文件系统驱动和PCI设备驱动所以编译时这些模块都会被一并编进Shell.efi里这也解释了为什么一个Shell应用编译产物有几百KB甚至更大。你不需要额外配置任何东西就能在Shell里访问FAT分区这是后面验证环节能跑通的前提。3.2 编译产物路径与文件识别编译完成后Shell应用位于以下路径Build/Shell/DEBUG_GCC48/X64/Shell.efi补充说明Build目录下按模块组名、编译类型、工具链标签、架构分层的目录结构是EDK2的统一产物布局。DEBUG_GCC48表示这是调试版本、GCC48工具链编译、X64架构。Shell.efi是PE/COFF格式的UEFI可执行文件它的头部格式和Windows下的PE文件同源UEFI固件通过文件系统协议读取它并加载执行。此时可以顺手验证一下文件格式file Build/Shell/DEBUG_GCC48/X64/Shell.efi正常情况下输出会显示PE32 executable (EFI application) x86-64确认无误后这个文件就是我们要放到U盘上的核心内容。如果你在Build目录里看到多个Shell.efi优先取DEBUG_GCC48路径下的那个RELEASE版本是Release编译模式的产物会在后续生成两者都能用但DEBUG版本的调试符号和日志信息更全学习阶段建议用DEBUG版本。3.3 用DiskGenius转换U盘为GPT布局并创建ESP分区UEFI固件要求可引导块设备必须包含ESP系统分区。普通U盘出厂默认是MBR分区表Windows下用DiskGenius把分区表类型转换为GUID格式具体操作是清空U盘扇区数据右键磁盘→转换分区表类型为GUID格式软件会弹出确认对话框提醒原分区将失效确认后磁盘变成GPT布局。接下来点击新建分区在弹出的对话框里勾选“建立ESP分区”分配20MB空间勾选“对齐到此扇区数的整数倍”按4096扇区2097152字节对齐。ESP分区的作用是存放UEFI引导程序分区格式会被格式化为FAT16/FAT32。再创建一个10MB的FAT32分区GPT磁盘布局下只有主分区类型不存在扩展分区和逻辑分区的概念所以可见分区比MBR时代更干净。这里有两个点值得展开。第一ESP分区的大小20MB在当前场景够用因为只是放一个Shell.efi和后续的引导程序但如果你打算在ESP里同时放多个操作系统的引导加载器建议留到100MB以上第二4096扇区对齐是现代SSD和U盘的通用对齐方案对齐后访问性能更好在虚拟机环境里影响不明显但换到物理机上差距就出来了。还有一点需要留意DiskGenius创建ESP分区后U盘里会出现两个分区但Windows资源管理器里能看到的只有FAT32分区ESP分区完全不可见。这会让很多人误以为ESP分区没创建成功其实只要用Linux挂载确认一下就能验证。3.4 挂载U盘拷贝引导文件Windows系统出于保护机制限制了对ESP分区的访问资源管理器里看不到它。解决方法是把U盘插到CentOS7机器上Linux内核原生支持GPT分区表用root权限可以直接挂载。先查看U盘设备节点[rootpromote edk2]# fdisk -l /dev/sdb Disk /dev/sdb: 33 MB, 33554432 bytes, 65536 sectors Disk label type: dos Device Boot Start End Blocks Id System /dev/sdb1 1 4294967295 2147483647 ee GPT看起来只显示了一个/dev/sdb1而且分区类型是GPT伪分区这不是我们创建的ESP分区。原因是fdisk这个工具对GPT分区表的兼容性不好它把protective MBR里的记录当成一个分区列出来了。用ls命令能看到真实情况[rootpromote edk2]# ls /dev/sdb* /dev/sdb /dev/sdb1 /dev/sdb2其中/dev/sdb1是ESP分区/dev/sdb2是FAT32分区。确认节点后再执行拷贝mount /dev/sdb1 /mnt/ mkdir -p /mnt/EFI/BOOT cp /root/Desktop/edk2/Build/Shell/DEBUG_GCC48/X64/Shell.efi /mnt/EFI/BOOT/BOOTx64.EFI sync umount /mnt/这几条命令的逻辑是先挂载ESP分区到/mnt创建EFI/BOOT目录——这是UEFI规范约定的默认引导路径然后把Shell.efi复制过去并改名为BOOTx64.EFI。UEFI固件在找不到其他引导项时会按照\EFI\BOOT\BOOTx64.EFI这个默认路径去尝试加载64位应用程序所以改名这一步不能省略。sync强制刷盘确保数据落盘后再umount避免拔掉U盘后文件丢失。整个拷贝操作里最容易出错的是目标路径。很多初学者把Shell.efi直接拷贝到ESP分区根目录改名BOOTx64.EFI但UEFI引导管理器默认不会去根目录找引导文件它只会按\EFI\BOOT\BOOTx64.EFI这个固定路径查找。目录结构少一层引导就失败而且失败提示往往是很笼统的“找不到可引导设备”查起来全靠经验。注意ESP分区的挂载操作需要root权限普通用户执行mount会报Operation not permitted。另外U盘的文件系统如果之前被Windows格式化过建议在DiskGenius里重新分区而不是只转换分区表清空残留分区记录能省去后续很多兼容性麻烦。4. 配置UEFI虚拟机环境让固件从U盘引导4.1 创建64位虚拟机考虑到大部分读者手头没有支持UEFI引导的裸机或者不想为了一个引导程序反复重启物理机这本书的实践环境选择在VMware虚拟机里完成。创建虚拟机时需要选择64位操作系统环境这是因为UEFI引导程序最终运行在64位保护模式下32位环境无法复现书中的X64架构实验结果。虚拟机磁盘镜像可以选择现有Linux/Windows的安装镜像做基础也可以直接创建一个空虚拟机。我一般建议创建一个空虚拟机后续只用U盘做引导介质避免硬盘上的既有引导器干扰UEFI固件的启动顺序。内存建议分配到2GB以上UEFI固件在DXE阶段会加载大量驱动内存太小会出现固件卡死的现象。虚拟机创建过程中选择客户机操作系统版本时尽量选带“64-bit”字样的项。某些版本的VMware如果一开始创建了32位虚拟机后面再改成64位会比较麻烦需要重建虚拟机或修改虚拟机配置文件里的guestOS参数别给自己找麻烦一开始就选对。系统安装步骤可以直接跳过因为实验不需要在虚拟机上装系统U盘本身就是引导介质。4.2 修改固件类型为UEFIVMware虚拟机默认使用传统BIOS作为引导固件这和我们的目标不符需要手动修改。进入虚拟机设置→选项→高级→固件类型将固件类型改为UEFI点击确定保存。这一步是整个实验的关键转折点只有固件类型是UEFI虚拟机才会按照UEFI规范去扫描ESP分区、加载BOOTx64.EFI而不是去读取传统MBR引导扇区。如果保持BIOS固件不变前面花大力气做的GPT分区U盘插进去根本不会被识别现象就是开机黑屏或直接进入无引导设备提示让人误以为U盘制作失败。为什么UEFI固件能找到U盘上的Shell这与UEFI的设计思路有关。UEFI固件自带引导管理器Boot Manager它在启动时会扫描每个块设备上的ESP分区查找\EFI\BOOT\BOOTx64.EFI文件。找到后通过FAT文件系统驱动把文件读入内存按照PE/COFF镜像格式解析并执行。整个流程不再依赖引导扇区代码不再需要实模式下的中断调用而是完全以文件为单位进行操作这正是UEFI比BIOS灵活的原因。这也是为什么我们在拷贝文件时必须严格放置在EFI/BOOT目录下并命名为BOOTx64.EFI——引导管理器查的就是这个路径路径不对一切白费。修改固件类型后建议顺手检查一下虚拟机的引导顺序确保UEFI固件会把USB设备放在引导列表的前面。VMware的固件配置里可以通过上下键调整引导顺序U盘排第一、硬盘往后放这样插着U盘就能优先从U盘引导。4.3 添加USB控制器如果虚拟机创建时没有配置USB控制器进入虚拟机→硬件界面点击“添加硬件设备”向导在设备类型里选择USB控制器按向导提示完成添加。添加完成后在硬件列表里可以看到新出现的USB控制器设备对它设置USB兼容性版本和连接状态。这里有一个容易被忽略的细节U盘要在虚拟机启动之前连接到USB控制器上。VMware的USB设备连接不是在虚拟机设置里勾选而是在虚拟机窗口底部的设备图标菜单里选择“连接”或者在虚拟机设置里配置启动时连接。我一般先把U盘插到物理机USB口在虚拟机开机前通过设备菜单把U盘分配给虚拟机再启动虚拟机这样UEFI固件在枚举USB总线时就能发现U盘引导自然成功。如果启动后才把U盘接入虚拟机UEFI固件的引导设备选择阶段可能已经结束即使后续能在Shell里看到U盘设备也无法把它作为启动介质需要重启虚拟机才能正常引导。这个细节在物理机上同样适用开机后再插U盘按F12进启动菜单也常常看不到U盘选项因为固件在POST阶段就已经完成USB设备枚举了。4.4 卸下网络适配器UEFI固件在BDS阶段会主动探测网络适配器尝试从PXE网络引导服务器获取启动映像。在无DHCP和TFTP服务的实验环境里这个过程会一直等到网络超时导致整个引导过程慢得让人以为机器死机了。解决办法是在虚拟机硬件配置里把网络适配器卸下或设为不连接。操作路径是虚拟机设置→硬件→网络适配器→取消“启动时连接”的勾选或者直接删除该设备。卸掉网络适配器之后UEFI固件会快速跳过网络引导检测几秒钟内就能进入Shell或引导失败提示实验节奏顺畅很多。这个网络引导等待的问题不仅发生在VMware里物理机裸机装UEFI时同样会遇到。很多品牌的台式机默认开启PXE引导插着网线开机就会卡在“Start PXE over IPv4”的界面解决思路是一样的——要么在固件设置里禁用网络引导要么拔掉网线。把虚拟机当成实验平台的好处就在这里硬件配置可以随意调整不用在BIOS设置里翻来找去。5. 避坑与排查这些坑我都踩过EDK2这套环境说难不难但每一步都有对应的“标准答案”。如果只是照着命令敲出现报错就懵了很容易卡在一两个细节上怀疑人生。下面这几条是我在复现这本书实验过程中真实踩过的坑每条都按现象→原因→解决写清楚你在搭建环境时遇见了直接对照处理即可。5.1 现象yum install build-essential报No package原因build-essential是Debian/Ubuntu系统的包组名称CentOS7的yum源里根本没有这个包名直接照抄会装不上。解决改为按CentOS7的包名安装实际需要的是make、gcc、gcc-c、iasl、nasm、libuuid-devel。如果是在全新最小化安装的CentOS7上操作还要额外确保git已经装上。装完之后用rpm -q make gcc确认关键包存在。还有一个小坑CentOS7最小化安装时默认的yum源可能不全尤其是extras和epel源未启用的情况下nasm和iasl可能装不上。遇到No package nasm available就先检查yum源配置启用epel源后再试。我一般会在安装完系统后第一时间执行yum install epel-release把扩展源加上避免后续装包时缺这少那。5.2 现象编译ShellPkg时openssl头文件缺失原因EDK2的部分模块依赖git子模块比如加密相关的openssl、FAT文件系统驱动可能依赖FatPkg子模块。clone主仓库时子模块默认不会拉下来不执行git submodule update --init --recursive直接build编译到依赖模块就报错。这类报错信息往往指向另一个模块看起来像玄学其实是子模块缺失导致的链式编译失败。解决在edk2源码根目录执行git submodule update --init --recursive等子模块全部下载完再重新build。网络环境差时这一步可能很慢中间断了就重新执行一遍git会断点续传。还有一类编译问题值得单独说。如果你之前编译过master分支的EDK2再切回UDK2018分支时Conf目录里会保留旧的target.txt和tools_def.txt这些旧配置可能引用了master分支才存在的模块路径导致build阶段报“文件不存在”或“平台描述文件缺失”。我一般会执行make -C BaseTools clean清理旧编译产物必要时删掉Conf目录重新执行edksetup.sh生成默认配置再手动改target.txt。这个习惯帮我避开了很多莫名其妙的编译问题。5.3 现象fdisk -l只显示一个GPT伪分区原因fdisk工具对GPT分区表的兼容性不好它读取的是protective MBR里的分区记录所以把整个GPT磁盘当成一个类型为eeGPT的分区显示出来并不是真的有只有一个分区。解决用ls /dev/sdb*或parted /dev/sdb print查看真实分区布局。ls能看到/dev/sdb1ESP分区和/dev/sdb2FAT32分区两个设备节点说明GPT分区创建成功放心继续挂载操作。不要因为fdisk的输出和预期不符就反复重新分区那是白费功夫。5.4 现象Windows资源管理器里看不到ESP分区原因Windows系统对GPT磁盘的ESP分区默认不分配盘符也不允许通过资源管理器访问这是系统的保护机制不是分区没创建成功。解决不需要在Windows下访问ESP分区。把U盘插到CentOS7或其他Linux系统上用mount /dev/sdb1 /mnt挂载后操作即可。如果手头只有Windows也可以用DiskGenius的浏览文件功能向ESP分区拷贝文件但那样不如Linux下方便我一般都是在Linux虚拟机里做这一步。类似的坑在制作Linux启动U盘时也会遇到。很多人U盘插上去发现引导不了第一反应是U盘没做对实际上往往是固件引导模式不对——传统BIOS模式的机器要写MBR引导UEFI模式的机器要写GPTESP分区两者不能混用。本书里的实验全程走UEFI路径所以U盘的分区表必须转为GPT这也是前面特意用DiskGenius转换分区表类型的原因。5.5 现象虚拟机UEFI引导卡在PXE网络启动原因VMware默认固件是BIOS没改成UEFI时GPT磁盘无法引导改成UEFI后如果网络适配器还连着固件会尝试PXE网络引导等待DHCP超时看起来像死机。解决第一件事去虚拟机设置确认固件类型已经是UEFI第二件事把网络适配器卸下或取消“启动时连接”。还有第三件事U盘要提前连接到虚拟机的USB控制器上再开机启动后再插U盘可能错过引导设备枚举阶段。这三件事按顺序检查一遍绝大多数引导失败问题都能解决。如果确认以上都没问题但虚拟机还是不进Shell可以在开机时按F2进入UEFI固件设置界面查看Boot Manager里是否列出了USB设备以及引导顺序里U盘是不是排在第一。UEFI固件设置界面本身就是排错工具不要忽略它。6. 装完怎么验证跑通用U盘把虚拟机拉进UEFI Shell验证的关键动作是把前面做好的U盘插入已配置UEFI固件的虚拟机开机看到UEFI Shell命令行界面。6.1 验证步骤具体操作流程虚拟机处于关机状态→设备菜单里把U盘连接到USB控制器→开机→UEFI固件启动后自动扫描引导设备→从U盘的\EFI\BOOT\BOOTx64.EFI加载Shell.efi→进入Shell命令行。进入Shell后先执行map -r这条命令刷新并显示当前UEFI可访问的设备映射表。正常情况下能看到fs0:这类FAT文件系统句柄以及blk0:这样的块设备句柄。如果能看到fs0:说明FAT文件系统驱动加载成功ESP分区被正确识别。再用内存映射命令验证UEFI环境状态Shell memmap输出会列出当前UEFI环境下的物理内存映射表包括可用内存Available、保留内存Reserved、MMIO区域等。能在Shell里看到真实内存布局说明UEFI固件已经完成了PEI和DXE阶段的内存初始化CPU运行在64位保护模式下这比任何教科书描述都直观。如果U盘没有被识别先用exit命令退到固件设置菜单检查引导顺序里USB是否排在第一位。实在不行就重启虚拟机在固件设置界面用Boot Manager手动选择U盘设备。6.2 一个值得保留的验证习惯验证完Shell能进还不算完整走通。你可以顺手在Shell里访问ESP分区确认引导文件位置Shell fs0: FS0:\ dir \EFI\BOOT\能看到BOOTx64.EFI列出来说明从分区创建、文件拷贝到固件加载这条UEFI引导链路全部畅通。这套验证习惯我后来在排查物理机的引导故障时也一直沿用凡是UEFI引导不走先确认ESP分区存在且引导文件路径正确再排查固件设置基本能定位90%的问题。从那以后我每次做完U盘引导盘都会先在虚拟机里跑一遍这套验证流程再拿给物理机用省掉了不少来回插拔U盘的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑