☰
手写UEFI引导程序:从EDK2环境搭建到内核跳转的完整实现
2026/10/24 1:16:02 网站建设 项目流程

简介:《一个UEFI引导程序的实现》是一本面向操作系统开发学习者的PDF电子书,围绕UEFI固件编程展开,帮助读者在掌握《一个64位操作系统的设计与实现》的基础上,为自研系统补充UEFI引导能力。内容从BIOS与UEFI的结构差异、启动流程入手,解释UEFI取代BIOS的原因,再基于TianoCore EDK2环境,依次讲解GPT磁盘布局、ESP文件系统、UEFI镜像、系统表、调用约定与协议,并通过Hello World示例逐步过渡到引导程序实战,包括配置显示模式、获取物理内存、读取存储介质、适配64位平台以及引导操作系统内核启动。资源包仅包含1个PDF文件,大小约2.36MB,方便阅读与检索;已有2379人学习。对准备深入学习内核引导、Linux源码或自主完成引导开发的初学者和工程师而言,书中内容能提供从零到可运行引导程序的完整思路与配套实践指引。

1. 一个UEFI引导程序,真的可以一步步手写出来

市面上的操作系统教程,大多把引导这层当黑匣子,一句“由引导程序加载内核”就带过了。真到自己动手,你会发现这段链路才是全场最折腾的部分:UEFI固件按什么规则找到你的.efi文件?ESP分区里应该放什么?固件把控制权交出来之前,哪些服务还能用、哪些已经失效?《一个UEFI引导程序的实现》这份资料解决的正是这些问题。它以EDK2为开发环境,从编译一个可用的UEFI Shell开始,逐步把显示模式、物理内存探测、磁盘文件读取、64位环境切换串成一条完整的引导链路,最后把自研内核从ESP分区读进内存并跳转执行。适合准备写操作系统或引导加载器的开发者,也适合想在读Linux内核源码前补齐固件侧上下文的人。整本书没有停留在概念翻译上,每个结论都能落到可运行的代码。

2. 底层差异:UEFI与BIOS的结构组成、启动链路与设计取舍

2.1 结构组成:BIOS的中断表与UEFI的系统表

BIOS把固件能力封装成一组中断服务。程序想读磁盘、写串口,触发INT 13H、INT 10H这些中断即可,硬件差异由固件消化。这样设计的问题在于:整套代码跑在16位实模式下,开发以汇编为主,新硬件要加功能只能往0x0C0000到0x0DFFFF这段有限的ROM区间里塞。静态链接、插桩式升级,整个体系很难扩展。

UEFI换了设计思路,它的接口表现形式是数据表——EFI_SYSTEM_TABLE(系统表)。这张表是全局入口,里面挂着控制台输出、引导服务(Boot Services)、运行时服务(Runtime Services)和一组协议句柄。设备访问不再靠中断,而是通过句柄与协议抽象:先找到设备句柄,再按GUID取出对应协议,调用协议里的函数。驱动是独立的模块,运行时可动态加载,固件体积和功能扩张不再受ROM物理空间约束。

对比项BIOSUEFI
接口形态中断向量表系统表 + 句柄 + 协议
运行环境16位实模式32位保护模式 / 64位长模式
扩展方式静态链接固件代码动态加载.efi驱动模块
开发语言汇编为主以C/C++为主
安全校验无支持SecureBoot签名校验
磁盘寻址32位LBA,2TB上限GPT,突破2TB限制

UEFI规范的另一个特点是只定义接口,不规定实现。凡是兼容UEFI的处理器平台都按同一套接口来写固件,操作系统不必为每个平台单独定制加载器,平台也能自由扩展新特性。这种“接口与实现解耦”的思路,才是UEFI取代BIOS的底层原因。

2.2 启动流程:BIOS的8个步骤与UEFI的7个阶段

BIOS的启动链路可以拆成8步:上电后CPU在实模式执行,从物理地址0x0FFFF0取出第一条跳转指令,进入BIOS入口后依次完成加电自检(POST)、初始化显示设备、检测CPU与内存、检测标准设备、检测即插即用设备、把变化的配置写回CMOS,最后按启动顺序把引导设备上的程序读入内存并交出控制权。这8步是“全量检测”思路——不管后面用不用,先把所有设备摸一遍。

