4. U-Boot 配置与编译:从 menuconfig 到生成镜像
好,到了真正动手的环节了。前面我们把源码拉下来,环境也搭好了,现在要做的就是把 U-Boot 变成一块能用的固件。这个过程说白了就两步:配置、编译。但这两步里头的门道,我当年可是踩过不少坑的。
核心流程速览: 配置(决定要什么功能)→ 编译(生成二进制)→ 分析产物(验证对不对)。
4.1 make menuconfig:图形化配置
我个人习惯,拿到一个新板子,第一件事就是跑 make menuconfig。这玩意儿是个基于 ncurses 的图形界面,虽然看着复古,但比直接改文本文件靠谱多了。
运行命令很简单:
# 进入 U-Boot 源码根目录
cd u-boot
# 先加载默认配置(以 qemu-arm 为例)
make qemu_arm_defconfig
# 然后打开图形配置界面
make menuconfig
你会看到一个蓝底白字的菜单界面。上下键移动,回车进入子菜单,空格切换选项。嗯,这里要注意:Y 表示编译进内核,M 表示编译成模块,N 表示不编译。但在 U-Boot 里,大部分选项只有 Y 和 N,没有 M。
我的小技巧: 第一次配置时,先加载厂商提供的 defconfig,再在此基础上修改。别从头开始配,那会累死人的。我之前有个项目,就是自己从头配,结果漏了串口驱动,折腾了两天才发现。
配置界面里,你需要重点关注这几个部分:
- ARM architecture —— 选择具体的 SoC 型号和板级配置
- Boot media —— 从哪启动(SD卡、NAND、SPI Flash 等)
- Network support —— 网络协议栈和网卡驱动
- Command line interface —— 你需要的 U-Boot 命令
- Environment —— 环境变量存储位置
配置完成后,按 Exit 退出,系统会问你是否保存。选 Yes,配置就会写入 .config 文件。
4.2 .config 文件:配置的“宪法”
退出 menuconfig 后,你会在源码根目录下看到一个 .config 文件。这玩意儿就是整个编译过程的“宪法”,所有编译决策都基于它。
打开看看,内容大概是这样的:
#
# Automatically generated file; DO NOT EDIT.
# U-Boot Configuration
#
CONFIG_ARM=y
CONFIG_TARGET_QEMU_ARM=y
CONFIG_SYS_TEXT_BASE=0x00000000
CONFIG_NET=y
CONFIG_CMD_DHCP=y
CONFIG_CMD_PING=y
CONFIG_CMD_TFTP=y
# CONFIG_CMD_NFS is not set
每一行都是一个配置项。=y 表示启用,is not set 表示禁用。我建议你养成一个好习惯:每次修改配置后,把 .config 文件备份一下。我曾经手滑删过,结果忘了之前的配置,只能重新配一遍,那叫一个痛苦。
警告: 千万不要手动编辑 .config 文件!虽然语法简单,但依赖关系很复杂。你改了一个选项,可能连带十几个依赖项都要变。用 menuconfig 会自动处理这些依赖,省心又安全。
4.3 make 编译:见证奇迹的时刻
配置搞定,接下来就是编译。命令简单到令人发指:
# 清理之前的编译产物(第一次编译可省略)
make clean
# 开始编译
make -j4
-j4 表示用 4 个线程并行编译。我一般会根据 CPU 核心数来设,比如 8 核就 -j8。但要注意,别设太大,否则电脑会卡成幻灯片。
编译过程中,你会看到屏幕上刷刷地滚过各种编译信息。如果一切顺利,最后会停在类似这样的输出:
LD u-boot
OBJCOPY u-boot.srec
OBJCOPY u-boot.bin
MKIMAGE u-boot.img
看到这四行,恭喜你,编译成功了!
编译失败怎么办? 别慌,90% 的情况是工具链问题。检查一下交叉编译器路径对不对,版本是否匹配。我遇到过最坑的一次,是系统装了多个版本的 gcc,U-Boot 调错了编译器,折腾了半天才发现。
4.4 生成文件分析:到底产出了什么
编译完成后,在源码根目录下会多出几个关键文件。咱们一个一个来看:
| 文件名 | 格式 | 用途 | 典型大小 |
|---|---|---|---|
| u-boot.bin | 纯二进制 | 直接烧录到存储介质(NAND、SD卡等) | ~500KB |
| u-boot.img | U-Boot 镜像格式 | 包含头部信息,用于网络启动或 u-boot 自身升级 | ~512KB |
| u-boot.srec | SREC 格式 | 用于串口下载,每行包含地址和数据 | ~1.5MB |
| u-boot.map | 符号映射表 | 调试用,查看函数地址和内存布局 | ~2MB |
这里重点说两个:u-boot.bin 和 u-boot.img。
u-boot.bin:最纯粹的二进制
这个文件就是 U-Boot 编译出来的“裸”二进制,从地址 0 开始,没有任何额外头部。它可以直接烧录到存储介质的起始位置。比如烧到 SD 卡:
# 将 u-boot.bin 写入 SD 卡(假设 /dev/sdb 是 SD 卡设备)
sudo dd if=u-boot.bin of=/dev/sdb bs=512 seek=1
注意那个 seek=1,很多 SD 卡第一个扇区是分区表,U-Boot 要从第二个扇区开始写。这个细节我当年不知道,烧了好几次都启动不了,后来才发现是偏移搞错了。
u-boot.img:带头的镜像
这个文件是在 u-boot.bin 的基础上,加了一个 64 字节的头部。头部里包含了:
- 镜像类型(IH_TYPE_U_BOOT)
- 加载地址(就是 CONFIG_SYS_TEXT_BASE)
- 镜像大小
- 校验和(CRC32)
u-boot.img 主要用于网络启动场景。比如通过 TFTP 下载时,U-Boot 会先读取头部,校验完整性,然后根据头部里的加载地址,把数据放到正确的位置。
怎么验证产物对不对? 用 file 命令和 hexdump 看一眼:
file u-boot.bin
# 输出: u-boot.bin: data
file u-boot.img
# 输出: u-boot.img: u-boot legacy uImage, ...
hexdump -C u-boot.img | head -5
# 可以看到开头的 64 字节头部信息
如果 u-boot.img 的 file 输出不是 "u-boot legacy uImage",那说明编译有问题,检查一下 CONFIG_FIT 之类的配置项。
4.5 避坑指南:我踩过的那些坑
做嵌入式这么多年,配置编译这块我栽过不少跟头。挑几个典型的说说:
- 工具链版本不匹配: 我曾经用 gcc 9.3 编译一个老版本的 U-Boot,结果报了一堆语法错误。后来换成 gcc 7.5,一次通过。记住,不是越新越好,要看 U-Boot 官方文档推荐的版本。
- .config 被覆盖: 如果你在 menuconfig 里改了配置,但没保存就退出,.config 不会更新。但如果你运行了
make distclean,.config 会被删掉。我建议每次改完配置,用cp .config config_myboard备份一份。 - 编译产物大小超限: 有些板子的存储空间很小,比如 512KB 的 SPI Flash。如果 u-boot.bin 超过这个大小,就装不下了。这时候需要裁剪功能,去掉不需要的命令和驱动。
重要提醒: 每次修改配置后,一定要 make clean 再重新编译。否则可能残留旧的目标文件,导致编译出来的镜像还是老版本。这个坑我至少踩过三次,每次都排查半天才发现是没 clean。