UEFI-编程开发C语言1-简介与预览
-
主要实现:开发一个
64位,面向x86架构的长模式引导程序,为较新的机器编写EFI程序 -
了解更多关于
UEFI编程的内容可以参考这个网站:Home | Unified Extensible Firmware Interface Forum
UEFI介绍
初步介绍
UEFI的全称是Unified Extensible Firmware Interface即统一可扩展固件接口。它是用来定义操作系统与系统固件之间的软件界面,是作为BIOS的替代方案,可扩展固件接口负责加电自检、联系操作系统以及提供连接操作系统与硬件的接口。UEFI最初被称为EFI,最初是Intel公司开发,英特尔已于2005年将此规范格式交由UEFI论坛来推广与发展,这样对于EFI的标准就有了一个组织进行统一,所以EFI后来被更名为UEFI。- 固件是机器中软件与硬件的中间层面,固件其实是一个软件,这个软件一般是存储在非易失性的
ROM或者Flash上。

-
UEFI其实就相当于一个管理固件的小型操作系统,这个操作系统向上为真正的操作系统,比如Windows、Linux,这些提供服务接口,向下又与真正的系统硬件、固件等进行交互。UEFI是用来取代BIOS的,所以UEFI会具有如下与BIOS相同的功能:- 硬件初始化:在计算机启动时,UEFI 负责对 CPU、内存、显卡、存储设备、网络等硬件进行初始化和配置。
- 执行电源自检(Power-On Self Test,POST)
- 初始化 CPU 和内存控制器
- 检测并配置 PCIe 设备、USB 控制器等
- 直接引导操作系统,无需引导扇区。
-
除此之外
UEFI还有其他BIOS所没有的功能:UEFI Shell:UEFI还提供了shell环境,我们可以通过shell命令来查看已识别的磁盘和设备- 提供驱动程序接口:这些驱动以
.efi文件形式存在,支持热拔插、动态加载等特性 - 具有用户界面(GUI)
- 能更安全的进行启动
-
接下来我们再进一步理解
UEFI这个名称的具体由来,以及它取代BIOS的原因:- 在早期,各个硬件的厂商都有自己的
BIOS来管理或者自检自己生产的主板等硬件,这就使得BIOS的类型太多,并且各个BIOS也不兼容。 - 这时
UEFI就对这些厂商的设计进行了一定的规范,各个厂商在设计主板的时候都遵循这个规范,并且让UEFI取代BIOS,作为自检的固件。并且UEFI还兼容不同的架构,即x86、x86_64、ARM,这就使得底层的设计变得稍微轻松一点。这就是UEFI中U(统一)的由来。 - 早期使用
BIOS的时候,固件的功能已经固定了,如果我们要添加新的固件,比如新添加一个网卡,这时我们就需要更新整个BIOS,使得BIOS增加新网卡的这一固件,这样上层操作系统才能够与新的固件网卡交互。 - 而
UEFI的出现就使得我们添加新固件后并不需要再更新UEFI,这时我们如果新添加一个SSD,就无需更新UEFI,它可以将SSD中的.efi程序加载进来,实现灵活扩展的功能。这就是E(可扩展的由来) - 而
F也就是Firmware(固件)的由来是UEFI本身就存储在ROM、Flash中,并且是一个软件,所以就被称为固件。 I就是Interface(接口):也就是UEFI为操作系统提供了统一的可以访问底层硬件的方式。
- 在早期,各个硬件的厂商都有自己的
-
接下来我们再具体介绍一下
UEFI,当我们计算机还没有开机的时候,架构是这样的,此时我们主板的FIRMWARE其实也就是UEFI,然后Loader和kernel被放入到磁盘格式为FAT32的文件中 -
但是主板的
UEFI 固件只认识efi格式的文件,加载不了exe或者elf文件(下文都使用elf文件),即我们的内核kernel就没办法加载到内存中。

- 这时我们就需要使用
Loader,Loader这个程序起到承上启下的作用,它会解析kernel这个elf文件格式,并对其进行加载。而Loader则是一个efi格式并且会保存在特定位置,这时计算机启动的时候主板的UEFI 固件就能识别并运行Loader,然后Loader又可以将Kernal加载进内存当中。

- 而
Loader所处的特定位置,要求位于启动设备的FAT32格式分区,一个U盘或者其他东西,只要是FAT32的格式,在启动的时候就会被计算机识别,并标明UEFI的字样,说明固件发现了这个磁盘格式。

- 当我们启动时,选择了该位置则计算机就会在这里去寻找
Loader,计算机会按照/EFI/Boot/BootX64.efi这个路径去寻找Loader - 一般这个
FAT32分区,都位于磁盘最开始,大小在100M左右

-
总结:
UEFI固件会将是FAT32格式分区的磁盘(一个磁盘可能有多个格式分区),都当做启动磁盘,并将这些发现的磁盘都添加到开机菜单中- 接下去我们就可以选择一个启动磁盘,如果是带
UEFI前缀,就去搜索固定路径。
-
而在加载
kernal中又会出现如下问题:kernal在FAT32分区应该如何读取?kernal以何种方式载入内存,应该放入内存的什么位置?kernal放入的内存是空闲的吗?kernal应该采取段式内存载入还是进行内存分页呢?- 这些问题如果没有统一的
API,就会使得Loader的开发相当复杂,所以UEFI存在的目的不仅仅是加载Loader,还为开发者创造一个统一、便捷的启动环境 - 并且对于
UEFI其提供的API,不仅仅是提供给Loader,还提供给了运行时的操作系统,例如操作系统是通过UEFI提供的API来获取物理内存的大小。