UEFI把启动过程拆成7个阶段,按缩写记更清晰。SEC阶段内存还没初始化,只能在处理器缓存里搭临时栈,顺便把CPU切到保护模式;PEI阶段做最关键的内存、CPU、芯片组初始化,代码要求越精简越好;DXE阶段是驱动大本营,大部分固件驱动在这里加载;BDS阶段负责选启动设备、初始化控制台环境变量;TSL阶段就是UEFI Shell所在的临时系统环境;等到操作系统调用ExitBootServices之后进入RT阶段,DXE和引导服务全部销毁,只剩运行时服务和系统表可用;最后的AL阶段是系统复位或关机后的状态,在服务器和工作站里比较常见。

对写引导程序的人来说,RT这个分界点最重要。调用ExitBootServices之后,分配缓冲区、句柄、协议这些Boot Services全部失效,如果内核代码还依赖固件提供的服务,一进内核就崩。所以成熟的操作系统在跳转前会自己维护一份内存映射、重建页表,而不是依赖UEFI留下的环境。

2.3 UEFI取代BIOS:16位实模式与2TB寻址只是开始

书里把BIOS的缺点列得很直接:开发效率低、性能差、扩展性差、无安全考虑、不支持2TB以上硬盘引导。前三个可以归为一类——基于汇编的静态链接代码注定难以适应硬件迭代速度。BIOS不够灵活的另一个体现是它没有动态加载驱动的方案,新硬件只能等固件升级,而固件升级本身又受限于ROM空间。

UEFI的优势反过来看更清楚:驱动模块化意味着硬件初始化可以拆成独立的.efi驱动,按需加载,不用把整个设备列表遍历一遍;异步事件机制取代了轮询式中断,CPU等待时间大幅缩短;安全启动则给可执行代码加了一道证书校验门槛。启动速度的提升其实不只在“快”,而在“少”——UEFI不再做BIOS时代那些全量检测,把资源留给真正需要的部分。还有一点容易被忽略:UEFI退出引导前,CPU已经运行在保护模式或IA-32e模式。引导程序不需要像BIOS时代那样自己从实模式切到保护模式再开长模式,UEFI已经铺好了部分地基,只是页表、GDT这些内核自己的设施仍然要重新搭一遍。

3. 开发环境与第一个UEFI程序:从EDK2编译到Hello World

3.1 EDK2环境搭建:工具链、分支与target.txt配置

EDK2是目前最主流的开源UEFI开发套件,自带编译系统,支持GCC、MinGW、Visual C++等交叉编译器。搭建过程不复杂,先装基础软件包:git、编译器、iasl(ACPI编译器)、nasm(汇编器)、uuid-dev等,然后把edk2仓库克隆下来,切到UDK2018分支。这个分支相当稳定,配套资料最多。

接下来编译BaseTools并source环境脚本,这是整个环境能否build的关键:

cd edk2 git checkout UDK2018 make -C BaseTools . ./edksetup.sh

edksetup.sh会把编译UEFI模块需要的环境变量全部导入当前shell,包括工具链路径、Python脚本路径、Build目录路径。很多新手卡在这一步:执行build命令报“找不到工具链”,多半是没source脚本,或者source后在另一个终端窗口里执行build。EDK2的构建系统对当前shell环境有依赖,换一个终端就得重新source一遍,我在终端复用工具里也踩过这个坑。

接下来编辑Conf/target.txt,指定要编译的平台模块和工具链。编译OVMF固件时的典型配置如下:

ACTIVE_PLATFORM = OvmfPkg/OvmfPkgX64.dsc TOOL_CHAIN_TAG = GCC48 TARGET_ARCH = X64

ACTIVE_PLATFORM是平台描述文件(.dsc)的相对路径,OVMF是EDK2自带的开源虚拟机固件,可以当成一个完整的UEFI固件来编译;TOOL_CHAIN_TAG要和本机实际安装的GCC版本对应,CentOS系的默认编译器是GCC 4.8就填GCC48,系统装的是GCC 5以上就改成对应标签;TARGET_ARCH指定目标架构,X64对应64位。编译OVMF前还要先执行git submodule update --init --recursive拉取子模块,否则会缺头文件。产物OVMF.fd在Build/OvmfX64/DEBUG_GCC48/FV路径下,这个文件可以整包交给虚拟机当固件使用。

3.2 编译UEFI Shell并让它从U盘跑起来

UEFI Shell本身是一个UEFI应用程序。固件找不到可引导的操作系统加载器时,经常会自动进入Shell,方便排查故障。EDK2里ShellPkg就是它的模块组,编译方式同上,只是ACTIVE_PLATFORM换成ShellPkg/ShellPkg.dsc。产物Shell.efi在Build/Shell/DEBUG_GCC48/X64下。

