3、环境变量管理(下):导入导出、脚本应用与常用变量解读

上一讲我们聊了环境变量的增删改查和保存机制。这一讲,咱们把剩下的硬骨头啃完——导入导出、脚本里怎么用,以及那几个你天天见、但未必真吃透的常用变量。

说实话,环境变量这块内容,很多工程师用了一年半载,还是停留在「改个bootargs、设个serverip」的层面。但真正遇到启动失败、网络不通、脚本跑飞的时候,才回头翻文档。我希望你看完这一讲,能少走这些弯路。

3.1 环境变量的导入与导出

先问一个问题:你手上有三块开发板,配置几乎一样,难道要一块一块地敲 setenv?

当然不用。U-Boot 提供了导入导出机制,说白了就是把环境变量当成一个文本文件,拷来拷去就行。

3.1.1 导出环境变量到主机

导出用 saveenv 吗?不对。saveenv 是存到存储设备上。导出到主机,用的是 printenv 配合串口或网络。

最原始但可靠的方法:

# 在 U-Boot 命令行执行
printenv

# 然后在终端软件(如 SecureCRT、Minicom)里
# 把输出全部复制,保存为 .txt 文件

但这样有个问题——你拷下来的东西带了很多格式信息,下次导入时 U-Boot 不一定认。我个人习惯用 env export 命令:

# 导出到内存地址 0x20000000
env export -t 0x20000000

# 然后用 tftp 或 loadb 传到主机
tftp 0x20000000 env.txt 80000

这里 -t 表示文本格式,可读性好。还有 -b(二进制)和 -s(带校验),但日常调试用 -t 就够了。

小技巧: 导出前先执行 env default -a 恢复出厂设置,再按需修改。这样导出的文件干净,不会把调试时的脏数据也带进去。

3.1.2 导入环境变量到板子

导入用 env import。假设你已经在内存 0x20000000 放好了环境变量文本:

# 从内存导入
env import -t 0x20000000 $filesize

# 验证一下
printenv

这里 $filesize 是上一步 tftp 或 loadb 自动设置的变量,表示文件大小。如果你手动指定大小,一定要算对,否则导入会截断或报错。

我曾经在一次产线烧录中吃过这个亏——脚本里写死了 0x10000,结果某次环境变量文件超过了 64KB,导入后后半截全丢了,板子启动到一半就挂。从那以后,我再也不敢手动写大小,一律用 $filesize。

注意: 导入的环境变量会覆盖当前所有变量,不是追加。如果你只想更新某几个变量,别用 env import,老老实实 setenv 一个个改。

3.2 环境变量在脚本中的应用

U-Boot 的脚本能力,比你想象的要强。它虽然不像 Bash 那么灵活,但做条件判断、循环、函数调用,完全够用。

3.2.1 基本脚本语法

先看一个最简单的脚本:

setenv myboot 'echo "Loading kernel..."; fatload mmc 0:1 0x42000000 uImage; bootm 0x42000000'
run myboot

这里 myboot 是一个变量,里面存了多条命令,用分号隔开。run 命令负责执行它。

你可能会问:为什么不用 ; run myboot 直接写在 bootcmd 里?因为拆成独立变量,调试时只改一个地方,不影响其他逻辑。

3.2.2 条件判断

U-Boot 的条件判断用 if、then、else、fi:

setenv checkboot 'if test -e mmc 0:1 uImage; then echo "Found uImage on MMC"; else echo "uImage not found, trying tftp..."; tftp 0x42000000 uImage; fi'
run checkboot

test 命令支持文件存在检查、字符串比较、数值比较。常用的:

  • test -e mmc 0:1 uImage — 检查文件是否存在
  • test "${bootdev}" = "mmc" — 字符串相等
  • test ${loadaddr} -gt 0x1000000 — 数值大于
避坑指南: 字符串比较时,变量最好用双引号包起来。我曾经遇到过 bootdev 变量为空,直接写 test $bootdev = mmc 导致语法错误,板子卡死在脚本里。加引号 "${bootdev}" 就安全了。

3.2.3 循环与迭代

U-Boot 支持 for 循环:

setenv testnet 'for device in eth0 eth1; do echo "Testing $device..."; ping 192.168.1.1; done'
run testnet

也支持 while 循环,但用得少。我个人觉得,脚本里循环别写太复杂,否则调试起来很痛苦——U-Boot 没有断点,只能靠 echo 打日志。

3.2.4 脚本的存储与自动执行

写好的脚本怎么让它开机自动跑?答案是 bootcmd:

setenv bootcmd 'run checkboot; run myboot'
saveenv

这样每次上电,U-Boot 先检查文件是否存在,再决定从 MMC 启动还是 tftp 下载。

