分区表基础:MBR与GPT分区表结构、分区类型与属性、分区对齐与大小计算
分区表,说白了就是一张「地图」。Bootloader 启动时,得靠这张地图找到内核、设备树、文件系统镜像分别藏在存储设备的哪个位置。我早年调试一块 ARM 板子时,就因为分区表偏移算错了一个扇区,结果内核死活加载不起来——折腾了两天才发现是分区起始地址写错了。嗯,从那以后我对分区表的每一个字节都不敢马虎。
这一节我们重点聊三件事:MBR 和 GPT 这两种主流分区表的底层结构、分区类型与属性怎么影响 Bootloader 的行为、以及分区对齐和大小计算这些实操中容易踩坑的地方。
MBR 分区表:经典但有限
MBR(Master Boot Record)诞生于 DOS 时代,至今仍被广泛使用。它位于存储设备的第一个扇区(LBA 0),总共 512 字节。结构大致如下:
| 偏移(字节) | 长度 | 内容 |
|---|---|---|
| 0x000 | 446 | 引导代码(Bootloader 第一阶段代码) |
| 0x1BE | 16 | 第 1 个分区表项 |
| 0x1CE | 16 | 第 2 个分区表项 |
| 0x1DE | 16 | 第 3 个分区表项 |
| 0x1EE | 16 | 第 4 个分区表项 |
| 0x1FE | 2 | 魔数 0x55AA |
每个分区表项 16 字节,结构如下:
| 偏移 | 长度 | 含义 |
|---|---|---|
| 0 | 1 | 引导标志(0x80 = 可引导,0x00 = 不可引导) |
| 1 | 3 | 起始 CHS 地址 |
| 4 | 1 | 分区类型 ID |
| 5 | 3 | 结束 CHS 地址 |
| 8 | 4 | 起始 LBA 地址(小端序) |
| 12 | 4 | 分区总扇区数(小端序) |
分区类型 ID 是一个单字节值,常见的有:
- 0x83:Linux 原生分区(ext4、btrfs 等)
- 0x0C:FAT32 分区(LBA 模式)
- 0x82:Linux Swap 分区
- 0xEF:EFI 系统分区(ESP)
- 0xEE:GPT 保护分区(用于兼容性)
MBR 还有一个硬伤:最多只能有 4 个主分区。想分更多区?得用扩展分区和逻辑分区的嵌套方案。这在嵌入式场景下其实挺烦人的——你想想看,一个 Bootloader 要管 boot、kernel、dtb、rootfs、recovery、data 这么多分区,4 个主分区哪够用?
GPT 分区表:现代嵌入式系统的首选
GPT(GUID Partition Table)是 UEFI 时代的产物,解决了 MBR 的几乎所有痛点。它使用 64 位 LBA 地址,最大支持 8 ZiB 的磁盘空间——对嵌入式系统来说,基本不用担心容量上限了。
GPT 的结构比 MBR 复杂一些,但逻辑更清晰:
- LBA 0:保护 MBR(包含一个类型为 0xEE 的分区表项,防止老式工具误认磁盘为「未分区」)
- LBA 1:GPT 头部(主表头)
- LBA 2 ~ 33:分区表项数组(默认 128 个表项,每个 128 字节)
- LBA 34 ~ 磁盘末尾 - 33:用户数据区域
- 磁盘末尾 - 32 ~ 磁盘末尾 - 1:备份分区表项
- 磁盘末尾:备份 GPT 头部
GPT 头部结构(LBA 1)的关键字段:
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0x00 | 8 | 签名 "EFI PART" |
| 0x08 | 4 | 版本号(当前为 1.0) |
| 0x0C | 4 | 头部大小(通常为 92 字节) |
| 0x10 | 4 | 头部 CRC32 校验 |
| 0x18 | 8 | 当前 LBA(通常为 1) |
| 0x20 | 8 | 备份 LBA(磁盘末尾) |
| 0x28 | 8 | 第一个可用 LBA(通常为 34) |
| 0x30 | 8 | 最后一个可用 LBA |
| 0x38 | 16 | 磁盘 GUID |
| 0x48 | 8 | 分区表项起始 LBA(通常为 2) |
| 0x50 | 4 | 分区表项数量(通常为 128) |
| 0x54 | 4 | 每个分区表项大小(通常为 128 字节) |
| 0x58 | 4 | 分区表项 CRC32 校验 |
每个 GPT 分区表项 128 字节,结构如下:
| 偏移 | 长度 | 含义 |
|---|---|---|
| 0x00 | 16 | 分区类型 GUID |
| 0x10 | 16 | 分区唯一 GUID |
| 0x20 | 8 | 起始 LBA |
| 0x28 | 8 | 结束 LBA(包含) |
| 0x30 | 8 | 属性标志位 |
| 0x38 | 72 | 分区名称(36 个 UTF-16LE 字符) |
C12A7328-F81F-11D2-BA4B-00A0C93EC93B— EFI 系统分区EBD0A0A2-B9E5-4433-87C0-68B6B72699C7— Microsoft 基本数据分区0FC63DAF-8483-4772-8E79-3D69D8477DE4— Linux 文件系统分区A19D880F-05FC-4D3B-A006-743F0F84911E— Linux 根分区(/)
我个人习惯在嵌入式项目里用 GPT。原因很简单:分区数量不受限、有备份表头不怕损坏、而且 64 位 LBA 让分区大小计算变得非常直接。你想想看,一个 32 GB 的 eMMC,用 MBR 算分区偏移时还得小心别超过 2 TiB 限制——虽然实际用不到,但心里总不踏实。GPT 就没这顾虑。
分区类型与属性:Bootloader 怎么认分区?
Bootloader 加载镜像时,不能靠分区名去猜——它得根据分区类型和属性来做决策。我见过一个坑:有人把内核分区类型设成了 0x83(Linux 文件系统),结果 Bootloader 死活不认,因为它的代码里只认 0xA2(自定义类型)。
在 GPT 中,分区属性标志位(64 位)包含了一些对 Bootloader 特别重要的位:
- Bit 0:系统分区(System Partition)——平台固件必须识别
- Bit 1:EFI 隐藏分区——EFI 固件不应暴露给操作系统
- Bit 2:传统 BIOS 可引导——用于传统 BIOS 启动
- Bit 60:只读——分区内容不可修改
- Bit 61:隐藏——分区在操作系统中不可见
- Bit 62:不自动挂载——操作系统不应自动挂载此分区
- Bit 63:非卷——分区不是卷的一部分
分区对齐:性能与兼容性的关键
分区对齐,说白了就是让分区的起始地址落在某个「自然边界」上。为什么需要对齐?因为存储设备的物理读写单元(比如 NAND Flash 的页大小、eMMC 的擦除块大小)通常不是 512 字节。如果分区起始地址不对齐,每次读写都可能跨物理块,性能会大幅下降。
常见的对齐单位:
| 存储介质 | 推荐对齐单位 | 原因 |
|---|---|---|
| 传统 HDD | 1 MiB(2048 扇区) | 匹配 4K 高级格式化扇区 |
| eMMC | 4 MiB(8192 扇区) | 匹配擦除块大小 |
| NAND Flash | 页大小 × 块大小 | 匹配物理擦除单元 |
| NVMe SSD | 1 MiB 或 4 MiB | 匹配命名空间管理单元 |
我曾在项目里遇到过一个问题:eMMC 的擦除块是 4 MiB,但分区起始地址只对齐到 1 MiB。结果每次擦除操作都要多擦 3 MiB 的无关数据,写入速度直接腰斩。后来我把所有分区起始地址都改成 4 MiB 对齐,问题就解决了。
分区大小计算:别让 Bootloader 找不到镜像
分区大小计算看起来简单,但实际项目中经常出错。核心公式就一个:
分区大小(字节) = 分区大小(扇区) × 扇区大小(字节)
分区起始地址(字节) = 分区起始 LBA × 扇区大小(字节)
但要注意几个细节:
- 扇区大小:大多数设备是 512 字节,但有些新设备(如 4K 高级格式化硬盘)是 4096 字节。Bootloader 必须正确读取设备报告的扇区大小。
- 分区结束地址:GPT 中结束 LBA 是包含的,所以分区大小 = (结束 LBA - 起始 LBA + 1) × 扇区大小。
- 保留空间:GPT 头部和分区表项占用了 LBA 0~33,所以第一个可用分区从 LBA 34 开始。别忘了留够空间。
举个例子,一个典型的嵌入式分区布局:
LBA 0: 保护 MBR
LBA 1: GPT 头部
LBA 2~33: 分区表项(128 个)
LBA 34~: 用户数据区域
分区布局(4 MiB 对齐):
- 分区 1: Bootloader (起始 LBA 34, 大小 4 MiB)
- 分区 2: 内核 (起始 LBA 8226, 大小 16 MiB)
- 分区 3: DTB (起始 LBA 40994, 大小 2 MiB)
- 分区 4: 根文件系统 (起始 LBA 45090, 大小 256 MiB)
- 分区 5: 数据分区 (起始 LBA 569378, 剩余空间)
知识体系总览
下面这张图把 MBR 和 GPT 的核心差异、分区对齐、以及 Bootloader 加载流程串在了一起:
这张图把 MBR 和 GPT 的核心差异、分区对齐要求、以及 Bootloader 的加载流程串在了一起。你可以看到,分区表解析是 Bootloader 启动流程的第一步——这一步错了,后面全白搭。
sgdisk 或 parted 工具先验证分区布局,确认对齐和大小都正确后,再把分区表固化到 Bootloader 的配置中。这样能避免后期反复修改。