简介:
对于戴正华《UEFI原理与编程》书籍的学习笔记
本文旨在以用什么学什么的原则,使用EDK2构建一个运行在UEFI机器上的boot loader,使其可以将内核载入内存并移交控制权。
最终运行情况
需要的知识
UEFI启动/运行过程
UEFI启动/运行过程分为以下部分
- SEC:安全验证
- PEI:EFI前期初始化
- DXE:驱动执行环境
- BDS:启动设备选择
- TSL:操作系统加载前期
- RT:运行时
- AL:系统灾难恢复期
编写一个简单的Boot Loader只需要了解其中两个部分,即TSL和RT。
TSL
TSL(Transient System Load)是操作系统加载器(OS Loader)执行的第一阶段,在这一阶段OS Loader作为一个UEFI应用程序运行,系统资源仍然由UEFI内核控制。当启动服务的ExitBootServices()服务被调用后,系统进入Run Time阶段。
TSL阶段之所以称为临时系统,在于它存在的目的就是为操作系统加载器准备执行环境。虽然是临时系统,但其功能已经很强大,已经具备了操作系统的雏形,UEFI Shell是这个临时系统的人机交互界面。正常情况下,系统不会进入UEFI Shell,而是直接执行操作系统加载器,只有在用户干预下或操作系统加载器遇到严重错误时才会进入UEFI Shell。
RT
系统进入RT(Run Time)阶段后,系统的控制权从UEFI内核转交到OS Loader手中,UEFI占用的各种资源被回收到OS Loader,仅有UEFI运行时服务保留给OS Loader和OS使用。随着OS Loader的执行,OS最终取得对系统的控制权。
EDK2
- 构建开发环境的过程不在本文章的范围内
源码: https://github.com/tianocore/edk2
EDK2简介
EDK2时遵循UEFI标准和PI标准的跨平台固件开发环境。我们可以通过EDK2编写UEFI Application,即本文所描述的boot loader。
一个最简单的boot loader所需的文件
.
|-- MyBootLoader.inf
|-- boot.c
其中boot.c为C语言代码,MyBootLoader.inf为EDK2所需的描述文件,其中包括
- [Defines] 模块属性
- [Sources] 本模块源文件,资源文件
- [Packages] 本模块引用到的所有包的包声明文件
- [LibraryClasses] 本模块要链接的库模块
- [Protocols] 本模块用到的Protocol
- [Guids] 本模块用到的GUID
- [BuildOptions] 编译和链接选项
- [Pcd] 本模块用到的Pcd变量,可被整个UEFI访问(Pcd: Platform Configuration Database全平台配置数据库)
- [PcdEx] 本模块用到的Pcd变量,可被整个UEFI访问
- [FixedPcd] 本模块用到的Pcd编译期常量
- [FeaturePcd] 本模块用到的Pcd常量
- [PatchPcd] 本模块用到的Pcd变量,仅本模块可用
一个简单的boot loader的UefiMain如下,LoadKernel函数为使用EDK2所提供的功能所实现的载入内核功能代码。
EFI_STATUS EFIAPI UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
){
EFI_STATUS status;
void (*KernelEntry)();
// load kernel
status = LoadKernel(
L"kernel.elf",
&KernelEntry
);
if(EFI_ERROR(status)){
Log(L"Cannot load kernel...\n");
return EFI_LOAD_ERROR;
}
Log(L"Load kernel success...\n");
DebugLog(L"kernel entry is %x\n", (UINTN)KernelEntry);
// exit BS
UINTN mem_map_size = 0;
EFI_MEMORY_DESCRIPTOR* mem_map = 0;;
UINTN map_key = 0;
UINTN des_size = 0;
UINT32 des_version = 0;
gBS->GetMemoryMap(&mem_map_size, mem_map, &map_key, &des_size, &des_version);
gBS->ExitBootServices(ImageHandle, map_key);
KernelEntry();
// kernel will never return
while(1){}
return EFI_SUCCESS;
}
UefiMain的参数:
- ImageHandle:Image句柄
- SystemTable:系统表,其中包含一些信息和最重要的两个服务BootServices与RuntimeServices的指针。这两个服务也会以gBS与gRT全局变量的形式给出,与系统表中成员的指向一样。
BootServices
todo
RuntimeServices
todo
设计
通常,我们编写一个efi application并将其置于硬盘引导分区的/efi/boot/BOOTxx.efi位置,在UEFI启动时会自动将这个efi application启动。
在我们编写的UEFI Application的UEFI Main执行的时候,系统已经位于TSL阶段。
以最简单的设计,将内存映像与boot loader都放入引导分区,即该分区的fat32文件系统中,使用UEFI原生支持的文件系统读取内核即可
这样,一个最简单的boot loader只需要:
- 轮询所有支持fat32文件系统的设备(分区)
- 找到内核映像,将其载入内存
- 做一些准备工作,如记录当前内存映射状况等
- 调用UEFI提供的接口让UEFI系统进入RT阶段
- 跳转至载入内存的内核,转移控制权
实现
todo