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布局中的位置:

典型Flash分区布局(含env分区) SPL 0x00000000 - 0x0001FFFF U-Boot主程序 0x00020000 - 0x0009FFFF env分区 0x000A0000 - 0x000A1FFF 设备树(DTB) 0x000A2000 - 0x000BFFFF 内核(Kernel) 低地址 → 高地址 env分区关键参数 CONFIG_ENV_OFFSET = 0x000A0000 CONFIG_ENV_SIZE = 0x2000 (8KB) CONFIG_ENV_SECT_SIZE = 0x10000 ⚠ 常见问题与避坑 1. env分区不能跨擦除块边界 2. 大小必须是擦除块的整数倍? → 不一定,但建议对齐 3. NAND Flash要预留冗余备份 4. 与U-Boot主程序保持距离 5. 避免放在坏块区域 推荐配置(MMC为例) CONFIG_ENV_IS_IN_MMC CONFIG_SYS_MMC_ENV_DEV = 0

从这张图可以看出,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命令修改环境变量。流程是这样的:

  1. setenv key value:修改内存中的环境变量哈希表
  2. saveenv:将哈希表序列化,写入Flash的env分区
  3. 下次启动时,U-Boot从env分区读取,加载到哈希表

这里有个细节:saveenv会先擦除整个env分区,再写入新数据。如果写入过程中断电,env分区就空了。所以,关键操作前最好先备份环境变量。我习惯用env export命令导出到内存,再md.b保存到文件。

警告:千万不要在saveenv过程中断电或复位!我有个同事就因为这个,把一块板子的env分区搞成了全0xFF,结果启动时U-Boot找不到任何环境变量,只能用默认值。虽然能启动,但之前配置的网络参数全丢了。

3.8 小结

环境变量分区,说白了就是U-Boot的"小本本"。它记录了你所有的配置参数,是板子正常启动的关键。设计时要注意:

  • 选对存储介质(NOR、NAND、MMC等)
  • 算好偏移和大小(别和别的分区打架)
  • 配好默认环境变量(第一次启动全靠它)
  • 考虑冗余备份(NAND用户必看)

这些搞定了,env分区这块基本就稳了。下一节我们会聊到分区表的具体配置方法,到时候会用到今天讲的环境变量知识。

可可精品资料库