3、U-Boot环境变量与分区:env分区的作用、环境变量存储方式、默认环境变量配置
环境变量这东西,说大不大,说小不小。我刚开始做U-Boot移植那会儿,总觉得环境变量就是个存参数的地方,没啥技术含量。直到有一次,板子启动到一半卡死了,查了三天才发现是环境变量分区被写坏了。嗯,从那以后,我再也不敢小看这个"小东西"了。
说白了,环境变量就是U-Boot的"记忆"。你设置的IP地址、启动参数、文件系统路径,全存在这里。没有它,每次重启都得重新敲命令,那还玩什么?
3.1 env分区到底干啥用?
env分区,全称是environment partition。它的任务就一个:持久化保存环境变量。
你想想看,U-Boot本身是个bootloader,代码在Flash里跑。但环境变量是动态的,用户随时可能改。改完以后,下次重启还得用。所以必须找个地方存起来。这就是env分区的由来。
核心作用总结:
- 存储用户自定义的启动参数(bootargs、bootcmd等)
- 保存网络配置(ipaddr、serverip、gatewayip)
- 记录设备标识(serial#、ethaddr)
- 存放文件系统路径(root、kernel_addr等)
- 支持运行时修改并持久化(saveenv命令)
我在项目中遇到过一种情况:某款NAND Flash的坏块管理做得不好,env分区刚好落在坏块上,结果每次saveenv都失败。后来我强制把env分区挪到了好块区域,问题才解决。所以,env分区的物理位置一定要避开坏块,这个坑我替你们踩过了。
3.2 环境变量的存储方式
U-Boot支持好几种存储方式。具体用哪种,取决于你的硬件平台。我列个表,大家一目了然:
| 存储介质 | 配置宏 | 适用场景 | 注意事项 |
|---|---|---|---|
| NOR Flash | CONFIG_ENV_IS_IN_FLASH | NOR启动的板子 | 注意擦除块大小对齐 |
| NAND Flash | CONFIG_ENV_IS_IN_NAND | NAND启动的板子 | 必须处理坏块 |
| MMC/SD卡 | CONFIG_ENV_IS_IN_MMC | 树莓派、Rockchip等 | 指定分区号或偏移 |
| SPI Flash | CONFIG_ENV_IS_IN_SPI_FLASH | 小容量存储 | 速度慢但可靠 |
| EEPROM | CONFIG_ENV_IS_IN_EEPROM | 工业控制板 | 容量有限(通常几KB) |
| FAT文件系统 | CONFIG_ENV_IS_IN_FAT | 与内核共用分区 | 需要文件系统驱动 |
| UBI卷 | CONFIG_ENV_IS_IN_UBI | NAND + UBIFS | 需要UBI支持 |
我个人习惯用MMC方式,因为现在大多数板子都用SD卡或eMMC启动,方便调试。但要注意,MMC方式下,env分区不能和文件系统分区重叠,否则saveenv会把文件系统搞坏。我曾经手滑过一次,后果嘛...你懂的。
3.3 环境变量的存储格式
环境变量在Flash里是怎么存的?其实很简单,就是key=value\0的连续字符串。最后用两个\0结尾。
举个例子:
bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2\0
bootcmd=mmc read 0x42000000 0x800 0x4000; bootm 0x42000000\0
ipaddr=192.168.1.100\0
serverip=192.168.1.1\0
\0\0
每个变量后面跟一个\0,最后一个变量后面跟两个\0表示结束。U-Boot启动时,会从指定位置读取这段数据,然后解析成哈希表。
小技巧:如果你在调试时想手动查看环境变量的原始数据,可以用md.b命令读取env分区的起始地址。比如:md.b 0x140000 0x2000。这样能看到最原始的存储内容,方便排查问题。
3.4 默认环境变量配置
默认环境变量,就是板子第一次启动时用的那套参数。它们定义在头文件里,通常是include/configs/你的板子.h。
配置方式有两种:
- 直接定义法:用
#define CONFIG_EXTRA_ENV_SETTINGS宏,把所有变量写在一起。 - 分段定义法:用多个
#define宏,最后用CONFIG_EXTRA_ENV_SETTINGS汇总。
我一般用分段定义法,清晰好维护。看个例子:
/* 网络相关 */
#define CONFIG_ENV_IP_SETTINGS \
"ipaddr=192.168.1.100\0" \
"serverip=192.168.1.1\0" \
"gatewayip=192.168.1.1\0" \
"netmask=255.255.255.0\0"
/* 启动相关 */
#define CONFIG_ENV_BOOT_SETTINGS \
"bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rw\0" \
"bootcmd=mmc read 0x42000000 0x800 0x4000; bootm 0x42000000\0"
/* 汇总 */
#define CONFIG_EXTRA_ENV_SETTINGS \
CONFIG_ENV_IP_SETTINGS \
CONFIG_ENV_BOOT_SETTINGS \
"loadaddr=0x42000000\0" \
"fdt_addr=0x43000000\0"
注意:每个字符串末尾必须加\0,否则U-Boot解析时会出错。我见过有人漏了最后一个\0,结果环境变量全乱套了。还有,默认环境变量的总大小不能超过env分区的容量,否则saveenv会写不进去。
3.5 env分区的布局设计
env分区在Flash里的位置,由几个关键宏决定:
CONFIG_ENV_OFFSET:环境变量在Flash中的偏移地址CONFIG_ENV_SIZE:环境变量分区的大小(通常0x2000~0x10000)CONFIG_ENV_SECT_SIZE:Flash的擦除块大小(NOR通常是0x10000,NAND是0x20000)
我画了一张图,帮大家理解env分区在整个Flash布局中的位置:
从这张图可以看出,env分区通常放在U-Boot主程序后面,设备树前面。为什么这么放?因为U-Boot主程序大小是固定的,env分区紧挨着它,方便管理。但要注意,env分区不能和U-Boot主程序重叠,否则启动时U-Boot把自己覆盖了,那就真成"自杀"了。
3.6 环境变量的冗余备份
对于NAND Flash这类容易出错的介质,U-Boot支持冗余环境变量。说白了就是存两份,一份坏了用另一份。
配置方式:
#define CONFIG_ENV_OFFSET 0x000A0000
#define CONFIG_ENV_OFFSET_REDUND 0x000A2000
#define CONFIG_ENV_SIZE 0x2000
这样,U-Boot启动时会先读主env分区,如果CRC校验失败,就自动切换到冗余分区。我建议所有用NAND Flash的项目都开启这个功能,因为NAND的比特翻转太常见了。
我的经验:冗余分区的大小和主分区一样就行。但两个分区之间最好留点间隔,防止一个坏块影响到两个分区。我曾经见过一个案例,主分区和冗余分区紧挨着,结果一个坏块把两个分区都毁了...嗯,设计时多留点余量总没错。
3.7 环境变量的运行时修改
U-Boot启动后,你可以用setenv和saveenv命令修改环境变量。流程是这样的:
setenv key value:修改内存中的环境变量哈希表saveenv:将哈希表序列化,写入Flash的env分区- 下次启动时,U-Boot从env分区读取,加载到哈希表
这里有个细节:saveenv会先擦除整个env分区,再写入新数据。如果写入过程中断电,env分区就空了。所以,关键操作前最好先备份环境变量。我习惯用env export命令导出到内存,再md.b保存到文件。
警告:千万不要在saveenv过程中断电或复位!我有个同事就因为这个,把一块板子的env分区搞成了全0xFF,结果启动时U-Boot找不到任何环境变量,只能用默认值。虽然能启动,但之前配置的网络参数全丢了。
3.8 小结
环境变量分区,说白了就是U-Boot的"小本本"。它记录了你所有的配置参数,是板子正常启动的关键。设计时要注意:
- 选对存储介质(NOR、NAND、MMC等)
- 算好偏移和大小(别和别的分区打架)
- 配好默认环境变量(第一次启动全靠它)
- 考虑冗余备份(NAND用户必看)
这些搞定了,env分区这块基本就稳了。下一节我们会聊到分区表的具体配置方法,到时候会用到今天讲的环境变量知识。
可可精品资料库