3. 分区表概念:分区表的作用、常见分区表格式(GPT、MBR)、分区属性
分区表这东西,说白了就是一张“地图”。
你想想看,一块Flash或者硬盘,少则几MB,多则几十GB。Bootloader、内核、文件系统、用户数据全堆在上面。如果没有分区表,你怎么知道哪个区域是干什么的?
分区表就是用来干这个的。它告诉Bootloader:“从哪个地址开始,到哪个地址结束,这块区域归谁管”。
3.1 分区表到底有什么用?
我刚开始做嵌入式的时候,觉得分区表就是个“形式主义”。反正代码写死了地址,直接跳过去不就行了?
后来项目量产,发现不同批次Flash的坏块分布不一样,固件升级又需要动态调整分区大小。嗯,从那以后我再也不敢硬编码地址了。
分区表的核心作用,我总结为三点:
- 地址映射:把逻辑分区名称(如“kernel”、“rootfs”)映射到物理存储的起始地址和大小。
- 启动引导:Bootloader通过分区表找到下一个启动阶段(如U-Boot、ATF、内核)的位置。
- 空间管理:防止不同模块越界写入,避免“你踩了我的内核,我占了你的文件系统”。
一句话总结:没有分区表的系统,就像没有门牌号的楼房——你知道里面有人,但不知道谁住在哪一间。
3.2 两种主流分区表格式:MBR vs GPT
嵌入式领域,最常见的就是MBR和GPT。我分别说说它们的脾气。
3.2.1 MBR(Master Boot Record)
MBR是“老前辈”了。它诞生于1980年代,最初是为x86 PC设计的。结构很简单:
- 占用存储设备的第一个扇区(通常是512字节)。
- 前446字节是启动代码(Bootloader的第一阶段)。
- 接着64字节是分区表,最多记录4个主分区。
- 最后2字节是魔数(0x55AA),用于校验。
MBR的局限性很明显:
- 最大支持2TB:因为分区地址用32位表示,每个扇区512字节,算下来就是2^32 × 512 = 2TB。
- 最多4个主分区:想多分?得用扩展分区和逻辑分区,麻烦得很。
- 没有备份:MBR只存在第一个扇区,一旦损坏,整个盘就废了。
我曾经踩过的坑:在一个NAND Flash项目里,我用了MBR。结果Flash前几个块坏了,MBR直接读不出来。整个系统变砖。后来我强制要求:所有新项目必须用GPT,或者至少保留MBR的冗余备份。
3.2.2 GPT(GUID Partition Table)
GPT是MBR的“接班人”。它是UEFI规范的一部分,现在基本是主流。
GPT的优势很明显:
- 支持超大容量:用64位表示LBA地址,理论上支持8ZB(对,你没看错,泽字节级别)。
- 分区数量几乎无限制:默认预留128个分区表项,不够还能扩展。
- 有备份:GPT在存储设备的头部和尾部各存一份分区表,一份坏了,另一份顶上。
- 自带校验:每个分区表项都有CRC32校验,防止数据损坏。
但GPT也不是没缺点。它占用的空间比MBR大——头部需要至少34个扇区(包括主分区表和备份分区表)。对于只有几MB的Flash来说,这个开销有点心疼。
| 对比项 | MBR | GPT |
|---|---|---|
| 诞生时间 | 1980年代 | 2000年代(UEFI规范) |
| 最大容量 | 2TB | 8ZB |
| 最大主分区数 | 4个 | 128个(默认) |
| 备份机制 | 无 | 头部+尾部双备份 |
| 校验机制 | 仅魔数0x55AA | CRC32校验 |
| 适用场景 | 小容量、老旧系统 | 大容量、新设计 |
我的个人习惯:对于Flash小于16MB的MCU项目,我倾向于用自定义分区表(类似MBR但更精简)。对于Linux+大容量eMMC/NAND的项目,我无脑选GPT。别问为什么,问就是省心。
3.3 分区属性:每个分区都有“身份证”
不管是MBR还是GPT,每个分区表项都包含一组属性。我挑几个最重要的说说。
3.3.1 起始地址(Start LBA)
这个字段告诉Bootloader:“这个分区从哪个扇区开始”。
在MBR里,它是32位的,单位是扇区。在GPT里,它是64位的。
注意:起始地址必须是扇区对齐的。我见过有人把起始地址设成奇数扇区,结果读写性能直接砍半。
3.3.2 大小(Size / End LBA)
分区的大小。MBR用“起始地址 + 扇区数”表示,GPT用“起始LBA + 结束LBA”表示。
这里有个细节:结束LBA是包含的。也就是说,分区占用的扇区范围是 [StartLBA, EndLBA]。
3.3.3 类型(Partition Type)
这个字段标识分区里装的是什么。
- 在MBR里,类型是一个1字节的ID。比如0x83表示Linux文件系统,0x0C表示FAT32,0x82表示Swap。
- 在GPT里,类型是一个128位的GUID。比如Linux文件系统的GUID是
0FC63DAF-8483-4772-8E79-3D69D8477DE4。
我个人觉得,类型字段最大的作用是防止误用。Bootloader读到分区类型后,可以判断“这个分区是不是我能启动的”。比如你让Bootloader去加载一个Swap分区,那肯定崩。
3.3.4 标志位(Flags / Attributes)
标志位是分区的“开关”和“属性”。
- MBR:每个分区表项有一个字节的标志位。最常用的是第7位(0x80),表示“可引导分区”。
- GPT:每个分区表项有64位的属性字段。常用的位包括:
- Bit 0:Required(系统必需分区,不能删除)
- Bit 1:No Block IO(不通过块IO访问)
- Bit 2:Legacy BIOS Bootable(兼容传统BIOS启动)
避坑指南:我曾经在调试一个双系统启动时,发现第二个系统死活起不来。查了两天,最后发现是MBR里两个分区都标记了“可引导”。Bootloader只认第一个,第二个直接被忽略。所以,标志位不是你想设就能设的,得跟Bootloader的解析逻辑对齐。
3.4 一张图看懂分区表结构
说了这么多,不如直接画张图。下面这张SVG展示了GPT分区表的完整布局,以及Bootloader如何通过它找到各个分区。
3.5 嵌入式场景下的分区表选择建议
说了这么多理论,最后给点实际建议。
- 小Flash(≤16MB):用自定义精简分区表,或者MBR。GPT的头部开销太大,不划算。
- 大容量eMMC/NAND(≥1GB):无脑上GPT。备份和校验功能太香了。
- 需要兼容老旧Bootloader:查一下Bootloader的源码,看它支持MBR还是GPT。有些U-Boot版本对GPT支持不完整。
- 安全启动场景:建议用GPT,因为可以在分区属性里加“只读”或“已验证”标志,防止分区被篡改。
我的经验:不管用哪种格式,一定要在分区表里预留一个“备用分区”。OTA升级时,新固件先写到备用分区,验证通过后再切换。这样即使升级失败,系统还能回滚。这个习惯救了我好几次。