3、分区表的结构:MBR与GPT的底层拆解
分区表这东西,说白了就是一张“地图”。BootROM 上电后,得靠这张地图找到操作系统在哪儿。我早年做嵌入式开发时,亲手写过一段从 MBR 里捞内核地址的代码,那会儿才真正理解——分区表的结构,直接决定了启动流程怎么走。
今天咱们就把 MBR 和 GPT 这两张地图,从底层到上层,掰开揉碎讲清楚。
3.1 MBR 结构:传统但依然活跃
MBR(Master Boot Record)诞生于 DOS 时代,结构极其紧凑。它总共只有 512 字节,放在磁盘的 0 号扇区(LBA 0)。
这 512 字节被分成三块:
| 偏移地址 | 大小 | 内容 | 说明 |
|---|---|---|---|
| 0x000 - 0x1BD | 446 字节 | 引导代码(Boot Code) | 第一阶段引导程序,负责加载活动分区中的 VBR |
| 0x1BE - 0x1FD | 64 字节 | 分区表(Partition Table) | 4 个分区表项,每项 16 字节 |
| 0x1FE - 0x1FF | 2 字节 | 签名(Signature) | 固定为 0x55AA,标识这是一个有效的 MBR |
核心要点:MBR 的 512 字节是硬性限制。引导代码 + 分区表 + 签名,一个字节都不能多。这也是为什么 MBR 最多只支持 4 个主分区——64 字节分区表,每项 16 字节,刚好 4 项。
3.1.1 引导代码(Boot Code)
这 446 字节是 CPU 上电后最早执行的用户代码(在 BIOS 阶段)。它的任务很简单:
- 扫描分区表,找到标记为“活动”(Active)的分区
- 将该分区的第一个扇区(VBR)加载到内存
- 跳转到 VBR 继续执行
我在一个老旧工控项目里遇到过一个问题:引导代码被意外覆盖,磁盘数据全在,但就是启动不了。排查了半天,最后用 hexdump 一看,0x55AA 还在,但引导代码区域全是 0x00。嗯,那之后我养成了习惯——每次烧写固件前,先备份 MBR 的前 446 字节。
3.1.2 分区表(Partition Table)
每个分区表项 16 字节,结构如下:
| 偏移 | 大小 | 字段 | 说明 |
|---|---|---|---|
| 0x00 | 1 字节 | 引导标志 | 0x80 = 活动分区,0x00 = 非活动 |
| 0x01-0x03 | 3 字节 | 起始 CHS 地址 | 磁头/扇区/柱面(已过时,但兼容保留) |
| 0x04 | 1 字节 | 分区类型 | 如 0x0C = FAT32,0x83 = Linux ext |
| 0x05-0x07 | 3 字节 | 结束 CHS 地址 | 同上,历史遗留 |
| 0x08-0x0B | 4 字节 | 起始 LBA 地址 | 逻辑块地址,这才是现代系统用的 |
| 0x0C-0x0F | 4 字节 | 分区总扇区数 | 该分区占用的扇区数量 |
注意:MBR 分区表使用 32 位 LBA 地址,最大只能寻址 2^32 × 512 字节 = 2 TB。超过 2 TB 的磁盘,必须用 GPT。我曾经被这个坑过——客户买了个 4 TB 硬盘,用 MBR 分区,结果只认出来 2 TB,数据全写在前面 2 TB 里,后面 2 TB 直接“消失”。
3.1.3 签名(0x55AA)
最后两个字节必须是 0x55 0xAA(注意是小端序,磁盘上看到的是 0x55 0xAA,但作为 16 位值读出来是 0xAA55)。
BootROM 或 BIOS 在加载 MBR 后,会检查这个签名。如果不匹配,就认为磁盘不可引导。你想想看,如果签名被破坏,整个磁盘就像“失忆”了一样——数据全在,但系统不认。
3.2 GPT 结构:现代系统的选择
GPT(GUID Partition Table)是 UEFI 时代的产物。它解决了 MBR 的三大痛点:
- 最大磁盘容量从 2 TB 扩展到 9.4 ZB(2^64 扇区)
- 主分区数量从 4 个扩展到理论上无限制(实际受分区表大小限制)
- 增加了备份分区表,容错性更强
GPT 的磁盘布局是这样的:
LBA 0: 保护 MBR(Protective MBR)
LBA 1: GPT 头(GPT Header)
LBA 2-33: 分区表项(Partition Entries),默认 128 项,每项 128 字节
LBA 34+: 用户数据区域
...
最后 33 个 LBA: 分区表项备份
最后一个 LBA: GPT 头备份
3.2.1 保护 MBR(LBA 0)
这是一个“假”的 MBR。它的作用是:
- 让不支持 GPT 的旧系统(如老 BIOS)识别到这是一个“已分区”的磁盘
- 防止旧工具误认为磁盘“未初始化”而覆盖数据
保护 MBR 的分区表里只有一个分区表项:
- 分区类型:0xEE(GPT 保护分区)
- 起始 LBA:1
- 大小:整个磁盘大小(或 2 TB,取决于旧工具)
我见过一个案例:有人用老版本的 fdisk 直接写 MBR,结果把 GPT 的保护 MBR 覆盖了。虽然 GPT 头和数据还在,但某些系统死活不认盘。最后用 gdisk 重建保护 MBR 才救回来。
3.2.2 GPT 头(LBA 1)
GPT 头是整个分区表的“总指挥”。它的结构如下:
| 偏移 | 大小 | 字段 | 说明 |
|---|---|---|---|
| 0x00 | 8 字节 | 签名 | "EFI PART"(45 46 49 20 50 41 52 54) |
| 0x08 | 4 字节 | 版本号 | 当前为 1.0(0x00010000) |
| 0x0C | 4 字节 | 头大小 | 通常为 92 字节(0x5C) |
| 0x10 | 4 字节 | 头 CRC32 | 对整个头计算校验,用于完整性检查 |
| 0x14 | 4 字节 | 保留 | 必须为 0 |
| 0x18 | 8 字节 | 当前 LBA | GPT 头所在位置,即 LBA 1 |
| 0x20 | 8 字节 | 备份 LBA | 备份 GPT 头所在位置(最后一个 LBA) |
| 0x28 | 8 字节 | 分区表起始 LBA | 通常为 LBA 2 |
| 0x30 | 4 字节 | 分区表项数量 | 默认 128 |
| 0x34 | 4 字节 | 每项大小 | 固定 128 字节 |
| 0x38 | 4 字节 | 分区表 CRC32 | 对整个分区表计算校验 |
| 0x3C | 420 字节 | 保留/填充 | 填 0,凑够 512 字节 |
个人建议:调试 GPT 问题时,先检查 GPT 头的 CRC32。如果头被破坏,系统会尝试加载备份 GPT 头。我曾经用 dd 命令直接读取 LBA 1,然后用 Python 算 CRC,对比头里的值——不一致就说明有人动过 GPT 头。
3.2.3 分区表项(LBA 2-33)
每个分区表项 128 字节,结构如下:
| 偏移 | 大小 | 字段 | 说明 |
|---|---|---|---|
| 0x00 | 16 字节 | 分区类型 GUID | 标识分区用途(如 EFI 系统分区、Microsoft 基本数据分区) |
| 0x10 | 16 字节 | 唯一分区 GUID | 每个分区的全球唯一标识 |
| 0x20 | 8 字节 | 起始 LBA | 64 位,支持超大磁盘 |
| 0x28 | 8 字节 | 结束 LBA | 包含关系(即分区占用 [start, end]) |
| 0x30 | 8 字节 | 属性标志 | 如 0x01 = 系统分区,0x02 = 隐藏 |
| 0x38 | 72 字节 | 分区名称 | UTF-16LE 编码,最多 36 个字符 |
默认 128 个分区表项,占用 128 × 128 = 16384 字节 = 32 个扇区(LBA 2-33)。
3.2.4 分区表备份
GPT 在磁盘末尾完整备份了:
- 分区表项(最后 33 个 LBA,与 LBA 2-33 一一对应)
- GPT 头(最后一个 LBA)
这个设计非常实用。我记得有一次,一个同事误操作把磁盘前 64 个扇区全写零了。要是 MBR,这盘就废了。但因为是 GPT,我们用 gdisk 的恢复功能,从备份 GPT 头重建了主 GPT,数据完好无损。
关键区别:MBR 没有备份机制,一旦 0 号扇区损坏,整个分区表丢失。GPT 有主备两份,只要备份没坏,就能恢复。这也是为什么现代系统(尤其是服务器)都推荐 GPT。
3.3 MBR vs GPT:一张图看懂
下面这张 SVG 图展示了两种分区表的磁盘布局对比:
3.4 实际项目中的选择建议
说了这么多,到底什么时候用 MBR,什么时候用 GPT?我个人的经验是这样的:
- 2 TB 以下、传统 BIOS 启动、兼容性优先 → MBR。比如一些老旧的工控主板,只支持 BIOS 模式,用 MBR 最稳。
- 2 TB 以上、UEFI 启动、需要可靠性 → GPT。服务器、新式 PC、嵌入式设备(如树莓派 4 以上),无脑选 GPT。
- 混合场景 → 有些 BIOS 也支持 GPT 启动(通过 CSM 模块),但兼容性参差不齐。我建议:能用 UEFI 就用 GPT,省心。
避坑提醒:我曾经在一个项目里,为了兼容旧系统,强行在 4 TB 磁盘上用 MBR。结果系统只认前 2 TB,后面 2 TB 完全不可见。最后不得不重新分区,把数据迁移到 GPT 上。折腾了两天。所以,容量超过 2 TB,直接上 GPT,别犹豫。