要让虚拟机UEFI固件从U盘执行这个.efi,需要先做一个符合规范的引导U盘。步骤是:先用磁盘分区工具把U盘扇区清空,把分区表类型转为GUID(GPT),再建立分区。这里有一个关键点,UEFI引导需要创建ESP分区(EFI System Partition),大小20MB左右即可;另外再建一个10MB左右的普通FAT32分区。创建ESP分区时要勾选“建立ESP分区”选项,分区对齐按4096扇区设置,文件系统格式为FAT32。在Windows下资源管理器默认看不到ESP分区,但在Linux下没有这个限制。

挂载ESP分区后,把编译好的Shell.efi按UEFI规范路径放进去:

mount /dev/sdb1 /mnt mkdir -p /mnt/EFI/BOOT cp Build/Shell/DEBUG_GCC48/X64/Shell.efi /mnt/EFI/BOOT/BOOTx64.EFI sync umount /mnt

UEFI固件扫描启动设备时,会优先查找\EFI\BOOT\BOOTx64.EFI这个固定路径,所以要把Shell.efi改名后再放进BOOT目录。这里的路径分隔符是反斜杠,目录层级一个都不能少。还有一个容易误解的点:UEFI引导不依赖磁盘的第一个扇区,GPT磁盘第一个扇区是保护性MBR,它只是为了阻止老式MBR工具把GPT盘误判为空盘而存在。真正的引导入口是ESP分区里的.efi文件,固件直接通过文件系统路径找到并加载它,不需要写任何引导扇区。虚拟机方面,默认固件是BIOS,要先在虚拟机设置里把固件类型改成UEFI,再添加USB控制器,U盘才能被固件识别。如果觉得启动时网络等待太久,把虚拟网卡摘掉可以快不少。

3.3 Hello World背后的UEFI约定:PE/COFF镜像、系统表与调用规则

环境通了就该写第一个UEFI程序。EDK2里一个UEFI Application最少包含三个文件:C源码、.inf构建描述文件,以及在某个.dsc里注册模块。下面是一个标准写法:

#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiBootServicesTableLib.h> EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { Print (L"Hello, UEFI World!\r\n"); return EFI_SUCCESS; }

每个UEFI程序的入口函数都叫UefiMain,签名固定是(EFI_HANDLE, EFI_SYSTEM_TABLE*)。ImageHandle表示当前加载镜像的句柄,SystemTable是核心数据结构EFI_SYSTEM_TABLE的指针。Print是UefiLib封装的输出宏,内部走到SystemTable->ConOut的输出协议。字符串前的L前缀表示宽字符,因为UEFI内部统一使用UTF-16编码,这个细节容易在写中文字符串时踩坑。

UEFI可执行文件采用PE/COFF格式,和Windows下的PE格式同源,固件里的镜像加载服务按PE格式解析节区、分配内存、跳转入口。调用约定是EFIAPI,在64位环境下对应微软x64调用约定,前四个参数放RCX、RDX、R8、R9,其余参数压栈。写纯C代码时不必关心,但一旦混入汇编就要格外小心参数传递顺序。

.inf文件负责描述模块来源和依赖:

[Defines] INF_VERSION = 0x00010006 BASE_NAME = HelloWorld FILE_GUID = 9E968B6C-9D0F-4F47-A8DB-16B7F4E9F0E1 MODULE_TYPE = UEFI_APPLICATION ENTRY_POINT = UefiMain [Sources] HelloWorld.c [LibraryClasses] UefiLib UefiBootServicesTableLib

FILE_GUID是模块唯一标识,固件用它缓存驱动、组织启动项;ENTRY_POINT必须与C代码里的入口函数名一致;LibraryClasses列出依赖的库。EDK2构建系统靠这些描述自动生成Makefile和AutoGen头文件,漏了库或写错GUID都会在编译阶段报错,这类错误还算好定位。

4. 核心模块实现:从显示模式到内核引导的完整链路

4.1 配置图形设备显示模式

引导程序第一阶段是显示输出。UEFI最标准的输出接口是ConOut,SystemTable->ConOut指向它。通常先遍历查询当前控制台支持的模式,再切到目标分辨率:

