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把硬件相关的参数抽出来,做成配置项,你选什么,它就编译什么。

配置流程分两步走:

  1. 加载默认配置:用make <board_name>_defconfig,把厂商提供的默认配置写进.config文件。
  2. 个性化调整:用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配置与编译的完整流程,你可以对照着看:

U-Boot 配置与编译流程 U-Boot 源码 export ARCH=arm make my_imx6ull_defconfig make menuconfig make -j4 u-boot.bin + SPL + u-boot.dtb 关键点 1. 先设ARCH和CROSS_COMPILE 2. defconfig生成.config 3. menuconfig做最终调整 4. SPL自动编译

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 验证编译产物

编译完成后,别急着烧录。先做两个快速验证:

  1. 检查文件大小:用ls -lh u-boot.bin spl/u-boot-spl.bin,确认大小在合理范围内。如果SPL超过了你板子的SRAM容量(比如64KB),那肯定跑不起来。
  2. 查看文件头:用file u-boot.bin,确认它是ARM架构的二进制文件,而不是x86的。

如果一切正常,恭喜你,U-Boot已经编译成功了。下一步就是把它烧录到板子上,看它能不能跑起来。不过那是下一章的内容了。

嗯,这一章的内容就到这里。配置和编译是U-Boot移植的基础,也是你后续调试的起点。把这步走扎实了,后面会顺很多。