4. 环境变量文件结构:env.txt 的语法、变量引用与条件编译
环境变量文件,说白了就是 U-Boot 启动时的「配置文件」。我个人习惯把它看作一块可编程的存储区——你写进去什么,系统启动时就加载什么。今天咱们就把它掰开揉碎了讲清楚。
4.1 env.txt 的基本语法
env.txt 的语法其实很简单,核心就两条规则:
- 键值对格式:
变量名=值 - 每行一个变量,空行会被忽略
举个例子:
bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait
bootcmd=mmc dev 0; fatload mmc 0:1 0x82000000 kernel.img; bootm 0x82000000
ipaddr=192.168.1.100
serverip=192.168.1.1
嗯,这里要注意一点:值里不能有换行。如果你需要多行内容,可以用 \ 续行符:
bootcmd=mmc dev 0; \
fatload mmc 0:1 0x82000000 kernel.img; \
bootm 0x82000000
⚠ 常见坑:续行符后面不能有空格或注释,否则解析会出错。我曾经在项目里因为这个查了一下午,最后发现是续行符后多了个空格。
4.2 变量引用:${var} 的用法
env.txt 支持变量引用,语法是 ${变量名}。这个功能非常实用,能让你写出更简洁、更易维护的配置。
看个实际例子:
kernel_addr=0x82000000
fdt_addr=0x88000000
load_kernel=mmc dev 0; fatload mmc 0:1 ${kernel_addr} kernel.img
load_fdt=mmc dev 0; fatload mmc 0:1 ${fdt_addr} device_tree.dtb
bootcmd=run load_kernel; run load_fdt; bootm ${kernel_addr} - ${fdt_addr}
这样做的好处很明显:
- 地址统一管理:改一个地方,所有引用都生效
- 可读性提升:
${kernel_addr}比裸的0x82000000好理解得多 - 复用性强:可以用
run命令执行组合变量
💡 我的习惯:我会把地址、分区名、文件名这些「常量」单独定义在文件头部,后面所有逻辑都引用它们。这样换板子时只需要改头部几行,不用满文件找。
4.3 条件编译:#if / #endif 的应用
这是 env.txt 里最容易被忽略、但实际非常强大的功能。U-Boot 的解析器支持预处理指令,让你可以根据条件决定哪些变量生效。
基本语法:
#if 条件表达式
变量1=值1
变量2=值2
#endif
条件表达式可以是:
- 数值比较:
${var} == 1、${var} != 0 - 字符串比较:
"${var}" == "mmc" - 逻辑运算:
&&(与)、||(或)
来看一个实际场景——同一份 env.txt 同时支持 NAND 和 MMC 启动:
# 定义启动介质类型
boot_media=mmc
#if "${boot_media}" == "nand"
bootargs=console=ttyS0,115200 root=/dev/mtdblock0 rootfstype=jffs2
load_kernel=nand read 0x82000000 kernel_part
load_fdt=nand read 0x88000000 fdt_part
#endif
#if "${boot_media}" == "mmc"
bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait
load_kernel=mmc dev 0; fatload mmc 0:1 0x82000000 kernel.img
load_fdt=mmc dev 0; fatload mmc 0:1 0x88000000 device_tree.dtb
#endif
bootcmd=run load_kernel; run load_fdt; bootm 0x82000000 - 0x88000000
🔑 关键点:条件编译是在 U-Boot 解析 env.txt 时实时计算的,不是编译时预处理。所以你可以通过修改某个变量的值,动态切换整个配置集。
4.4 条件编译的高级用法
除了简单的 #if / #endif,还支持 #else 和 #elif:
#if "${boot_media}" == "nand"
root_dev=/dev/mtdblock0
#elif "${boot_media}" == "mmc"
root_dev=/dev/mmcblk0p2
#else
root_dev=/dev/sda1
#endif
bootargs=console=ttyS0,115200 root=${root_dev} rootwait
我曾在某个项目里用这个特性做了一套「调试版 vs 发布版」的配置:
debug_mode=1
#if ${debug_mode} == 1
bootargs=${bootargs} debug earlyprintk
bootdelay=10
#else
bootdelay=2
silent=1
#endif
调试时把 debug_mode 设成 1,发布时改成 0,一套文件搞定两种场景。
4.5 整体结构图
下面这张图展示了 env.txt 的完整解析流程和各个语法元素的关系:
4.6 实际项目中的最佳实践
最后分享几个我在项目中积累的经验:
| 实践 | 说明 | 推荐度 |
|---|---|---|
| 常量前置 | 把地址、分区名等常量放在文件头部 | ⭐⭐⭐⭐⭐ |
| 条件分支 | 用 #if 区分不同硬件版本或启动介质 | ⭐⭐⭐⭐ |
| 避免硬编码 | 所有路径、地址都用变量引用 | ⭐⭐⭐⭐⭐ |
| 注释清晰 | 用 # 写注释,说明每个变量的用途 | ⭐⭐⭐ |
| 版本标记 | 在文件头部加版本号和修改日期 | ⭐⭐⭐⭐ |
⚠ 避坑指南:我曾经在项目里遇到一个诡异的问题——条件编译死活不生效。查了半天发现是变量名拼写错了,
${boot_media} 写成了 ${boot_medi}。U-Boot 不会报错,只会把未定义的变量当成空字符串,条件自然永远不成立。所以,变量名一定要核对三遍。
env.txt 的语法虽然简单,但用好变量引用和条件编译,能让你的配置管理效率翻倍。说白了,这就是一套「可编程的启动配置」,值得花时间吃透。