UINTN Cols = 0, Rows = 0; UINT32 Mode; for (Mode = 0; ; Mode++) { Status = gST->ConOut->QueryMode (gST->ConOut, Mode, &Cols, &Rows); if (EFI_ERROR (Status)) break; Print (L"mode %d: %d x %d\r\n", Mode, Cols, Rows); } gST->ConOut->SetMode (gST->ConOut, 3); gST->ConOut->ClearScreen (gST->ConOut);

QueryMode返回指定模式下的行列数,SetMode切换文本模式,ClearScreen清屏。Mode编号由固件定义,不同平台不一样,所以先遍历一遍再挑最合适的模式比较稳。这里gST是全局EFI_SYSTEM_TABLE指针,EDK2的UefiBootServicesTableLib在模块入口前会自动初始化好它,不需要自己去找。

要做图形化启动界面,就得操作图形输出协议GOP。GOP通过LocateProtocol定位:

EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop; gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)&Gop); Gop->SetMode (Gop, Gop->Mode->Mode); // 之后用 Gop->Blt 做位块传输,把像素数据搬到帧缓冲

GOP的Mode结构体里有分辨率、像素格式和帧缓冲地址。Blt类似显卡驱动的Block Transfer,可以画点、画块、输出图片。内核接管屏幕之前,引导程序完全可以用GOP画一个简单的LOGO或进度条。不过要注意,一旦调用ExitBootServices之后GOP协议也可能失效,所以如果内核需要接续显示,要在跳转前把帧缓冲地址和分辨率存到引导信息结构体里传给内核。

4.2 获取可用物理内存

加载内核之前必须弄清楚物理内存布局,否则分配内存时可能踩到固件保留区。UEFI的统一入口是gBS->GetMemoryMap:

typedef EFI_STATUS (EFIAPI *EFI_GET_MEMORY_MAP)( IN OUT UINTN *MemoryMapSize, IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, OUT UINTN *MapKey, OUT UINTN *DescriptorSize, OUT UINT32 *DescriptorVersion );

实际调用有个经典技巧:第一次传入空缓冲和很小的Size,系统返回EFI_BUFFER_TOO_SMALL并把所需字节数写回MemoryMapSize;按这个大小分配缓冲后再调用第二次,才能拿到完整映射:

UINTN MapSize = 0; UINTN MapKey = 0; UINTN DescSize = 0; UINT32 DescVersion = 0; gBS->GetMemoryMap (&MapSize, NULL, &MapKey, &DescSize, &DescVersion); // 此时 MapSize 被改写为实际需要的字节数 EFI_MEMORY_DESCRIPTOR *Map = AllocateZeroPool (MapSize); gBS->GetMemoryMap (&MapSize, Map, &MapKey, &DescSize, &DescVersion);

返回的描述符数组里,每个EFI_MEMORY_DESCRIPTOR包含Type字段,标记这块内存是EfiConventionalMemory还是EfiReservedMemoryType等。只有类型为EfiConventionalMemory的区域才能交给内核使用,其他保留类型要原样传给内核或直接丢弃。遍历数组时有个隐藏坑:不能取sizeof(EVF_MEMORY_DESCRIPTOR)当步长,平台为了对齐会补padding,必须用GetMemoryMap返回的DescriptorSize作为指针步进值。

内存分配另一个常用接口是gBS->AllocatePages,支持按页数分配、按固定地址分配等模式。加载内核镜像时用AllocateAddress指定物理地址比较常见,因为内核镜像可能在链接期就定死了加载基址,分配不到指定地址会直接返回错误。

4.3 从存储介质读取内核文件

从磁盘读文件要走“定位协议 → 打开卷 → 打开文件 → 读数据”这条链路:

EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; EFI_FILE_PROTOCOL *Root, *File; gBS->LocateProtocol (&gEfiSimpleFileSystemProtocolGuid, NULL, (VOID **)&Fs); Fs->OpenVolume (Fs, &Root); EFI_STATUS Status = Root->Open (Root, &File, L"\\EFI\\BOOT\\kernel.bin", EFI_FILE_MODE_READ, 0);

LocateProtocol找的是系统里第一个实现指定协议的句柄。OpenVolume拿到卷的根目录,Root->Open打开文件。这里文件路径的写法值得单独提醒:UEFI文件协议路径用反斜杠分隔目录,字符串是宽字符,所以写成L"\EFI\BOOT\kernel.bin",而不是L"/EFI/BOOT/kernel.bin"。路径写错不会立刻报错,Open返回EFI_NOT_FOUND,排查时还要确认是不是路径分隔符的问题。

打开文件之后读大小、读内容:

