分区表基础:MBR、GPT与对齐的艺术

做嵌入式开发这么多年,我见过不少工程师在分区表上栽跟头。说白了,分区表就是一张「地图」,告诉系统你的存储设备上哪些区域归谁管。没有它,Bootloader 连自己该从哪儿启动都不知道。

今天咱们就把分区表这事儿彻底聊透。我会结合自己踩过的坑,帮你把 MBR、GPT 和对齐这几个核心概念理清楚。

什么是分区表?

分区表是存储设备(比如 eMMC、NAND Flash、SD 卡)最开头的一段特殊数据。它记录了每个分区的起始位置、大小和类型。

你可以把它想象成一本「目录」:

  • 第 0 页:分区表本身(通常占用 LBA 0)
  • 第 1 页:Bootloader 分区
  • 第 2 页:环境变量分区
  • 第 3 页:内核分区
  • ……

系统启动时,BootROM 会先读分区表,找到 Bootloader 的位置,然后跳过去执行。如果分区表坏了,整个设备就变砖了——嗯,我在项目里就遇到过这种惨案。

⚠️ 重要提醒:分区表损坏 = 设备变砖。所以分区表通常有备份,或者放在硬件写保护的区域。

MBR 与 GPT:两代分区表标准

目前主流的分区表格式就两种:MBR(Master Boot Record)和 GPT(GUID Partition Table)。

我刚开始做嵌入式时,几乎所有项目都用 MBR。但最近几年,GPT 越来越常见了。为什么?咱们对比着看。

MBR(主引导记录)

MBR 诞生于 1983 年,是 IBM PC 时代的产物。它长这样:

  • 位置:LBA 0(第一个扇区)
  • 大小:512 字节
  • 结构:446 字节引导代码 + 64 字节分区表 + 2 字节签名
  • 分区数量:最多 4 个主分区(或 3 个主分区 + 1 个扩展分区)
  • 最大容量:2TB(因为用 32 位 LBA 地址)

MBR 最大的问题是:它太老了。2TB 的限制在嵌入式设备上可能还好,但 4 个主分区的限制真的很烦人。我记得有个项目需要 6 个分区(Bootloader、环境变量、内核、DTB、rootfs、数据区),用 MBR 就得搞扩展分区,麻烦得要命。

GPT(GUID 分区表)

GPT 是 UEFI 时代的产物,解决了 MBR 的几乎所有痛点:

  • 位置:LBA 1 开始(LBA 0 保留给 MBR 兼容性)
  • 大小:通常 16KB(128 个分区条目 × 128 字节)
  • 分区数量:理论上无限制(Windows 限制 128 个)
  • 最大容量:9.4ZB(64 位 LBA 地址)
  • 可靠性:有 CRC32 校验,有备份 GPT 表

我个人习惯在 Buildroot 项目里优先用 GPT,尤其是当存储设备大于 2GB 时。GPT 的备份机制让我睡得踏实——有一次 eMMC 的 LBA 0 区域被意外擦除了,但 GPT 的备份表在 LBA 末尾,系统照样能启动。

特性 MBR GPT
诞生时间 1983 年 2000 年代
分区表位置 LBA 0 LBA 1 ~ LBA 33
最大分区数 4 个主分区 128+ 个
最大磁盘容量 2TB 9.4ZB
备份机制 无 有(磁盘末尾备份)
校验 无 CRC32
嵌入式适用性 小容量设备(<2GB) 大容量设备(≥2GB)
💡 我的建议:如果你的 eMMC 或 SD 卡容量小于 2GB,且分区数不超过 4 个,MBR 完全够用。否则,直接用 GPT,省心。

分区对齐:为什么它如此重要?

分区对齐,说白了就是让每个分区的起始扇区号是某个值的整数倍。这个值通常是 1MB(2048 个扇区),或者 Flash 的擦除块大小。

你可能会问:「不对齐会怎样?」

我举个例子。假设你的 eMMC 擦除块大小是 512KB,但你分区起始在扇区 1(512 字节偏移)。当你写入一个 512KB 的数据时,它实际上跨越了两个擦除块。这意味着:

  • 写入放大:本来只擦一个块,现在要擦两个
  • 性能下降:写入速度可能掉 30%~50%
  • 寿命缩短:Flash 有擦写次数限制,多擦一次就少一次

我曾经在一个项目里忽略了分区对齐,结果 Bootloader 的写入速度只有预期的 60%。排查了两天才发现是分区起始扇区没对齐。从那以后,我所有分区都强制对齐到 1MB 边界。

