分区表设计原则:分区表的作用、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. 估算当前需求:编译出来的固件多大?文件系统要存多少数据?日志分区要保留多久?
  2. 预留增长空间:我一般按当前大小的1.5到2倍来规划。比如Bootloader现在32KB,我分64KB。为什么?因为后续可能要加安全启动、加OTA校验,代码量会涨。
  3. 考虑擦除寿命:频繁擦写的分区(比如日志分区)要适当放大,分散写磨损。

给你一个典型的分区规划参考:

分区名称 典型大小 说明
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强。

总结一下

分区表设计没有银弹,但有几个原则是通用的:

  • 分区表要冗余,要有校验
  • 对齐比大小更重要
  • 预留空间永远不嫌多
  • 用宏定义,别硬编码

好了,这一章就到这里。下一章咱们会深入分区表的代码实现,手把手教你写一个轻量级的分区表解析器。到时候你会看到,理论上的对齐和规划,在代码里是怎么落地的。