分区表基础: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) |
分区对齐:为什么它如此重要?
分区对齐,说白了就是让每个分区的起始扇区号是某个值的整数倍。这个值通常是 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
}
}
分区表在 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 的整数倍。这就是对齐。
align = 1M,结果分区自动从 LBA 1 开始。刷到板子上后,U-Boot 死活找不到环境变量分区。排查了整整一天,最后发现是分区没对齐导致的读写错位。所以,永远记得在 hdimage 里加上 align 参数。
用 SVG 图看懂分区表结构
说了这么多,不如一张图来得直观。下面这张图展示了 MBR 和 GPT 在磁盘上的布局差异:
从图上可以清楚看到:MBR 把所有信息塞进 LBA 0 的 512 字节里,而 GPT 用了 LBA 0 到 LBA 33 共 34 个扇区来存放分区表,还在磁盘末尾放了备份。这就是 GPT 更可靠的原因。
小结
分区表是 Bootloader 启动的第一步。选 MBR 还是 GPT,取决于你的存储容量和分区数量。但无论选哪种,分区对齐都是必须遵守的硬规则。
我个人在 Buildroot 项目中的习惯是:
- 存储 < 2GB 且分区 ≤ 4 个 → MBR
- 存储 ≥ 2GB 或分区 > 4 个 → GPT
- 所有分区强制对齐到 1MB 边界