分区表基础:MBR与GPT分区表的结构与区别

做嵌入式固件开发,绕不开分区表。你想想看,Bootloader 要找到内核、文件系统、设备树,靠的就是分区表。我刚开始做这块时,总觉得分区表就是存几个地址偏移,后来踩了坑才明白——分区表选不对,整个启动流程都可能翻车。

今天咱们就聊聊两种主流分区表:MBR 和 GPT。我会结合自己项目里的实际经验,把它们的结构、区别、标识符和对齐规则讲清楚。

MBR 分区表:老将的坚守与局限

MBR(Master Boot Record)诞生于上世纪 80 年代,至今仍在大量嵌入式设备中使用。它的结构其实很简单:

  • 引导代码区(446 字节):存放第一阶段 Bootloader
  • 分区表(64 字节):4 个分区表项,每项 16 字节
  • 结束标志(2 字节):0x55AA

每个分区表项的结构是这样的:

typedef struct {
    uint8_t  status;        // 0x80 表示可引导
    uint8_t  chs_first[3];  // 起始 CHS 地址
    uint8_t  type;          // 分区类型标识符
    uint8_t  chs_last[3];   // 结束 CHS 地址
    uint32_t lba_first;     // 起始 LBA 地址
    uint32_t sector_count;  // 扇区数
} __attribute__((packed)) mbr_part_entry_t;

这里有个关键点:LBA 地址是 32 位的。这意味着最大只能寻址 2^32 个扇区,按 512 字节/扇区算,就是 2TB。我在一个老项目里遇到过,客户拿了个 3TB 的硬盘,MBR 分区表死活认不全——这就是它的硬伤。

注意:MBR 的分区类型标识符是单字节的。比如 0x83 是 Linux 文件系统,0x0C 是 FAT32,0x82 是 Linux swap。我建议你在做 Bootloader 时,一定要校验这个字节,否则可能把内核分区当成文件系统来读。

GPT 分区表:现代标准,大势所趋

GPT(GUID Partition Table)是 UEFI 规范的一部分,解决了 MBR 的诸多限制。它的结构更复杂,但更灵活:

  • 保护 MBR(LBA 0):防止老工具误识别
  • GPT 头部(LBA 1):描述分区表本身的信息
  • 分区表项(LBA 2~33):默认 128 个分区表项,每项 128 字节
  • 备份区(磁盘末尾):头部和分区表的备份

每个 GPT 分区表项的结构:

typedef struct {
    uint8_t  type_guid[16];   // 分区类型 GUID
    uint8_t  part_guid[16];   // 分区唯一 GUID
    uint64_t lba_first;       // 起始 LBA(64 位)
    uint64_t lba_last;        // 结束 LBA(64 位)
    uint64_t attributes;      // 属性标志
    uint16_t name[36];        // 分区名称(UTF-16LE)
} __attribute__((packed)) gpt_part_entry_t;

你看,LBA 地址变成了 64 位,理论上可以支持 8ZB 的磁盘。分区类型用 16 字节的 GUID 标识,再也不用担心冲突了。我个人习惯在项目里用 GPT,尤其是做嵌入式 Linux 或 Android 系统时,分区数量多、类型复杂,GPT 的灵活性就体现出来了。

核心区别:MBR 用 1 字节标识分区类型,最多 4 个主分区;GPT 用 16 字节 GUID,最多 128 个分区,且自带备份和 CRC32 校验。

分区类型标识符:别搞混了

做 Bootloader 开发,分区类型标识符是必须掌握的。我整理了一份常用对照表:

用途 MBR 类型字节 GPT 类型 GUID
FAT32 0x0C EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
Linux 文件系统 0x83 0FC63DAF-8483-4772-8E79-3D69D8477DE4
Linux swap 0x82 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F
EFI 系统分区 0xEF C12A7328-F81F-11D2-BA4B-00A0C93EC93B
Android 用户数据 0x83 EBD0A0A2-B9E5-4433-87C0-68B6B72699C7

我曾经在一个项目里,把 FAT32 分区误标成了 0x83,结果 Bootloader 读不到 boot.img,卡在启动界面半天。排查到最后才发现是分区类型写错了——这种低级错误,真的很坑。

分区对齐:性能的关键

分区对齐,说白了就是让分区的起始地址是某个值的整数倍。为什么需要对齐?因为存储介质的物理特性。

以 NAND Flash 为例,它的读写单位是页(Page),擦除单位是块(Block)。如果分区起始地址不对齐到页边界,读写时就会跨页操作,性能直接腰斩。我测过一组数据:

对齐方式 顺序读(MB/s) 随机写(IOPS)
未对齐(偏移 1 扇区) 42 1200
1MB 对齐 98 5800

差距很明显吧?所以现在主流做法是 1MB 对齐。为什么是 1MB?因为 1MB 是 2048 个 512 字节扇区,能兼容绝大多数 Flash 的页大小(4KB、8KB、16KB)和块大小(128KB、256KB、512KB)。

我的建议:在分区表工具里,把起始扇区设为 2048 的整数倍。比如第一个分区从 LBA 2048 开始,第二个分区从 LBA 4096 开始,以此类推。这样既保证了性能,也方便后续扩展。

MBR vs GPT:怎么选?

我总结了一个选择原则:

  • 选 MBR:磁盘小于 2TB,分区不超过 4 个,需要兼容老 Bootloader(比如某些 U-Boot 版本)
  • 选 GPT:磁盘大于 2TB,分区超过 4 个,需要 CRC 校验和备份保护

不过说实话,现在新项目我基本都用 GPT。哪怕磁盘只有 1GB,GPT 的灵活性和安全性也值得。唯一要注意的是,有些老 Bootloader 不支持 GPT,需要额外加补丁。

知识体系总览

下面这张图,把 MBR 和 GPT 的核心结构、区别、对齐要求串起来了。你可以把它当作本章的思维导图:

分区表知识体系 MBR 分区表 GPT 分区表 引导代码 446B 分区表 64B 最多 4 分区 最大 2TB 保护 MBR GPT 头部 最多 128 分区 最大 8ZB 分区对齐:推荐 1MB 对齐 起始 LBA % 2048 == 0 提升读写性能 选择建议:新项目优先 GPT,老兼容选 MBR