UINTN FileSize = 0; UINTN InfoSize = 0; EFI_FILE_INFO *FileInfo = NULL; File->GetInfo (File, &gEfiFileInfoGuid, &InfoSize, FileInfo); // 第一次调用 InfoSize 会被改写为 EFI_FILE_INFO 的实际大小 FileInfo = AllocatePool (InfoSize); File->GetInfo (File, &gEfiFileInfoGuid, &InfoSize, FileInfo); UINT8 *Buffer = AllocatePages (EFI_SIZE_TO_PAGES (FileInfo->FileSize)); File->Read (File, &FileSize, Buffer);

GetInfo通过文件信息GUID拿EFI_FILE_INFO结构,里面有文件大小。分配足够内存后Read一次性把文件读进来。到这里内核已经躺在内存里了,问题只剩怎么跳过去。

4.4 64位处理器平台的引导细节

UEFI引导到64位内核时,处理器已经在长模式(IA-32e)下运行,这一点比BIOS时代省力很多。但内核仍然需要自己的页表、GDT和IDT。常见做法是在UEFI阶段用AllocatePages分配页表内存,手动填PML4、PDPT、PD条目,把内核所在物理地址映射到期望的虚拟地址。

页表项里的权限位是我觉得最值得盯紧的:内核代码段要置为可执行,数据段要可写,NX位(不可执行位)必须设对,否则一进内核就触发页错误。很多引导程序明明跑到了跳转那一步,却在进入内核后崩溃,最常见的根因不是跳转指令写错,而是页表权限没配置好。

另外GDT和IDT也要在跳转前重新装载。UEFI的GDT由固件维护,里面的段描述符不一定符合内核预期。标准流程是:新页表就绪 → 加载自己的GDT → 关中断 → 调用ExitBootServices → 跳转内核入口。ExitBootServices之后,固件占用的内存和句柄全部失效,任何UEFI服务都不能再触碰,整个处理器环境由内核接管。

4.5 跳转内核:ExitBootServices与参数交接

ExitBootServices是UEFI引导里最特殊的那个接口,它的作用是和固件“断交”,只接收一个参数MapKey。这个Key来自最近一次成功的GetMemoryMap调用。如果这两次调用之间,固件因为异步事件改动了内存映射,MapKey不匹配,接口就返回EFI_INVALID_PARAMETER。处理方式只能重试:再GetMemoryMap拿新Key,再次调用ExitBootServices。代码上就是一个小循环,限定重试次数防止死转。

gBS->ExitBootServices (ImageHandle, MapKey);

调用成功后系统进入RT阶段,能用的只剩运行时服务。内核入口地址、内存映射指针、显示信息这些参数,各家操作系统自由发挥。我见过比较常见的做法是定义内核引导信息结构体,把结构体地址放在某个寄存器里传给内核,内核解析后再初始化自己的内存管理。

一个从实践里来的提醒:ExitBootServices成功后连Print都不能用了,调试手段只剩串口输出或直接写帧缓冲。所以引导程序里要尽早准备一个串口初始化和简单的putc函数,否则跳转一失败就只剩黑屏,完全不知道卡在哪一步。

5. 避坑记录:UEFI引导开发中最常见的五个问题

5.1 U盘做好了却进不了UEFI Shell

现象:Shell.efi已经改成BOOTx64.EFI放进ESP分区,虚拟机开机还是直接进固件设置或黑屏。

原因:极少数情况是U盘硬件不被固件识别,绝大多数是三个细节没做对——虚拟机固件类型仍然是BIOS、ESP分区的创建方式不对,或者固件要求的\EFI\BOOT\BOOTx64.EFI路径不完整。

解决:先到虚拟机设置里确认固件类型改为UEFI,再加USB控制器;然后回Linux挂载ESP分区,用ls确认路径确实是/EFI/BOOT/BOOTx64.EFI,注意UEFI对路径大小写不敏感但对目录层级敏感。最后用fdisk -l看看分区表类型是否为GPT,如果还是dos类型,说明分区表根本没转成功。

5.2 EDK2编译报错:找不到工具链或交叉编译器

现象:执行build命令立刻报GCC相关错误,或者提示找不到nasm、iasl。

原因:edksetup.sh引入的环境变量没生效,很常见的是换了一个终端窗口;另一种情况是TOOL_CHAIN_TAG填的版本和本机实际GCC版本不一致,GCC48这个标签在GCC 5以上的系统里不匹配。

