分区表设计原则:分区表的作用、GPT与MBR对比、分区对齐与大小规划
大家好,我是你们的嵌入式系统讲师。今天我们来聊聊Bootloader开发中一个绕不开的话题——分区表设计。说实话,这玩意儿看着简单,但坑特别多。我见过不少项目,前期分区规划随便搞搞,后期升级时发现空间不够,或者启动时因为对齐问题卡死,那叫一个头疼。
分区表说白了就是一张地图。它告诉Bootloader:你的代码放在哪,文件系统在哪,升级包又该存哪。没有这张地图,芯片上电后就是无头苍蝇。嗯,咱们今天就把这事彻底讲透。
分区表的核心作用
分区表到底在干什么?我总结了三件事:
- 定位:告诉Bootloader每个分区的起始地址和大小。比如我的Bootloader分区从0x08000000开始,大小64KB。
- 类型标识:区分这个分区是存代码的、存配置的、还是存日志的。不同类型有不同的保护策略。
- 启动顺序:多分区系统里,哪个分区先启动,哪个是备份,全靠分区表来编排。
重要提醒:分区表一旦损坏,整个系统就变砖了。所以很多方案会把分区表存两份,一份主表,一份备份表。我在一个量产项目中就遇到过Flash坏块正好砸在主分区表上,幸亏有备份,不然几万台设备就废了。
GPT与MBR:两种分区表的对决
嵌入式世界里,MBR和GPT是两大主流。你可能会问:MBR这么老,为什么还在用?我刚开始也有这个疑问。后来在几个低功耗项目里才明白,有些MCU的Flash只有256KB,你让GPT那34个扇区的开销往里塞,根本不现实。
来,咱们直接看对比:
| 特性 | MBR | GPT |
|---|---|---|
| 分区数量 | 最多4个主分区 | 理论上无限(通常128个) |
| 最大容量 | 2TB | 9.4ZB(基本无限制) |
| 冗余备份 | 无(只有一份) | 有(主表和备份表) |
| 校验机制 | 无 | CRC32校验 |
| 适用场景 | 小容量Flash、老旧MCU | 大容量eMMC、NAND、安全要求高 |
| 兼容性 | 几乎所有Bootloader都支持 | 需要UEFI或较新Bootloader |
我个人习惯是:Flash小于1MB的,用MBR就够了。大于4MB的,尤其是跑Linux或复杂RTOS的,直接上GPT。为什么?因为GPT的CRC校验能帮你发现分区表是否被篡改,这在安全启动里太重要了。
小技巧:如果你用MBR但需要超过4个分区,可以用扩展分区。不过我在项目里吃过亏——扩展分区的链式结构在Bootloader里解析起来特别麻烦,容易出bug。所以能上GPT就别折腾MBR扩展分区。
分区对齐:性能与可靠性的关键
分区对齐,说白了就是让每个分区的起始地址是某个数值的整数倍。这个数值通常是Flash的擦除块大小,或者NAND的页大小。
为什么会这样?你想想看,Flash擦除是按块来的。如果你的分区起始地址不在块边界上,擦除一个分区时可能会影响到相邻分区。我曾经在一个项目中,分区A的结束地址和分区B的起始地址在同一个擦除块里。结果擦除分区A时,把分区B的头部数据也擦掉了。嗯,那次排查了整整两天。
对齐规则其实很简单:
- NOR Flash:按扇区对齐,通常是4KB或64KB
- NAND Flash:按页对齐,通常是2KB或4KB
- eMMC:按擦除组对齐,通常是512KB或1MB
我建议你在分区表里直接定义对齐宏:
/* 分区对齐宏 */
#define FLASH_SECTOR_SIZE 4096 /* 4KB扇区 */
#define PARTITION_ALIGN(x) (((x) + FLASH_SECTOR_SIZE - 1) & ~(FLASH_SECTOR_SIZE - 1))
/* 分区定义示例 */
#define BOOTLOADER_START 0x08000000
#define BOOTLOADER_SIZE PARTITION_ALIGN(64 * 1024) /* 64KB,自动对齐到64KB */
#define APP_START (BOOTLOADER_START + BOOTLOADER_SIZE)
#define APP_SIZE PARTITION_ALIGN(256 * 1024) /* 256KB */
警告:千万不要手动计算对齐地址!我见过有人直接在代码里写0x08010000这种硬编码,结果换了Flash型号后扇区大小变了,整个分区表全乱套。用宏定义自动计算,一劳永逸。
分区大小规划:留多少才够用?
这是最让人纠结的问题。分大了浪费空间,分小了后期升级不够用。我的经验是分三步走:
- 估算当前需求:编译出来的固件多大?文件系统要存多少数据?日志分区要保留多久?
- 预留增长空间:我一般按当前大小的1.5到2倍来规划。比如Bootloader现在32KB,我分64KB。为什么?因为后续可能要加安全启动、加OTA校验,代码量会涨。
- 考虑擦除寿命:频繁擦写的分区(比如日志分区)要适当放大,分散写磨损。
给你一个典型的分区规划参考:
| 分区名称 | 典型大小 | 说明 |
|---|---|---|
| Bootloader | 64KB - 256KB | 包含启动代码和分区表 |
| App Primary | 256KB - 2MB | 主应用程序 |
| App Backup | 256KB - 2MB | OTA升级时的备份区 |
| Config | 16KB - 64KB | 设备配置参数 |
| Log | 64KB - 512KB | 运行日志,循环写入 |
| File System | 剩余空间 | 用户数据存储 |
这里有个坑:备份分区的大小必须和主分区完全一致。我见过有人把主分区设成256KB,备份分区设成260KB,结果OTA升级时校验通过但写入失败——因为备份区最后4KB写不进去。这种低级错误,排查起来特别丢人。
我的习惯:在分区表末尾留一个“未分配区域”,大小是擦除块的整数倍。万一哪个分区不够用了,可以从这里借空间。虽然不优雅,但比重新烧录整个Flash强。
总结一下
分区表设计没有银弹,但有几个原则是通用的:
- 分区表要冗余,要有校验
- 对齐比大小更重要
- 预留空间永远不嫌多
- 用宏定义,别硬编码
好了,这一章就到这里。下一章咱们会深入分区表的代码实现,手把手教你写一个轻量级的分区表解析器。到时候你会看到,理论上的对齐和规划,在代码里是怎么落地的。