3、U-Boot深度解析:U-Boot的编译、配置与常用命令,环境变量与启动脚本
好,我们直接进入正题。U-Boot 这东西,说白了就是嵌入式系统的“第一道门”。你想想看,CPU 上电后,第一个跑起来的软件就是它。它得把硬件初始化好,然后把内核加载进内存,最后跳转过去执行。今天我就带你把它彻底搞明白。
3.1 U-Boot 的编译与配置
先聊聊编译。很多新手拿到 U-Boot 源码,第一反应就是直接 make。嗯,这肯定不行。U-Boot 支持几百种开发板,你得先告诉它“我是谁”。
我个人习惯,拿到一个板子,先看 configs/ 目录。这里全是默认配置。比如我手头这块 i.MX6ULL 的板子,对应的配置文件就是 mx6ull_14x14_evk_defconfig。
配置分两步走:
- 加载默认配置:
make mx6ull_14x14_evk_defconfig - 编译:
make -j4
这里有个坑。我在项目中遇到过,直接 make 会报错,提示缺少交叉编译工具链。你得先设置环境变量:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
为什么要指定 ARCH?因为 U-Boot 支持 x86、ARM、RISC-V 等多种架构。你不告诉它,它默认用本机编译器,编译出来的东西根本跑不了。
核心要点:U-Boot 的编译流程就是“配置 -> 编译”。配置决定了你的板子有哪些外设、多大内存、用什么驱动。编译则把源码变成二进制镜像。
编译完成后,你会得到几个关键文件:
u-boot.bin:纯二进制镜像,直接烧写到 SPI Flash 或 eMMC 的起始位置。u-boot.imx:i.MX 系列专用的镜像,头部加了 IVT(Image Vector Table)和 DCD(Device Configuration Data)。u-boot-dtb.bin:把设备树和 U-Boot 打包在一起,适合新版本。
你可能会问:“我该用哪个?” 这取决于你的启动方式。如果是 SD 卡启动,i.MX 系列必须用 .imx 格式。如果是 NOR Flash 启动,用 .bin 就行。
3.2 常用命令实战
U-Boot 启动后,进入命令行。这里我挑几个最常用的命令,每个都是我在调试中反复用到的。
3.2.1 内存操作命令
md(内存显示)和 mw(内存写)是调试利器。我曾经遇到一个内存颗粒时序问题,就是靠 md 反复读同一地址,发现数据偶尔跳变,才定位到问题。
# 查看 0x80000000 地址开始的 16 个字节
md 0x80000000 10
# 向 0x80000000 写入 0xdeadbeef
mw 0x80000000 0xdeadbeef
md 的第二个参数是长度,单位是 32 位字(4 字节)。所以 md 0x80000000 10 显示的是 64 字节。
3.2.2 网络命令
网络是 U-Boot 的灵魂。没有网络,你只能插拔 SD 卡或者用串口传文件,效率极低。
# 设置网络参数
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
setenv netmask 255.255.255.0
# 测试网络连通性
ping 192.168.1.1
# 从 TFTP 服务器下载内核
tftp 0x80800000 zImage
这里要注意,serverip 是 TFTP 服务器的 IP。我刚开始总搞混,以为 serverip 是网关。其实网关是 gatewayip,如果你跨网段访问才需要设置。
小技巧:如果 ping 不通,先检查网线,再检查驱动。U-Boot 的网卡驱动有时会因 PHY 芯片复位不完整而挂掉。可以试试 phy_reset 命令强制复位。
3.2.3 存储设备命令
从 eMMC 或 NAND 启动时,mmc 和 nand 命令是核心。
# 查看 MMC 设备信息
mmc list
mmc dev 0
mmc info
# 从 MMC 读取内核到内存
mmc read 0x80800000 0x800 0x4000
mmc read 的参数:目标内存地址、起始块号、块数量。一个块是 512 字节。所以 0x800 块就是 1MB 偏移,0x4000 块就是 8MB 数据。
3.3 环境变量
环境变量是 U-Boot 的“记忆”。你每次设置的 IP、启动参数,都保存在环境变量里。下次重启,它还能记住。
环境变量默认存储在 bootargs、bootcmd 等固定位置。你可以用 printenv 查看所有变量:
U-Boot> printenv
baudrate=115200
bootargs=console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait
bootcmd=mmc dev 0; fatload mmc 0:1 0x80800000 zImage; fatload mmc 0:1 0x83000000 imx6ull.dtb; bootz 0x80800000 - 0x83000000
bootdelay=2
ethaddr=00:04:9f:04:d2:35
ipaddr=192.168.1.100
serverip=192.168.1.10
这里最核心的是 bootcmd 和 bootargs:
- bootcmd:自动启动时执行的命令序列。说白了就是“开机后自动做的事”。
- bootargs:传递给 Linux 内核的启动参数。比如控制台设备、根文件系统位置。
修改环境变量用 setenv,保存用 saveenv:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait'
saveenv
如果不执行 saveenv,修改只在本次启动有效。重启后恢复原样。我曾经犯过这个错,调试了一下午,每次重启都回到原点,后来才发现忘了保存。
警告:saveenv 会把环境变量写入存储设备。如果存储设备坏了,或者分区表不对,保存会失败。建议先 mmc dev 0 确认设备正常,再执行保存。
3.4 启动脚本
启动脚本,其实就是把 bootcmd 里的命令写成一个脚本,方便管理和复用。U-Boot 支持两种脚本格式:
3.4.1 环境变量脚本
直接在 bootcmd 里写多行命令,用分号隔开:
setenv bootcmd 'mmc dev 0; fatload mmc 0:1 0x80800000 zImage; fatload mmc 0:1 0x83000000 imx6ull.dtb; bootz 0x80800000 - 0x83000000'
这种方式简单直接,但可读性差。命令一多,你根本看不清顺序。
3.4.2 独立脚本文件
更好的做法是写一个 boot.scr 文件。先创建文本文件 boot.txt:
# boot.txt
mmc dev 0
fatload mmc 0:1 0x80800000 zImage
fatload mmc 0:1 0x83000000 imx6ull.dtb
bootz 0x80800000 - 0x83000000
然后用 mkimage 工具转换成 U-Boot 可识别的格式:
mkimage -A arm -O linux -T script -C none -n "Boot Script" -d boot.txt boot.scr
把 boot.scr 放到 SD 卡或 eMMC 的 FAT 分区。然后在 U-Boot 中执行:
fatload mmc 0:1 0x81000000 boot.scr
source 0x81000000
source 命令会解析并执行脚本中的每一行。这样做的好处是,你修改启动流程时,只需要替换 boot.scr 文件,不用重新编译 U-Boot。
我在项目中遇到过,客户要求支持两种启动方式:SD 卡启动和 eMMC 启动。我就写了两套脚本,通过 GPIO 电平判断,在 bootcmd 里用 if 语句选择加载哪个脚本。这样既灵活又安全。
3.5 实战:从零构建一个启动流程
好,理论说完了,我们来个实战。假设你要从 eMMC 启动 Linux,步骤如下:
- 设置网络(方便调试):
setenv ipaddr 192.168.1.100; setenv serverip 192.168.1.10 - 下载内核和设备树:
tftp 0x80800000 zImage; tftp 0x83000000 imx6ull.dtb - 写入 eMMC:
mmc dev 0; mmc write 0x80800000 0x800 0x4000 - 设置启动参数:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait' - 设置自动启动:
setenv bootcmd 'mmc dev 0; mmc read 0x80800000 0x800 0x4000; mmc read 0x83000000 0x4800 0x800; bootz 0x80800000 - 0x83000000' - 保存并重启:
saveenv; reset
这里 0x800 是内核在 eMMC 中的起始块,0x4000 是大小。设备树放在 0x4800 块,大小 0x800 块(1MB 足够)。
你可能会问:“为什么内核要放在 0x80800000?” 这是约定俗成的地址。对于 ARM 平台,内存起始地址通常是 0x80000000,内核加载地址偏移 8MB,给 U-Boot 自己留点空间。
嗯,到这里,U-Boot 的核心内容就讲完了。编译、配置、命令、环境变量、启动脚本,这五块吃透了,你就能驾驭绝大多数嵌入式系统的启动流程。下一章,我们聊聊 AB 分区切换,那才是真正的高可用设计。