分区表基础: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 分区总扇区数(小端序)
注意:MBR 使用 32 位 LBA 地址,最大只能寻址 2 TiB 的磁盘空间。如果你的存储设备超过 2 TiB,MBR 根本管不过来。我见过有人把 4 TB 的 SSD 用 MBR 分区,结果系统只认了 2 TB——剩下的空间直接「消失」了。

分区类型 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 复杂一些,但逻辑更清晰:

  1. LBA 0:保护 MBR(包含一个类型为 0xEE 的分区表项,防止老式工具误认磁盘为「未分区」)
  2. LBA 1:GPT 头部(主表头)
  3. LBA 2 ~ 33:分区表项数组(默认 128 个表项,每个 128 字节)
  4. LBA 34 ~ 磁盘末尾 - 33:用户数据区域
  5. 磁盘末尾 - 32 ~ 磁盘末尾 - 1:备份分区表项
  6. 磁盘末尾:备份 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 字符)
关键点:GPT 使用 GUID 来标识分区类型,而不是 MBR 那种单字节 ID。常见的分区类型 GUID 有:
  • 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:非卷——分区不是卷的一部分
实战建议:在嵌入式 Bootloader 中,我通常把内核分区和 DTB 分区设为「只读」(Bit 60),防止系统运行时意外覆盖。恢复分区则设为「隐藏」(Bit 61),避免用户误操作删除。

分区对齐:性能与兼容性的关键

分区对齐,说白了就是让分区的起始地址落在某个「自然边界」上。为什么需要对齐?因为存储设备的物理读写单元(比如 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, 剩余空间)
注意:分区大小必须考虑镜像的实际大小。我见过有人给内核分区只留了 8 MiB,结果内核编译出来 9.5 MiB——Bootloader 加载时直接越界,把后面的 DTB 分区覆盖了。嗯,这种问题排查起来特别痛苦。

知识体系总览

下面这张图把 MBR 和 GPT 的核心差异、分区对齐、以及 Bootloader 加载流程串在了一起:

分区表知识体系总览 分区表知识体系总览 MBR 分区表 LBA 0: 引导代码 + 4 个表项 最大 2 TiB / 4 个主分区 分区类型: 单字节 ID (0x83等) GPT 分区表 LBA 0: 保护 MBR / LBA 1: 头部 最大 8 ZiB / 128 个分区 分区类型: GUID / 有备份表头 分区对齐 HDD: 1 MiB 对齐 eMMC: 4 MiB 对齐 NAND: 页 × 块对齐 Bootloader 加载流程 读取分区表 解析分区类型 计算偏移与大小 加载镜像到内存 分区对齐错误会导致性能下降甚至加载失败 建议在分区工具中强制指定对齐参数

这张图把 MBR 和 GPT 的核心差异、分区对齐要求、以及 Bootloader 的加载流程串在了一起。你可以看到,分区表解析是 Bootloader 启动流程的第一步——这一步错了,后面全白搭。

我的习惯:在项目初期,我会用 sgdisk 或 parted 工具先验证分区布局,确认对齐和大小都正确后,再把分区表固化到 Bootloader 的配置中。这样能避免后期反复修改。