对齐的数学原理

对齐其实很简单:

// 扇区大小 = 512 字节
// 对齐单位 = 1MB = 1024 * 1024 字节 = 2048 个扇区

// 正确对齐的分区起始扇区:
start_sector = 2048   // 第 1 个分区
start_sector = 4096   // 第 2 个分区
start_sector = 6144   // 第 3 个分区

// 错误对齐的分区起始扇区:
start_sector = 1      // ❌ 不对齐
start_sector = 100    // ❌ 不对齐
start_sector = 2049   // ❌ 不对齐

在 Buildroot 的 genimage.cfg 里,对齐是这样配置的:

image boot.vfat {
  vfat {
    files = {
      "boot.scr",
      "zImage",
      "sun8i-h3-orangepi-pc.dtb"
    }
  }
  size = 32M
}

image sdcard.img {
  hdimage {
    partition-table-type = "gpt"
    // 关键:对齐到 1MB
    align = 1M
  }

  partition boot {
    image = "boot.vfat"
    offset = 1M      // 起始于 1MB 边界
    size = 32M
  }

  partition rootfs {
    image = "rootfs.ext4"
    offset = 33M     // 33MB = 1MB + 32MB,依然对齐
    size = 256M
  }
}
🔑 核心原则:所有分区的起始扇区号必须是「对齐单位」的整数倍。嵌入式设备推荐对齐到 1MB(2048 扇区)。

分区表在 Buildroot 中的实际应用

在 Buildroot 里,分区表是通过 genimage 工具生成的。你只需要在 board/你的板子/genimage.cfg 里定义好分区布局,Buildroot 会自动帮你生成完整的镜像文件。

下面是一个典型的 GPT 分区布局:

分区名 起始偏移 大小 文件系统 用途
GPT 表 LBA 0 16KB - 分区表本身
bootloader 1MB 1MB raw SPL + U-Boot
env 2MB 256KB raw U-Boot 环境变量
kernel 3MB 16MB raw Linux 内核
dtb 19MB 1MB raw 设备树
rootfs 20MB 剩余空间 ext4 根文件系统

注意看,所有分区的起始偏移都是 1MB 的整数倍。这就是对齐。

⚠️ 避坑指南:我曾经在 genimage.cfg 里忘了写 align = 1M,结果分区自动从 LBA 1 开始。刷到板子上后,U-Boot 死活找不到环境变量分区。排查了整整一天,最后发现是分区没对齐导致的读写错位。所以,永远记得在 hdimage 里加上 align 参数。

用 SVG 图看懂分区表结构

说了这么多,不如一张图来得直观。下面这张图展示了 MBR 和 GPT 在磁盘上的布局差异:

MBR 与 GPT 分区表结构对比 MBR 布局 LBA 0: MBR (512B) LBA 1~: 分区1 (Bootloader) LBA n~: 分区2 (Kernel) LBA m~: 分区3 (Rootfs) ⚠️ 缺点:最多4个主分区 最大磁盘容量 2TB GPT 布局 LBA 0: 保护MBR LBA 1~33: GPT表头+分区条目 LBA 34~: 分区1 (Bootloader) LBA n~: 分区2 (Kernel) LBA m~: 分区3 (Rootfs) LBA end-33~end: 备份GPT表 ✅ 优点:最多128+个分区 最大磁盘容量 9.4ZB 分区对齐要求:所有分区起始扇区必须是 1MB (2048扇区) 的整数倍 例如:1MB、2MB、3MB…… 而不是 1MB + 512B 对齐示例: ✅ 正确:offset = 1M, 2M, 3M, 4M ... ❌ 错误:offset = 1M + 512B, 2M + 1K ...

从图上可以清楚看到:MBR 把所有信息塞进 LBA 0 的 512 字节里,而 GPT 用了 LBA 0 到 LBA 33 共 34 个扇区来存放分区表,还在磁盘末尾放了备份。这就是 GPT 更可靠的原因。

小结

分区表是 Bootloader 启动的第一步。选 MBR 还是 GPT,取决于你的存储容量和分区数量。但无论选哪种,分区对齐都是必须遵守的硬规则。

我个人在 Buildroot 项目中的习惯是:

  • 存储 < 2GB 且分区 ≤ 4 个 → MBR
  • 存储 ≥ 2GB 或分区 > 4 个 → GPT
  • 所有分区强制对齐到 1MB 边界