你想想看,如果产品要支持两种启动介质(比如调试时用网络,量产时用 SD 卡),用脚本做自动切换,比改代码重新编译 U-Boot 省事多了。

3.3 常用环境变量深度解读

下面这几个变量,是 U-Boot 里最核心的。我按重要程度排个序:

变量名 作用 典型值示例
bootargs 传递给 Linux 内核的启动参数 console=ttyS0,115200 root=/dev/mmcblk0p2 rw
bootcmd 上电后自动执行的命令 run loadkernel; run loaddtb; bootm
serverip TFTP/NFS 服务器的 IP 地址 192.168.1.100
ipaddr 开发板自身的 IP 地址 192.168.1.50
loadaddr 加载内核/设备树到内存的地址 0x42000000
fdt_addr 设备树在内存中的地址 0x43000000
bootfile TFTP 下载的文件名 uImage

3.3.1 bootargs — 内核的「遗嘱」

bootargs 是 U-Boot 传给内核的参数字符串。内核启动时,会解析这个字符串来配置控制台、根文件系统位置、内存大小等。

常见的 bootargs 片段:

# 串口控制台
console=ttyS0,115200

# 根文件系统在 MMC 分区
root=/dev/mmcblk0p2 rw rootwait

# 根文件系统在 NFS(调试用)
root=/dev/nfs nfsroot=192.168.1.100:/nfsroot ip=192.168.1.50

# 指定内存大小
mem=256M

# 关闭内核 logo
logo.nologo

我记得有一次,板子启动后串口没输出,查了半天发现是 console=ttyS0 写成了 ttyS1。这种低级错误,往往最浪费时间。

调试建议: 如果你不确定 bootargs 对不对,可以在 U-Boot 里先 printenv bootargs 确认,然后手动 bootm 启动。不要每次都 saveenv + 重启,太慢了。

3.3.2 bootcmd — 启动流程的总指挥

bootcmd 是 U-Boot 启动流程的入口。默认情况下,U-Boot 在倒计时结束后执行 bootcmd 里的命令。

一个典型的 bootcmd 链:

setenv loadkernel 'fatload mmc 0:1 ${loadaddr} uImage'
setenv loaddtb   'fatload mmc 0:1 ${fdt_addr} board.dtb'
setenv bootargs  'console=ttyS0,115200 root=/dev/mmcblk0p2 rw'
setenv bootcmd   'run loadkernel; run loaddtb; bootm ${loadaddr} - ${fdt_addr}'
saveenv

这里我把加载内核、加载设备树、设置参数拆成了三个独立变量。好处是:如果某天要换内核文件名,只改 loadkernel 就行,不用动整个 bootcmd。

3.3.3 serverip 与 ipaddr — 网络启动的命脉

这两个变量配合使用,缺一不可。U-Boot 的 TFTP、NFS、DHCP 都依赖它们。

设置示例:

setenv ipaddr 192.168.1.50
setenv serverip 192.168.1.100
setenv netmask 255.255.255.0
setenv gatewayip 192.168.1.1

然后就可以用 tftp 下载文件了:

tftp ${loadaddr} uImage

这里有个坑:serverip 和 ipaddr 必须在同一个网段,否则 tftp 会超时。我见过有人把 serverip 设成 192.168.1.100,板子 IP 却是 10.0.0.50,然后死活连不上——这不是 U-Boot 的错,是网络基础没打牢。

3.3.4 其他常用变量

  • ethaddr:MAC 地址。每个板子应该唯一,量产时要注意烧录不同的值。
  • bootdelay:倒计时秒数。设为 -1 或 0 可以跳过等待,直接启动。
  • baudrate:串口波特率。改了它,记得同时改终端软件的设置,否则乱码。
  • stderr/stdin/stdout:标准输入输出设备。一般设为 serial,但也可以改成 vga 或 lcd。

3.4 本章知识体系

下面这张图,帮你把这一讲的内容串起来:

环境变量管理(下)知识体系 环境变量管理(下)知识体系 导入与导出 脚本应用 常用变量解读 env export env import run 命令 if/then/else bootargs bootcmd console / root / mem loadkernel → loaddtb → bootm serverip / ipaddr / netmask 核心思路 导出备份 → 脚本自动化 → 关键变量精准配置,三步搞定环境变量管理

3.5 本章小结

这一讲的内容,说白了就是三件事:

  1. 导入导出 — 用 env export/import 实现批量迁移,别一块板一块板地敲。
  2. 脚本化 — 用 run、if、for 把启动流程写成变量,灵活又可控。
  3. 吃透常用变量 — bootargs 决定内核怎么跑,bootcmd 决定启动顺序,serverip/ipaddr 决定网络通不通。

嗯,环境变量这块,到这儿就讲完了。你把这些东西练熟了,U-Boot 命令行操作的基本功就算打扎实了。