解决:每次编译前都在同一终端里执行一遍. ./edksetup.sh,再执行build。nasm和iasl用包管理器装好,确认在PATH里。工具链版本问题就把target.txt里的TOOL_CHAIN_TAG改成和本机一致的标签,比如GCC5或GCC8,而不是死守着文档示例。

5.3 ESP分区里的文件在固件里找不到

现象:文件明明拷进去了,固件启动时却提示找不到启动项。

原因:大概率是拷贝动作本身没生效。ESP分区在Windows资源管理器里默认不可见,很多人以为拷进去了,实际写到了其他分区;另一种可能是路径分隔符用错,文件协议只认反斜杠,在代码里写路径时尤其容易混。

解决:在Linux下挂载确认文件真实存在,字节数对不对;代码里统一用L"\EFI\BOOT\kernel.bin"这种写法,字符串里是两个反斜杠转义成一个。想省事的做法是在UEFI Shell里直接用ls命令看文件在不在,Shell本身就能遍历ESP分区,比反复重启虚拟机快得多。

5.4 ExitBootServices返回EFI_INVALID_PARAMETER

现象:调用ExitBootServices时返回EFI_INVALID_PARAMETER,跳转内核失败,看起来最像玄学问题。

原因:MapKey和当前内存映射不一致。固件内部可能有异步事件在两次GetMemoryMap之间改动了内存映射,或者你传的Key来自一个很早期的调用,早已过期。

解决:按规范给出的重试流程处理。先重新GetMemoryMap拿到新MapKey,再调ExitBootServices,循环处理并限定次数。注意第二次调用也可能因为新事件再次失败,所以不能写成“调用一次失败就放弃”,要写成带重试上限的循环。

5.5 进入内核后屏幕花掉或行为异常

现象:中断已关、GDT已换、页表已建,跳转成功但内核一会屏花,一会死机。

原因:两类最可能。一是UEFI控制台协议还占着显示设备,引导程序没有把GOP模式固定下来,内核接管时分辨率突变;二是内存映射处理不干净,把EfiReservedMemoryType这类固件保留区域当作可用内存分配出去了,内核在保留区域上写数据,破坏固件驻留结构。

解决:跳转前把显示模式固定到目标分辨率,并把帧缓冲地址、分辨率、像素格式写进引导信息结构体;内存方面,遍历描述符时只把EfiConventionalMemory类型放进可用内存列表,所有保留类型原样跳过。排查时可以先把内核的物理内存初始化逻辑里加一段打印,把每块区域的Base和Length打出来和引导阶段的Map对照,两边不一致立刻暴露。

6. 二次开发:把固定引导路径改造成可配置的启动加载器

读到这里,引导程序的骨架已经完整。下一步最值得做的改造,是把它从一个“写死路径”的引导程序,升级成能选多系统、能存配置的启动加载器。第一步引入UEFI的NVRAM变量。NVRAM是固件里掉电不丢的一小块存储,UEFI自己就用它记录启动顺序和启动选项。引导程序完全可以把它当配置中心,存一个默认内核路径:

UINTN Size = 128; CHAR16 Path[128]; EFI_STATUS Status = gRT->GetVariable ( L"DefaultBootPath", &gEfiGlobalVariableGuid, NULL, &Size, Path ); if (EFI_ERROR (Status)) { StrCpyS (Path, 128, L"\\EFI\\BOOT\\kernel.bin"); }

GetVariable第一个参数是变量名,第二个是变量所在GUID命名空间。建议自定义一个GUID而不是用全局变量GUID,避免和固件自己的变量冲突。Size是输入输出共用的缓冲大小,变量不存在时返回EFI_NOT_FOUND,这时回退到默认路径。写入端用SetVariable,记得带EFI_VARIABLE_NON_VOLATILE属性,否则重启后配置丢失。

多系统支持也不复杂。UEFI自带的引导管理器可以维护多个启动项,引导程序甚至可以直接用LoadImage和StartImage接口加载另一个UEFI应用程序,比如把UEFI Shell当备用入口,或者做一个数字键选择的读秒菜单:启动时等3秒,超时后走默认启动项。调试方面,我的习惯是给引导程序加一套日志分级输出宏,把普通信息、警告、错误分开。虚拟机上用QEMU加OVMF跑,串口输出能看到完整引导日志,比反复接显示器高效得多。从那以后,我每次改完引导代码,都会强制在虚拟机里把内存映射和显示模式验一遍,确认无误再上真机,省掉了大量反复刷机的血泪时间。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询