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。这种低级错误,往往最浪费时间。
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 本章知识体系
下面这张图,帮你把这一讲的内容串起来:
3.5 本章小结
这一讲的内容,说白了就是三件事:
- 导入导出 — 用
env export/import实现批量迁移,别一块板一块板地敲。 - 脚本化 — 用
run、if、for把启动流程写成变量,灵活又可控。 - 吃透常用变量 —
bootargs决定内核怎么跑,bootcmd决定启动顺序,serverip/ipaddr决定网络通不通。
嗯,环境变量这块,到这儿就讲完了。你把这些东西练熟了,U-Boot 命令行操作的基本功就算打扎实了。