4. U-Boot配置与编译实战:使用menuconfig进行板级配置、交叉编译工具链设置、编译生成u-boot.bin与SPL
好,咱们进入第四章。前面几章我们把U-Boot的启动流程和源码结构捋了一遍,算是把地图看明白了。但光看不练假把式,这一章咱们直接上手——配置、编译,把u-boot.bin和SPL真正跑起来。
说实话,我刚开始学U-Boot移植那会儿,最头疼的就是配置这一步。明明照着文档敲了make xxx_defconfig,结果编译出来板子就是起不来。后来才发现,配置里藏着太多细节。今天咱们就把这些坑一个个填平。
4.1 板级配置:从defconfig到menuconfig
U-Boot的配置系统,说白了就是一套Kconfig机制。跟Linux内核的配置方式一模一样。你想想看,一个Bootloader要支持几百种开发板,总不能每种板子都维护一套独立的源码吧?所以U-Boot把硬件相关的参数抽出来,做成配置项,你选什么,它就编译什么。
配置流程分两步走:
- 加载默认配置:用
make <board_name>_defconfig,把厂商提供的默认配置写进.config文件。 - 个性化调整:用
make menuconfig,在图形界面里微调。
举个例子,我手头有一块基于NXP i.MX6ULL的板子,厂商叫它my_imx6ull。那默认配置命令就是:
make my_imx6ull_defconfig
执行完以后,你去看根目录,会多出一个.config文件。这个文件就是所有配置的“总账本”。
defconfig,我都会先cp .config config_backup备份一份。万一menuconfig改乱了,还能快速恢复。这招救过我不少次。
接下来就是重头戏——make menuconfig。它会调用ncurses库,在终端里弹出一个蓝底白字的配置菜单。你可能会问:“都什么年代了,还用这种字符界面?”嗯,嵌入式开发就是这样,很多时候你连图形桌面都没有,一个串口终端就是全部。menuconfig虽然简陋,但胜在轻量、可靠,而且所有配置项一目了然。
在menuconfig里,你可以:
- 按Y选中某个功能(编译进U-Boot)
- 按N排除
- 按M编译成模块(U-Boot里用得少)
- 按空格循环切换状态
- 按?查看帮助说明
核心配置项分类:
- ARM architecture:选择CPU架构,比如Cortex-A7、Cortex-A53
- Board select:板级特定选项,DDR参数、GPIO复用、时钟频率
- Boot media:从NAND、eMMC、SD卡还是SPI Flash启动
- Device Tree Control:是否启用设备树,以及设备树源文件路径
- SPL / TPL:SPL的独立配置区域
4.2 交叉编译工具链设置
配置搞定了,接下来就是编译。但这里有个关键问题:你的PC是x86架构,目标板是ARM架构。你不能用PC的gcc去编译ARM的代码,必须用交叉编译工具链。
工具链的选择,我建议直接用Linaro提供的预编译版本,稳定、省事。比如针对ARM 32位:
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
如果是ARM 64位:
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
这两个环境变量,ARCH告诉U-Boot你是什么CPU架构,CROSS_COMPILE指定交叉编译器的前缀。设置完以后,U-Boot的Makefile会自动拼接出完整的编译器命令,比如arm-linux-gnueabihf-gcc。
ARCH=arm,结果U-Boot默认用x86的配置去编译,报了一堆莫名其妙的语法错误。折腾了半小时才发现是架构没指定。所以,每次打开新终端,第一件事就是检查这两个变量有没有设对。
另外,工具链的路径也要加到PATH里:
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
我个人习惯把这几个export写到一个脚本文件里,比如setenv.sh,每次编译前source一下,省得反复敲。
4.3 编译生成u-boot.bin与SPL
环境变量配好,配置也调完了,接下来就是见证奇迹的时刻——编译。
对于大多数现代ARM平台,编译命令很简单:
make -j4
-j4表示用4个线程并行编译,能快不少。如果你的CPU核心多,可以设大一点,比如-j8。
编译完成后,在源码根目录下会生成几个关键文件:
| 文件名 | 说明 | 典型大小 |
|---|---|---|
u-boot.bin |
完整的U-Boot镜像,包含所有功能 | 200KB ~ 500KB |
u-boot.img |
带U-Boot头部信息的镜像,用于网络或文件系统加载 | 比u-boot.bin略大 |
SPL |
第一级Bootloader,通常很小 | 8KB ~ 64KB |
u-boot-spl.bin |
SPL的二进制镜像 | 与SPL相同 |
u-boot.dtb |
编译生成的设备树二进制文件 | 几KB到几十KB |
这里我要特别强调一下SPL。SPL的全称是Secondary Program Loader,但很多人叫它“第一级Bootloader”。为什么需要它?因为有些SoC的片上SRAM太小了,比如只有64KB,根本装不下完整的U-Boot。所以硬件上电后,先由固化在ROM里的代码加载SPL到SRAM,SPL初始化DDR内存,然后再把完整的U-Boot从存储设备加载到DDR里运行。
SPL的编译是自动的,只要你在menuconfig里启用了CONFIG_SPL,编译时就会一并生成。你可以单独编译SPL:
make spl/u-boot-spl.bin
或者只编译完整U-Boot:
make u-boot.bin
下面这张图,是我自己总结的U-Boot配置与编译的完整流程,你可以对照着看:
4.4 常见编译问题与排查
编译过程中难免遇到问题。我整理了几个高频错误,你遇到了可以直接对照:
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
arm-linux-gnueabihf-gcc: command not found |
工具链没装或PATH没设 | 检查which arm-linux-gnueabihf-gcc |
*** Can't find default configuration "xxx_defconfig" |
板级目录下没有对应的defconfig文件 | 检查configs/目录,确认文件名拼写 |
undefined reference to `xxx` |
配置项冲突或漏选了依赖 | 用make menuconfig检查依赖关系,按?查看 |
multiple definition of `yyy` |
同一个函数被重复编译 | 检查Makefile中是否重复添加了源文件 |
| SPL编译出来超过SRAM大小 | SPL功能开太多,代码膨胀 | 关闭不必要的驱动,比如USB、网络等 |
我的调试小技巧:如果编译报错,先别急着搜百度。先看错误的第一行,往往真正的错误原因就在那里。后面的几十行可能只是连锁反应。另外,make V=1可以打印出完整的编译命令,方便你看到底调用了哪个编译器、哪些源文件。
4.5 验证编译产物
编译完成后,别急着烧录。先做两个快速验证:
- 检查文件大小:用
ls -lh u-boot.bin spl/u-boot-spl.bin,确认大小在合理范围内。如果SPL超过了你板子的SRAM容量(比如64KB),那肯定跑不起来。 - 查看文件头:用
file u-boot.bin,确认它是ARM架构的二进制文件,而不是x86的。
如果一切正常,恭喜你,U-Boot已经编译成功了。下一步就是把它烧录到板子上,看它能不能跑起来。不过那是下一章的内容了。
嗯,这一章的内容就到这里。配置和编译是U-Boot移植的基础,也是你后续调试的起点。把这步走扎实了,后面会顺很多。