系统分区规划:Bootloader、Kernel、Rootfs、Data分区的布局策略

做嵌入式Linux升级,第一件事不是写代码,而是把存储空间规划好。我见过太多项目,开发阶段跑得挺好,一到量产升级就出各种问题——分区不够大、擦除把系统擦挂了、升级一半断电变砖了。说白了,分区规划是升级方案的基石,这块没想清楚,后面全是坑。

一、四大分区,各司其职

一个典型的嵌入式Linux系统,存储空间通常分成四个区域:Bootloader、Kernel、Rootfs、Data。每个分区都有自己的使命,咱们一个一个说。

分区名称 存储内容 典型大小 升级频率
Bootloader U-Boot、SPL、环境变量 1~4 MB 极少(除非换芯片)
Kernel zImage、DTB、设备树 8~32 MB 偶尔(驱动更新)
Rootfs 根文件系统、应用、库 128~512 MB 频繁(功能迭代)
Data 用户数据、日志、配置 剩余空间 不升级(只读写)

Bootloader分区:这是系统启动的第一段代码。我个人习惯把U-Boot和它的环境变量分开存放,环境变量单独划一个小块(比如256KB),这样升级Bootloader时不会把配置冲掉。嗯,这里要注意:Bootloader分区一般设为只读,除非你明确要做Bootloader升级,否则别给它写权限。

Kernel分区:存放压缩后的内核镜像和设备树。我在项目中遇到过一个问题——内核镜像越来越大,原来的16MB分区装不下了。所以建议你留点余量,按实际大小的1.5倍来规划。

Rootfs分区:这是升级最频繁的区域。应用代码、动态库、脚本全在这里。我建议用SquashFS或UBIFS这类只读文件系统,配合overlay实现可写层。为什么?因为只读文件系统在升级时可以直接整分区覆盖,不用担心文件系统不一致的问题。

Data分区:用户配置、日志、数据库文件放这里。这个分区不做升级,只做读写。格式建议用ext4或F2FS,支持断电保护。

二、双备份(A/B)系统原理

为什么要做A/B系统?你想想看,OTA升级最怕什么?升级到一半断电了,或者新版本有bug起不来。A/B系统的核心思想就是:永远有一个可用的系统在跑。

核心原理:把Bootloader、Kernel、Rootfs各复制一份,分成A区和B区。当前运行在A区,升级就写到B区。升级完成后切换启动标志,下次从B区启动。如果B区启动失败,自动回退到A区。

说白了,就是「双保险」。我参与过一个智能网关项目,客户要求升级成功率99.99%以上。没有A/B系统根本做不到——传统升级方式,一旦升级失败就得返厂刷机。

三、A/B系统的分区布局

来看一个实际的Flash分区表,以256MB NAND Flash为例:

分区编号 | 起始地址  | 大小     | 用途
---------|-----------|----------|-------------------
mtd0     | 0x000000  | 2MB      | Bootloader (A)
mtd1     | 0x200000  | 2MB      | Bootloader (B)
mtd2     | 0x400000  | 16MB     | Kernel (A)
mtd3     | 0x1400000 | 16MB     | Kernel (B)
mtd4     | 0x2400000 | 128MB    | Rootfs (A)
mtd5     | 0xA400000 | 128MB    | Rootfs (B)
mtd6     | 0x12400000| 剩余     | Data (共享)

注意看,Data分区是A/B共享的。为什么?因为用户数据不能丢,不管从A切到B还是B切到A,配置和日志都得在。我曾经踩过一个坑——把Data也做了双备份,结果用户改了配置,升级后配置丢了,被客户投诉。从那以后,Data分区我坚决不做双备份。

四、A/B系统的切换流程

整个切换逻辑其实不复杂,我画个流程图你就明白了:

A/B系统升级切换流程图 系统启动 读取Bootloader环境变量 判断当前启动槽位(A/B) 升级标志 是否有效? 是 切换到新槽位 更新环境变量 否 保持当前槽位 不做切换 从选定槽位启动Kernel 加载对应的DTB和Rootfs 启动成功? 是 正常运行 清除升级标志 否 回退到旧槽位 切换环境变量

流程其实就三步:

  1. 升级阶段:系统运行在A区,把新版本写到B区。写完后设置一个「升级标志」到Bootloader环境变量里。
  2. 切换阶段:重启后Bootloader检查这个标志。如果有,就尝试从B区启动。
  3. 回退阶段:如果B区启动失败(比如内核panic、应用起不来),Bootloader自动切回A区,并清除升级标志。

我的经验:回退机制一定要做「看门狗+超时检测」。我在一个路由器项目里,B区内核能起来但网络驱动挂了,用户根本连不上。后来加了应用层心跳检测——如果5分钟内应用没上报健康状态,系统自动回退。这才把问题堵住。

五、分区大小怎么定?

这个问题没有标准答案,但我给你一个参考公式:

Kernel分区大小 = 内核镜像大小 × 2 + 1MB余量
Rootfs分区大小 = 根文件系统大小 × 2 + 20%余量
Data分区大小 = 剩余空间 - 10%预留(坏块处理)

为什么要留余量?因为产品生命周期内,固件只会越来越大。我见过一个项目,最初Kernel只有6MB,三年后迭代到15MB,原来的8MB分区根本塞不下。最后不得不改分区表,所有设备返厂重刷——这个教训太深刻了。

警告:NAND Flash有坏块问题。分区规划时一定要考虑坏块管理。我建议每个分区末尾留出5%~10%的备用块。UBIFS和YAFFS2这类文件系统自带坏块管理,但裸分区(比如Kernel分区)需要你自己处理。

六、实际项目中的分区策略选择

不是所有设备都需要完整的A/B系统。我根据项目经验,给你三个方案:

方案 适用场景 存储开销 可靠性
完整A/B系统 工业设备、医疗、车机 2倍空间 极高
仅Rootfs双备份 消费电子、IoT网关 1.5倍空间 高
单分区+恢复模式 低成本设备、传感器 1倍空间 中等

我个人最推荐第二种——仅Rootfs双备份。Kernel和Bootloader相对稳定,升级频率低,单独做双备份性价比不高。把省下来的空间给Data分区,用户体验更好。

好了,分区规划这块就聊到这儿。记住一句话:分区规划不是技术问题,是取舍问题。空间、可靠性、成本,你总得牺牲一个。想清楚你的产品定位,再做决定。

核心要点回顾:

  • 四大分区各司其职,Bootloader只读、Kernel留余量、Rootfs用只读文件系统、Data共享
  • A/B系统的本质是「双保险」,升级失败自动回退
  • 回退机制必须配合看门狗和心跳检测
  • 分区大小要留余量,NAND Flash要处理坏块
  • 根据产品定位选择A/B方案,不要盲目追求全备份

可可精品资料库