彻底搞通UEFI, GPT, ESP 现代固件启动概念(附c代码)
author: hjjdebug
date: 2026年 04月 26日 星期日 12:19:02 CST
descip: 彻底搞通UEFI, GPT, ESP 现代固件启动概念(附c代码)
文章目录
1. 概念
uefi: universal extensible firmware interface. 统一的,可扩展的固件接口.
gpt: gud partition table
esp: efi system partition.
单纯看定义,也没有概念. 我们还是要结合使用场景和历史来说清楚概念.
1.1 uefi 的解释
计算机上电, 要运行主板上的一小段程序, 完成上电自检和系统启动. 旧的计算机主板上把它叫做BIOS 启动程序, 新的主板上叫UEFI 启动程序
好好的为啥要改名字呢? 因为旧版的是针对32位系统的, 后来出现了64位cpu,64位系统,
新的接口方式需要实现, 这就是uefi 出现的时机.
uefi是支持64位系统的上电主板启动程序. 并把它叫做统一可扩展的固件接口.
这种固件接口是计算机厂商的事, 出厂就固定好了,或者是BIOS接口,或者是UEFI接口
如果cpu是64位装bios启动也是可以的,只不过那是兼容模式,不是纯正64位模式
1.2. gpt
1.2.1 主引导扇区(MBR)及其分区表(partion table)
传统的BIOS 搭配的是MBR 分区表, MBR 是main boot record, 主引导扇区的意思.
磁盘的第一个扇区就是主引导扇区,一个扇区的大小是512byte, 在扇区的尾部0x1b4位置处,
存放着最多64byte的分区表,因为一个分区表项占用16bytes, 所以最多可存储4个分区表项
MBR
分区表是针对32位系统的,其逻辑扇区的起始地址占4byte,大小占4byte,这就8个字节了,
再加上用磁道,磁头,扇区表示的开始结束位置(磁盘似乎不使用这类参数只使用逻辑扇区),
以及分区类型, 否启动盘等其它信息,给16个bytes就可以了.
在那寸土寸金的年代,因为总共512bytes,给出64个bytes存4个分区表项, 已经算很大方了.
由于分区大小限制在32位(4bytes), 所以其最大长度4G块,一个磁盘块512bytes, 所以能表达的最大磁盘空间位4G*512=2048G=2T
顺便给一下pt(partion table)的定义
字节0 : 引导标志(0x80=可引导)
字节1~3: 起始 磁头/扇区/柱面(CHS)
字节4 : 分区类型ID
字节5~7: 结束 磁头/扇区/柱面(CHS)
字节8~11: 起始LBA扇区(4字节)
字节12~15: 分区总扇区数(4字节)
1.2.2 gpt 的引入(guid partion table)
系统发展到64位,需要8bytes表示起始地址,8bytes 表示长度,再加上其它信息, 旧的16bytes 已经不够用了,所以引入了gpt,叫guid partion table,
每一项给它分配了128个bytes. 这下空间就宽裕多了. 而且可以分配128项,
每项的结构定义如下:
0x00–0x0F (16B) 类型 GUID //表示该分区属于哪种类型.
0x10–0x1F (16B) 分区 GUID //分区guid,实际是每个分区对应的一个全局唯一号
0x20–0x27 (8B) 起始 LBA (64位) // 最关键部分开始LBA,结束LBA 其实只要16bytes 就够了.
0x28–0x2F (8B) 结束 LBA (64位)
0x30–0x37 (8B) 属性标志 // 属性给了8B, 因为64位系统,基础单位就是8B
0x38–0x7F (72B) 分区名 (UTF-16 LE) //分区名称很大方,分配了72byte. 分区名称是给人看的.
1.2.3 esp
esi的系统分区.esi system partion
对应着一块磁盘分区. 这就是gpt 中的一项.
esi: efi system interface.
efi: extended firmware interface, 就是uefi,统一可扩展接口.
概念有了,我们实际操练一下吧!
只说不练是不行的,最好是不仅要练,而且每步的变化都是清楚的,这需要我们用虚拟盘来操作.
并写代码来验证.
练习操作完了,会解除你很多疑问,心里就踏实了,未被时代所抛弃.而其还懂细节.
2 练习
2.1 创建一个100M 的虚拟盘
$ dd if=/dev/zero of=100M.img bs=1M count=100
用二进制查看工具可以看到100M.img 数据全为0
2.2. 把文件挂载为 虚拟块设备 /dev/loop
$ sudo losetup -f 100M.img
// 查看绑定的 loop 设备
$ losetup
一般会是 /dev/loop0, 后面就以/dev/loop0设备来操作
2.3. 给这个虚拟盘 创建 GPT 分区表
$ sudo parted -s /dev/loop0 mklabel gpt
-s 是–script, 不输出提示信息,创建gpt 分区表
文件头部 = 主 GPT 表
文件尾部 = 备份 GPT 表
用二进制工具来查看内容, 例如010editor,
//头部: 0扇区(到1FF止)为MBR, 属于旧的bios关联部分
01B0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
01C0h: 01 00 EE FE FF FF 01 00 00 00 FF 1F 03 00 00 00 …îþÿÿ…ÿ…
01D0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
01E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
01F0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA …Uª
//1扇区(0x200开始)为gpt表头部
0200h: 45 46 49 20 50 41 52 54 00 00 01 00 5C 00 00 00 EFI PART…
0210h: 93 9C 18 29 00 00 00 00 01 00 00 00 00 00 00 00 “œ.)…
0220h: FF 1F 03 00 00 00 00 00 22 00 00 00 00 00 00 00 ÿ…"…
0230h: DE 1F 03 00 00 00 00 00 72 B1 CC 5B 22 F9 A3 48 Þ…r±Ì["ù£H
0240h: AB 4C F7 C3 64 2A 2C 7C 02 00 00 00 00 00 00 00 «L÷Ãd*,|…
0250h: 80 00 00 00 80 00 00 00 86 D2 54 AB 00 00 00 00 €…€…†ÒT«…
0260h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
尾部: 备份gpt表头部
63F:FE00h: 45 46 49 20 50 41 52 54 00 00 01 00 5C 00 00 00 EFI PART…
63F:FE10h: 35 92 81 B9 00 00 00 00 FF 1F 03 00 00 00 00 00 5’
63F:FE20h: 01 00 00 00 00 00 00 00 22 00 00 00 00 00 00 00 …"…
63F:FE30h: DE 1F 03 00 00 00 00 00 72 B1 CC 5B 22 F9 A3 48 Þ…r±Ì["ù£H
63F:FE40h: AB 4C F7 C3 64 2A 2C 7C DF 1F 03 00 00 00 00 00 «L÷Ãd*,|ß…
63F:FE50h: 80 00 00 00 80 00 00 00 86 D2 54 AB 00 00 00 00 €…€…†ÒT«…
63F:FE60h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
想深刻理解这个头部信息.
这里给一个c读取程序吧.
$ cat gpt_read.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
// 标准 GPT Header 结构,UEFI 规范定义
typedef struct {
uint64_t signature; // GPT 签名 "EFI PART"
uint32_t revision; // 版本号
uint32_t header_size; // GPT 头大小
uint32_t header_crc32; // 头部校验和
uint32_t reserved; // 保留位
uint64_t my_lba; // 当前头部所在LBA
uint64_t alternate_lba; // 备份GPT头LBA
uint64_t first_usable_lba; // 第一个可用扇区
uint64_t last_usable_lba; // 最后一个可用扇区
uint8_t disk_guid[16]; // 磁盘唯一GUID
uint64_t entry_array_lba; // 分区表项起始LBA
uint32_t entry_count; // 分区表项数量
uint32_t entry_size; // 单个分区项大小
uint32_t entry_array_crc32;// 分区表校验和
uint8_t padding[420]; // 填充至512字节
} __attribute__((packed)) GptHeader;
// 打印 GUID
void print_guid(const uint8_t *guid)
{
printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X\n",
guid[0],guid[1],guid[2],guid[3],
guid[4],guid[5],
guid[6],guid[7],
guid[8],guid[9],
guid[10],guid[11],guid[12],guid[13],guid[14],guid[15]);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "用法: %s /dev/sda (Linux)\n", argv[0]);
return 1;
}
// 二进制只读打开磁盘
FILE *fp = fopen(argv[1], "rb");
if (!fp)
{
perror("fopen failed");
return 1;
}
// 跳转到 LBA 1 偏移:1 * 512
long lba1_off = 1 * 512;
if (fseek(fp, lba1_off, SEEK_SET) != 0)
{
perror("fseek failed");
fclose(fp);
return 1;
}
GptHeader hdr;
size_t ret = fread(&hdr, 1, sizeof(hdr), fp);
if (ret != sizeof(hdr))
{
perror("fread failed");
fclose(fp);
return 1;
}
fclose(fp);
// 校验 GPT 签名
if (hdr.signature != 0x5452415020494645ULL)
{
printf("未检测到合法 GPT 磁盘,可能是 MBR 分区表\n");
return 0;
}
// 打印 GPT 头关键信息
printf("===== GPT Header Info =====\n");
printf("签名 : %.8s\n", (char*)&hdr.signature);
printf("版本 : 0x%08X\n", hdr.revision);
printf("头部大小 : %u 字节\n", hdr.header_size);
printf("头部CRC32 : 0x%08X\n", hdr.header_crc32);
printf("当前LBA : %llu\n", (unsigned long long)hdr.my_lba);
printf("备份GPT LBA : %llu\n", (unsigned long long)hdr.alternate_lba);
printf("起始可用LBA : %llu\n", (unsigned long long)hdr.first_usable_lba);
printf("末尾可用LBA : %llu\n", (unsigned long long)hdr.last_usable_lba);
printf("磁盘GUID : ");
print_guid(hdr.disk_guid);
printf("分区表项LBA : %llu\n", (unsigned long long)hdr.entry_array_lba);
printf("分区项数量 : %u\n", hdr.entry_count);
printf("单项大小 : %u 字节\n", hdr.entry_size);
return 0;
}
针对上面测试文件的输出:
$ ./gpt_read /home/hjj/learn/100M.img
===== GPT Header Info =====
签名 : EFI PART
版本 : 0x00010000
头部大小 : 92 字节
头部CRC32 : 0x29189C93
当前LBA : 1
备份GPT LBA : 204799
起始可用LBA : 34
末尾可用LBA : 204766
磁盘GUID : 72B1CC5B-22F9-A348-AB4C-F7C3642A2C7C
分区表项LBA : 2
分区项数量 : 128
单项大小 : 128 字节
传统的BIOS 是没有分区头部的, 只有约定俗成的规矩. 即在0x1b4位置是MBR 分区表位置,4个表项
GPT首先需要用第1分区定义一个 GPT 头部,它定义GPT分区表在什么位置,
分区表项有几个, 每项大小是多少8字节.虽然是固定的128字节,它还是写成可定义形式.
此外,它还说明了本头部位于第1分区,备份头部位于204799分区,并定义了签名,版本,头部大小,crc32及磁盘GUID,
上面输出很好的说明了这一点.
2.4 给这个虚拟盘创建一个esp 分区.
//建立 ESP 分区:0% ~ 100MB
$ sudo parted -s /dev/loop0 mkpart “EFI” fat32 0% 100M
$ sudo parted -s /dev/loop0 set 1 esp on //让它可启动
mkpart, 子命令
EFI, 分区类型
fat32, 文件类型, efi对应的固定文件类型
0% 100M, 开始点,终止点
我们看看在第2分区它创建的分区表是什么样子的. 目前只含一个分区表
每一项占128字节,头部已经说好了.
0400h: A2 A0 D0 EB E5 B9 33 44 87 C0 68 B6 B7 26 99 C7 ¢ Ðëå¹3D‡Àh¶·&™Ç
0410h: 4E 9A 17 36 FE 51 6C 48 B9 9E 38 89 0A 9F F0 5F Nš.6þQlH¹ž8‰.Ÿð_
0420h: 22 00 00 00 00 00 00 00 F0 FA 02 00 00 00 00 00 "…ðú…
0430h: 00 00 00 00 00 00 00 00 45 00 46 00 49 00 00 00 …E.F.I…
0440h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
0450h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …
完整c代码验证
$ cat gpt_read.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#define SECTOR_SIZE 512U
#define GPT_SIGNATURE 0x5452415020494645ULL
/* GPT 头部 —— LBA 1 */
typedef struct {
uint64_t signature;
uint32_t revision;
uint32_t header_size;
uint32_t header_crc32;
uint32_t reserved;
uint64_t my_lba;
uint64_t alternate_lba;
uint64_t first_usable_lba;
uint64_t last_usable_lba;
uint8_t disk_guid[16];
uint64_t entry_array_lba;
uint32_t entry_count;
uint32_t entry_size;
uint32_t entry_array_crc32;
uint8_t pad[420];
} __attribute__((packed)) GptHeader;
/* GPT 分区表项 —— 标准 128 字节/项 */
typedef struct {
uint8_t type_guid[16]; // 分区类型 GUID
uint8_t unique_guid[16]; // 分区唯一 GUID
uint64_t start_lba;
uint64_t end_lba;
uint64_t attributes;
uint16_t name[36]; // UTF-16LE 分区名
} __attribute__((packed)) GptEntry;
static void print_guid(const uint8_t *g)
{
printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
g[0],g[1],g[2],g[3],
g[4],g[5],
g[6],g[7],
g[8],g[9],
g[10],g[11],g[12],g[13],g[14],g[15]);
}
static void print_utf16le(const uint16_t *str, size_t len)
{
for (size_t i = 0; i < len; i++) {
if (str[i] == 0) break;
putchar((char)str[i]);
}
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: %s /dev/sda\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
perror("fopen");
return 1;
}
/* 1. 读取 LBA1 GPT 头部 */
if (fseek(fp, 1ULL * SECTOR_SIZE, SEEK_SET) < 0) {
perror("fseek lba1");
fclose(fp);
return 1;
}
GptHeader hdr;
if (fread(&hdr, 1, sizeof(hdr), fp) != sizeof(hdr)) {
perror("read gpt header");
fclose(fp);
return 1;
}
if (hdr.signature != GPT_SIGNATURE) {
fprintf(stderr, "Not a valid GPT disk.\n");
fclose(fp);
return 1;
}
/* 打印 GPT 头部关键信息 */
puts("===== GPT Header =====");
printf("Signature : %s\n", (char *)&hdr.signature);
printf("Header Size : %u bytes\n", hdr.header_size);
printf("Entry Count : %u\n", hdr.entry_count);
printf("Entry Size : %u bytes\n", hdr.entry_size);
printf("Entry Array : LBA %llu\n", (unsigned long long)hdr.entry_array_lba);
printf("Disk GUID : ");
print_guid(hdr.disk_guid);
putchar('\n');
/* 2. 跳转到分区表项数组起始LBA */
off_t entry_off = (off_t)hdr.entry_array_lba * SECTOR_SIZE;
if (fseek(fp, entry_off, SEEK_SET) < 0) {
perror("fseek entry array");
fclose(fp);
return 1;
}
/* 3. 循环读取所有分区项 */
puts("\n===== GPT Partition Entries =====");
for (uint32_t i = 0; i < hdr.entry_count; i++) {
GptEntry ent;
if (fread(&ent, 1, sizeof(ent), fp) != sizeof(ent)) {
perror("read entry");
break;
}
/* 全0项 = 空分区,跳过 */
uint8_t zero[16] = {0};
if (memcmp(ent.type_guid, zero, 16) == 0)
continue;
printf("\nPartition #%u\n", i + 1);
printf(" Type GUID : "); print_guid(ent.type_guid); putchar('\n');
printf(" Unique GUID : "); print_guid(ent.unique_guid); putchar('\n');
printf(" Start LBA : %llu\n", (unsigned long long)ent.start_lba);
printf(" End LBA : %llu\n", (unsigned long long)ent.end_lba);
printf(" Attributes : 0x%016llX\n", (unsigned long long)ent.attributes);
printf(" Name : ");
print_utf16le(ent.name, 36);
putchar('\n');
}
fclose(fp);
return 0;
}
输出结果
$ ./gpt_read ~/learn/100M.img
===== GPT Header =====
Signature : EFI PART
Header Size : 92 bytes
Entry Count : 128
Entry Size : 128 bytes
Entry Array : LBA 2
Disk GUID : 72B1CC5B-22F9-A348-AB4C-F7C3642A2C7C
===== GPT Partition Entries =====
Partition #1
Type GUID : 28732AC1-1FF8-D211-BA4B-00A0C93EC93B
Unique GUID : 4E9A1736-FE51-6C48-B99E-38890A9FF05F
Start LBA : 34
End LBA : 195312
Attributes : 0x0000000000000000
Name : EFI
2.5. 刷新分区表 + 格式化分区为 FAT32(ESP 强制)
sudo partprobe /dev/loop0
sudo mkfs.fat -F32 /dev/loop0p1
这两个命令就不详细解释了,简单解释是更新esp分区,出现loop0p1
并把esp分区1(loop0p1)格式化为fat32文件系统,属于创建文件系统的范畴了.
更多推荐


所有评论(0)