4. U-Boot 配置与编译:从 menuconfig 到生成镜像

好,到了真正动手的环节了。前面我们把源码拉下来,环境也搭好了,现在要做的就是把 U-Boot 变成一块能用的固件。这个过程说白了就两步:配置、编译。但这两步里头的门道,我当年可是踩过不少坑的。

核心流程速览: 配置(决定要什么功能)→ 编译(生成二进制)→ 分析产物(验证对不对)。

U-Boot 配置与编译流程 U-Boot 配置与编译全流程 ① 源码准备 ② make menuconfig 配置 ③ 生成 .config ④ make 编译 ⑤ 产物分析(bin/img) 💡 关键点 • 配置决定功能集 • .config 是核心配置文件 • 编译产物需验证完整性 ⚠️ 常见问题 • 配置不匹配导致编译失败 • 工具链版本不对 • 产物大小超出存储限制

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。