4、U-Boot与分区表:U-Boot环境变量,mtdparts命令,如何让U-Boot识别并加载分区

好,我们进入第四章。这一章很关键,它连接了硬件和系统。

你想想看,U-Boot 是系统上电后第一个跑起来的软件。它得知道 Flash 里哪些区域是干嘛的。谁告诉它的?分区表。

分区表怎么传给内核?通过 U-Boot 的环境变量和 mtdparts 命令。说白了,这就是一套约定。U-Boot 按约定把分区信息写好,内核启动时按约定读走。

我在项目里见过太多次因为分区表没对齐,导致 OTA 升级写错位置,整片 Flash 变砖的情况。嗯,这一章我们就把这个坑填上。

4.1 U-Boot 环境变量:分区信息的“快递员”

U-Boot 环境变量存在 Flash 的某个固定区域里。它是一组 key-value 对。内核启动时,U-Boot 会把其中一部分变量传给内核命令行。

跟分区相关的核心变量就两个:mtdids 和 mtdparts。

环境变量 作用 示例值
mtdids 定义 MTD 设备 ID 与内核设备名的映射 nand0=nand0
mtdparts 定义分区名称、偏移、大小 mtdparts=nand0:1m(u-boot),4m(kernel),-(rootfs)

核心逻辑:U-Boot 通过 mtdparts 变量生成分区表。内核启动时,如果命令行里有 mtdparts= 参数,内核就会按照这个描述创建 MTD 分区设备。

我个人习惯在 U-Boot 的 include/configs/ 头文件里直接写死默认值。这样每次烧录 U-Boot 时,分区表就跟着固化了。省心。

4.2 mtdparts 命令:手动操作分区表

U-Boot 里有个 mtdparts 命令。它用来查看和修改当前的分区布局。

常用的操作就这几个:

# 查看当前分区表
mtdparts

# 显示某个 MTD 设备的详细分区
mtdparts nand0

# 设置新的分区表(临时生效)
setenv mtdparts 'mtdparts=nand0:2m(boot),6m(kernel),-(rootfs)'
saveenv

# 重置为默认分区表
mtdparts default

注意,setenv 只是改了环境变量。你得 saveenv 才能存到 Flash 里。我刚开始做的时候经常忘 saveenv,结果重启后分区表又变回去了。排查了半天……

避坑指南:我曾经在量产阶段发现一批板子启动不了。查到最后,是 U-Boot 环境变量区被擦坏了,mtdparts 读出来全是乱码。从那以后,我强制要求:mtdparts 必须在 U-Boot 代码里硬编码一份默认值,不能只依赖环境变量。

4.3 分区描述语法:mtdparts 格式详解

格式看起来有点绕,其实拆开看很简单。

mtdparts=<mtd-id>:<size>[@<offset>](<name>)[,...]
  • <mtd-id>:对应 mtdids 里定义的设备名。
  • <size>:分区大小。支持 k、m、g 后缀。用 - 表示剩余全部空间。
  • @<offset>:可选。分区起始偏移。不写的话,自动接上一个分区末尾。
  • <name>:分区名字。内核里会显示为 /dev/mtdblockX,名字在 /proc/mtd 里能看到。

举个例子:

setenv mtdparts 'mtdparts=nand0:1m@0x0(u-boot),4m@0x100000(kernel),-(rootfs)'

这个配置的意思是:

  • 从 0x0 开始,1MB 给 u-boot
  • 从 0x100000 开始,4MB 给 kernel
  • 剩下的全部空间给 rootfs

经验之谈:我建议分区偏移和大小都写清楚,不要依赖自动拼接。为什么?因为如果你中间插了一个分区,自动偏移会全乱掉。显式写 @offset 虽然啰嗦,但可读性和可维护性都好很多。

4.4 让 U-Boot 识别并加载分区:完整流程

U-Boot 启动时,分区加载流程是这样的:

  1. 初始化 MTD 子系统:U-Boot 检测到 NAND/NOR Flash,注册为 MTD 设备。
  2. 读取环境变量:从 Flash 的环境变量区加载 mtdids 和 mtdparts。
  3. 解析分区表:U-Boot 内部调用 mtdparts_init(),把字符串解析成链表结构。
  4. 传递到内核:U-Boot 把 mtdparts 拼接到 bootargs 里,传给内核。
  5. 内核创建 MTD 分区设备:内核启动时,解析 bootargs 中的 mtdparts,调用 mtd_part_do_parse(),生成 /dev/mtdblock0、/dev/mtdblock1 等设备。

下面这张图把整个流程串起来了:

U-Boot 分区识别与传递流程 Flash 硬件初始化 读取环境变量 mtdparts_init() 解析 拼入 bootargs 传内核 内核创建 MTD 分区设备 mtdids + mtdparts 从 Flash 环境变量区加载 bootargs 示例: mtdparts=nand0:... root=/dev/mtdblock2

4.5 内核侧:如何确认分区加载成功

系统启动后,进到 Linux 里,用这几个命令确认分区表是否生效:

# 查看 MTD 设备列表
cat /proc/mtd

# 查看分区名称和大小
cat /proc/partitions

# 查看内核启动日志中关于 MTD 的部分
dmesg | grep mtd

输出大概长这样:

dev:    size   erasesize  name
mtd0: 00100000 00020000 "u-boot"
mtd1: 00400000 00020000 "kernel"
mtd2: 0fb00000 00020000 "rootfs"

看到 name 列跟你设的一样,就说明分区表传对了。

检查要点:

  • size 是否跟 mtdparts 里定义的一致?
  • erasesize 是否跟 Flash 的物理擦除块大小匹配?
  • 分区顺序对不对?尤其是 rootfs 是不是在最后?

4.6 常见问题与排查思路

现象 原因 解决办法
内核启动时找不到 rootfs mtdparts 没传进内核,或分区名不匹配 检查 bootargs 里是否有 mtdparts=,检查 root= 参数指向的分区名
/proc/mtd 为空 内核没开启 MTD 分区支持 确认内核配置 CONFIG_MTD_PARTITIONS=y
分区大小不对 mtdparts 里 size 写错了,或偏移没对齐 用 mtdparts 命令重新查看,确认偏移和大小
U-Boot 启动时卡在 MTD 初始化 环境变量区损坏,或 mtdids 配置错误 进入 U-Boot 命令行,用 mtdparts default 重置

重要提醒:分区表一旦定下来,尽量不要改。尤其是 OTA 升级方案依赖固定偏移。我见过一个团队,产品发布后改了分区大小,结果 OTA 脚本里写死的偏移全废了,升级包写到了 kernel 区域……嗯,后果你懂的。

4.7 实战建议:分区表设计原则

最后,分享几条我在项目中沉淀下来的原则:

  • 分区大小用 1MB 对齐:NAND Flash 的擦除块通常是 128KB 或 256KB。1MB 对齐能避免跨块问题。
  • 给 U-Boot 环境变量单独分区:不要跟 U-Boot 代码放一起。环境变量频繁擦写,容易坏块。
  • 预留一个“恢复分区”:OTA 升级时,万一写坏了,还能从恢复分区启动。这个我后面会细讲。
  • 分区名字要有意义:别用 part1、part2。用 u-boot、kernel、rootfs、recovery。三个月后你自己回头看,也能一眼看懂。