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 的完整解析流程和各个语法元素的关系:

env.txt 解析流程与语法结构 env.txt 解析流程与语法结构 env.txt 原始内容 预处理阶段 #if / #elif / #else / #endif 条件判断 变量展开阶段 ${var} 引用替换为实际值 解析执行阶段 键值对解析 → 环境变量生效 U-Boot 环境变量表 ⚠ 注意事项 • 续行符后不能有空格 • 条件表达式实时计算 • 变量引用支持嵌套 📝 语法要素 • 键值对:var=value • 续行符:\ • 引用:${var}

4.6 实际项目中的最佳实践

最后分享几个我在项目中积累的经验:

实践 说明 推荐度
常量前置 把地址、分区名等常量放在文件头部 ⭐⭐⭐⭐⭐
条件分支 用 #if 区分不同硬件版本或启动介质 ⭐⭐⭐⭐
避免硬编码 所有路径、地址都用变量引用 ⭐⭐⭐⭐⭐
注释清晰 用 # 写注释,说明每个变量的用途 ⭐⭐⭐
版本标记 在文件头部加版本号和修改日期 ⭐⭐⭐⭐
⚠ 避坑指南:我曾经在项目里遇到一个诡异的问题——条件编译死活不生效。查了半天发现是变量名拼写错了,${boot_media} 写成了 ${boot_medi}。U-Boot 不会报错,只会把未定义的变量当成空字符串,条件自然永远不成立。所以,变量名一定要核对三遍。

env.txt 的语法虽然简单,但用好变量引用和条件编译,能让你的配置管理效率翻倍。说白了,这就是一套「可编程的启动配置」,值得花时间吃透。