关于开发文档
-
我们可以去这个网站下载关于
UEFI的一些开发规范:Specifications | Unified Extensible Firmware Interface Forum -
在访问这个网站的时候我们还会看到一个固件规范:
ACPI,这个固件和UEFI一起密切协作,共同负责系统启动、硬件管理和电源控制。不过这里主要是对UEFI的开发。特别地:休眠状态与ACPI这个固件关系比较大 -
在这个网站中有
UEFI规范、UEFI Shell规范、UEFI平台初始化规范,但是我们只使用UEFI规范,其他两个在这边并不需要使用。这边视频教程中使用的是2.10版本的规范,这边我就也使用2.10版本的规范(还比较新,没旧到哪里去)。网址在这里:UEFI_Spec_2_10_A_Aug8.pdf (SECURED)



环境与工具
- 首先要明确一点:
UEFI的制定很大程度上借鉴了Microsoft,它的编写规范似乎遵循了Microsoft的编程习惯,并且UEFI规定需要使用PE可执行文件。所以我们一般是在windows上对UEFI进行开发,或者也可以在Linux下,使用交叉编译工具对UEFI进行编译。 - 这个内容将会使用
gcc交叉编译,并且使用make来构建编译脚本,然后使用qemu模拟运行它,然后使用OVMF充当固件,之后会使用工具创建一个GPT磁盘镜像(EFI规范的镜像格式),用于存放EFI应用。之后可以使用模拟器运行EFI也可以将镜像装入U盘在真实机中运行UEFI(这一步就不做了)。
编译器安装
gcc或者mingw或者clang,这里gcc需要的是交叉编译的gcc即能编译出PE文件的gcc。
1 | sudo apt update |
- 安装好后查看是否安装成功
1 | x86_64-w64-mingw32-gcc --version |
自动化构建工具
- 使用
make这一自动化构建工具,通过制作make文件,使得make调用编译器去编译。安装如下:
1 | sudo apt update |
- 安装好后检查是否安装成功
1 | make --version |

虚拟化运行工具
- 安装
qemu,使用qemu运行我们所编译好的UEFI,这样我们就可以查看我们所编写的UEFI的具体功能和效果。
1 | sudo apt install qemu-system-x86_64 |
- 然后使用命令,查看
qemu是否安装成功,可以使用Ctrl+Alt+加号放大终端,也可以使用Ctrl+ALT+减号缩小终端。
1 | qemu-system-x86_64 |

UEFI模拟文件
- 安装
ovmf,UEFI模拟文件,在虚拟机中ovmf充当UEFI/BIOS的功能,在虚拟机启动的时候完成对应的功能。这个文件存储在/usr/share/ovmf这个目录下。

- 我们需要额外下载一个
OVMF-pure-efi.fd这个文件,从这里下载:Index of /repos/jenkins/edk2

- 下载后解压文件夹就会看到这个
.fd文件,然后将这个文件复制到/usr/share/ovmf这个文件夹中。

- 我们使用
qemu先运行一下这个fd文件看看真实效果,这时我们会发现我们进入了UEFI Shell中
1 | qemu-system-x86_64 -bios /usr/share/ovmf/OVMF-pure-efi.fd -net none |

- 之后我们要将这个
OVMF-pure-efi.fd移动到这个目录下,并重新命名为bios64.bin:
1 | /home/myheart/program/my_uefi |
- 之后我们制作好了
BOOTX64.EFI,使用qemu启动时,bios64.bin使用的就是OVMF-pure-efi.fd
1 |
|
UEFI_GPT镜像生成器
GPT disk image,磁盘分区格式,通常用于存储UEFI文件。在github上使用这个项目的软件:queso-fuego/UEFI-GPT-image-creator: GPT Disk Image Creator for UEFI Development, with EFI System Partition and FAT32 Filesystem
1 | git clone https://github.com/queso-fuego/UEFI-GPT-image-creator.git |
- 拉取到本地文件夹后,就可以使用
cd命令进入该文件夹,在该文件夹中会看到.sh文件,这是自动编译该镜像生成器的命令,在windos上可以使用.bat文件实现自动编译。编译后就会出现write_gpt这个文件

U盘(不详细说明)
U盘(可选):用于测试真实硬件环境是否可以启动UEFI
Hello_world
- 这里简单介绍一下
EFI应用的一个例子,初步编写一个在终端上显示Hello_world的UEFI固件。我们只需要两个文件分别为efi.c和efi.h - 然后我们要使用
make对这两个C语言文件进行交叉编译。 - 可以将这些代码敲一遍,也可以直接复制,建议还是敲一遍。
efi.h编写
- 我们先来看
efi.h的代码
1 |
|
efi.c编写
- 然后再查看
efi.c这个代码
1 |
|
makefile编写
- 编写好着两个代码后,我们就可以创建一个
makefile,向该文件写入如下内容,其中--subsystem,10这个参数需要在MinGW 链接器版本在v2.36版本及以上才能使用:
1 | gcc: |
其他步骤
- 编译好后我们就会生成一个
BOOTX64.EFI这个PE文件

- 然后我们将使用
cp命令,将BOOTX64.EFI这个文件复制一份到UEFI-GPT-image-creator这个目录下
1 | cp ./BOOTX64.EFI ./UEFI-GPT-image-creator/ |
- 然后我们进入到
UEFI-GPT-image-creator文件下,使用./write_gpt,就可以将BOOTX64.EFI写入到GPT格式的磁盘中。

- 之后我们就使用
sh脚本,运行qemu来模拟UEFI的运行
1 |
|
- 运行后具体效果如下:

efi.h和efi.c详解
- 按顺序我们应该先搞清楚
efi.h这个文件中的内容,然后再搞清楚efi.c中的内容。

