分区表基础:分区表的作用、MBR与GPT分区表格式、分区类型与属性
各位做嵌入式开发的同行,今天我们来聊聊分区表。这东西看着不起眼,但你要是搞错了,板子直接变砖。我这些年调试过的Bootloader问题,少说有三成跟分区表有关。所以这一章,咱们把基础打扎实。
分区表到底是干什么的?
说白了,分区表就是一张地图。它告诉Bootloader:你的Flash或者eMMC上,哪块区域是Bootloader自己,哪块是内核,哪块是文件系统,哪块是用户数据。
没有分区表,Bootloader启动时就像没头苍蝇——它不知道从哪里加载内核,也不知道该跳转到哪个地址。你想想看,一个系统连自己身体结构都不清楚,怎么可能跑得起来?
核心作用一句话:分区表是Bootloader与存储介质之间的契约,定义了每个分区的起始地址、大小、类型和属性。
我在一个量产项目上遇到过这种情况:工程师把分区表偏移搞错了1个扇区,结果Bootloader每次启动都读到错误的内核镜像,系统反复重启。查了三天,最后发现是分区表里一个字节写错了。嗯,从那以后我每次写分区表都要反复对三遍。
MBR与GPT:两种主流分区表格式
嵌入式领域最常见的就是MBR和GPT。我建议你至少把这两种都吃透,因为不同芯片、不同项目用的格式不一样。
MBR(Master Boot Record)
MBR是上世纪80年代的老将了,但至今仍在大量使用。它位于存储设备的第一个扇区(LBA 0),总共512字节。结构很简单:
- 引导代码(446字节):Bootloader的第一阶段代码,负责加载后续阶段
- 分区表(64字节):4个分区表项,每个16字节
- 结束标志(2字节):固定为0x55AA
每个分区表项包含:
| 偏移 | 长度 | 含义 |
|---|---|---|
| 0x00 | 1字节 | 引导标志(0x80表示可引导) |
| 0x01 | 3字节 | 起始CHS地址 |
| 0x04 | 1字节 | 分区类型(如0x83=Linux, 0x0C=FAT32) |
| 0x05 | 3字节 | 结束CHS地址 |
| 0x08 | 4字节 | 起始LBA地址 |
| 0x0C | 4字节 | 分区大小(扇区数) |
注意:MBR最大只能管理2TB的磁盘,因为LBA地址用32位表示。另外,MBR只支持4个主分区。如果需要更多分区,就得用扩展分区和逻辑分区——这在嵌入式里很麻烦。
我曾经调试过一个项目,Flash容量刚好超过2TB,结果MBR分区表死活认不全。后来换成GPT,问题立刻解决。所以,如果你的存储介质超过2TB,别犹豫,直接上GPT。
GPT(GUID Partition Table)
GPT是UEFI时代的产物,也是现代嵌入式系统的首选。它使用LBA 1~33来存放分区表信息,结构更灵活、更健壮。
GPT的核心结构:
- 保护MBR(LBA 0):一个假的MBR,防止老工具误操作
- GPT头(LBA 1):包含分区表的位置、大小、校验和等信息
- 分区表项(LBA 2~33):每个分区表项128字节,最多支持128个分区
- 备份GPT(磁盘末尾):主GPT的完整备份,用于恢复
每个GPT分区表项包含:
| 偏移 | 长度 | 含义 |
|---|---|---|
| 0x00 | 16字节 | 分区类型GUID |
| 0x10 | 16字节 | 唯一分区GUID |
| 0x20 | 8字节 | 起始LBA地址 |
| 0x28 | 8字节 | 结束LBA地址 |
| 0x30 | 8字节 | 属性标志 |
| 0x38 | 72字节 | 分区名称(UTF-16LE) |
个人建议:新项目一律用GPT。它支持2TB以上容量、无限分区数(实际受限于表项数量)、有CRC校验防止数据损坏,还有备份GPT做容错。我在一个车机项目里用GPT,有一次Flash坏块导致主GPT损坏,Bootloader自动切换到备份GPT,系统照样启动——这种可靠性,MBR给不了。
分区类型与属性
分区类型决定了这个分区里装的是什么。Bootloader根据分区类型来决定如何处理这个分区。
常见分区类型
| 类型 | MBR类型值 | GPT类型GUID | 用途 |
|---|---|---|---|
| 未使用 | 0x00 | 00000000-0000-0000-0000-000000000000 | 空表项 |
| FAT12 | 0x01 | EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 | 小容量文件系统 |
| FAT16 | 0x06 | EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 | 旧式文件系统 |
| FAT32 | 0x0C | EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 | 通用文件系统 |
| Linux | 0x83 | 0FC63DAF-8483-4772-8E79-3D69D8477DE4 | Linux原生分区 |
| Linux Swap | 0x82 | 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F | 交换分区 |
| EFI系统 | 0xEF | C12A7328-F81F-11D2-BA4B-00A0C93EC93B | EFI引导分区 |
| Bootloader | 自定义 | 自定义 | 存放Bootloader镜像 |
分区属性
分区属性决定了Bootloader怎么对待这个分区。常见的属性包括:
- 启动标志:MBR里用0x80标记可引导分区,GPT里用属性位表示。Bootloader会扫描所有分区,找到带启动标志的那个,然后加载它。
- 系统分区:存放操作系统内核和核心文件。Bootloader通常需要从这个分区加载内核镜像。
- 数据分区:存放用户数据、日志、配置等。Bootloader一般不需要直接访问,但有些场景下需要从数据分区读取升级包。
- 只读属性:防止意外写入。我在一个安全关键系统里,把Bootloader分区设为只读,结果有一次升级程序写错了地址,差点把Bootloader覆盖掉——幸好只读属性拦住了。
关键点:Bootloader在启动时,会按照分区表里的顺序扫描每个分区。它先看分区类型,再看属性标志。如果分区类型是"Bootloader"且属性包含"可执行",Bootloader就会尝试加载并跳转。如果分区类型是"系统",Bootloader就会加载内核。
分区表在Bootloader中的典型布局
我画了一张图,展示一个典型的嵌入式系统分区布局。你可以看到分区表在整个存储结构中的位置。
这张图展示的是GPT的布局。如果是MBR,结构更简单:LBA 0就是MBR本身,包含引导代码和4个分区表项,没有备份区域。
Bootloader如何解析分区表
Bootloader启动时,分区表解析流程大致如下:
- 读取LBA 0:判断是MBR还是GPT(通过检查0x55AA标志和后续结构)
- 如果是MBR:直接解析4个主分区表项,读取类型、起始地址、大小
- 如果是GPT:先读LBA 1的GPT头,获取分区表项的位置和数量,再读分区表项
- 遍历分区:按顺序扫描所有分区,匹配类型和属性
- 加载目标分区:找到启动分区或系统分区后,读取内容到内存并执行
避坑指南:我曾经在一个项目里发现,Bootloader解析GPT时没有校验CRC。结果有一次Flash写入错误,GPT头损坏,Bootloader读到了错误的分区表项,直接跳转到非法地址,系统崩溃。后来我加上了CRC校验,并在校验失败时自动切换到备份GPT——这个改动救了那个项目至少三次。
分区表调试常见问题
最后,我总结几个我在调试中经常遇到的分区表问题:
- 分区表偏移错误:分区表没有放在正确的LBA地址。Bootloader读到的全是垃圾数据。
- 分区大小计算错误:分区大小用字节数而不是扇区数,或者扇区大小搞错(512字节 vs 4096字节)。
- 分区类型不匹配:Bootloader期望的分区类型与实际写入的不一致,导致无法识别。
- 属性标志冲突:多个分区同时设置了启动标志,Bootloader不知道该启动哪个。
- 备份GPT损坏:主GPT和备份GPT都不一致,系统无法恢复。
重要提醒:分区表是Bootloader的命根子。我建议你在开发阶段就加上分区表完整性检查,包括CRC校验、魔数检查、地址范围检查。别等到量产了才发现问题——那时候改一个字节都要走完整的变更流程,代价太大了。
好了,这一章的内容就到这里。分区表的基础知识,说白了就是一张地图加一套规则。你理解了MBR和GPT的区别,掌握了分区类型和属性的含义,后面调试Bootloader分区问题就会顺手很多。