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 启动时,分区加载流程是这样的:
- 初始化 MTD 子系统:U-Boot 检测到 NAND/NOR Flash,注册为 MTD 设备。
- 读取环境变量:从 Flash 的环境变量区加载
mtdids和mtdparts。 - 解析分区表:U-Boot 内部调用
mtdparts_init(),把字符串解析成链表结构。 - 传递到内核:U-Boot 把
mtdparts拼接到bootargs里,传给内核。 - 内核创建 MTD 分区设备:内核启动时,解析
bootargs中的mtdparts,调用mtd_part_do_parse(),生成/dev/mtdblock0、/dev/mtdblock1等设备。
下面这张图把整个流程串起来了:
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。三个月后你自己回头看,也能一眼看懂。