4. U-Boot编译系统

说实话,U-Boot的编译系统,是很多初学者第一个碰壁的地方。

我第一次接触U-Boot时,直接敲了个make,结果报了一堆莫名其妙的错误。后来才明白——这东西不是随便就能编译的。你得先配好工具链,选对板子,然后它才会乖乖工作。

这一章,我就带你把它彻底搞明白。

4.1 从顶层Makefile说起

U-Boot的顶层Makefile,是整个编译系统的入口。你执行make时,它负责调度一切。

但你别指望它像普通Linux应用那样简单。U-Boot的Makefile结构,其实借鉴了Linux内核的Kbuild系统。说白了,就是一套递归编译 + 配置分离的框架。

核心思想:U-Boot把编译过程拆成两步——先配置,再编译。配置生成.config文件,编译则根据.config决定哪些文件参与构建。

顶层Makefile主要干这几件事:

  • 定义目标架构、交叉编译工具链前缀
  • 包含config.mk和.config文件
  • 递归进入各子目录执行编译
  • 链接生成最终的u-boot.bin、u-boot.img等镜像

嗯,这里要注意:U-Boot的Makefile不是手写的,而是通过scripts/Makefile.build这套模板自动生成的。你想想看,如果每个子目录都手写Makefile,那维护量得多大?

4.2 Kbuild系统:U-Boot的编译骨架

Kbuild系统,全称是Kernel Build System。U-Boot直接把它从Linux内核搬了过来。

它的核心逻辑很简单:

  • 每个子目录里放一个Makefile,只描述本目录要编译什么
  • 顶层Makefile通过scripts/Makefile.build统一调用
  • 所有编译选项、链接脚本,都通过变量传递

举个例子,你在drivers/net/目录下看到的Makefile,可能就几行:

obj-$(CONFIG_DRIVER_ETH_DM9000) += dm9000.o
obj-$(CONFIG_DRIVER_ETH_RTL8139) += rtl8139.o

看到了吗?obj-y表示总是编译,obj-$(CONFIG_XXX)表示条件编译。这个CONFIG_XXX的值,就来自你配置时生成的.config文件。

我的经验:如果你要添加一个新的驱动,千万别去改顶层Makefile。只需要在对应子目录的Makefile里加一行obj-y += my_driver.o,然后在Kconfig里加上配置项就行了。U-Boot会自动处理剩下的。

4.3 交叉编译工具链配置

这是最容易踩坑的地方。我见过太多人卡在这一步。

交叉编译工具链,说白了就是一套在PC上运行、但生成目标板代码的编译器。比如你给ARM板编译U-Boot,就得用arm-linux-gnueabihf-gcc,而不是PC自带的gcc。

配置方式有两种:

方式 命令示例 说明
环境变量 export CROSS_COMPILE=arm-linux-gnueabihf- 全局生效,适合长期使用
命令行参数 make CROSS_COMPILE=arm-linux-gnueabihf- 临时指定,适合测试

我个人习惯用环境变量。因为这样不用每次编译都敲一遍。但要注意:工具链的路径必须加到PATH里,否则系统找不到。

我曾经踩过的坑:有一次我配好了CROSS_COMPILE,但编译时一直报arm-linux-gnueabihf-gcc: command not found。查了半天才发现,工具链装在了/opt/toolchains/下,但PATH里没加。记住:export PATH=$PATH:/opt/toolchains/bin,这一步不能省。

4.4 编译过程详解:从源码到镜像

好了,现在我们来走一遍完整的编译流程。你执行make后,U-Boot内部到底发生了什么?

我用一张流程图来展示:

1. 配置阶段 2. 读取 .config 3. 生成 autoconf.mk 4. 递归编译各子目录 5. 链接生成 u-boot 6. 生成 u-boot.bin/.img make xxx_defconfig 配置项生效 include/autoconf.mk 各驱动、板级代码 链接脚本 u-boot.lds mkimage 打包

流程其实不复杂。我拆开来讲:

  1. 配置阶段:你执行make xxx_defconfig,U-Boot会根据configs/目录下的默认配置,生成.config文件。
  2. 读取配置:顶层Makefile包含.config,所有CONFIG_XXX变量被赋值。
  3. 生成autoconf.mk:.config被转换成include/autoconf.mk,供Makefile使用。同时也会生成include/config.h,供C代码使用。
  4. 递归编译:Kbuild系统遍历所有子目录,根据obj-y和obj-$(CONFIG_XXX)决定编译哪些文件。每个.c文件被编译成.o文件。
  5. 链接:所有.o文件按照u-boot.lds链接脚本,生成ELF格式的u-boot文件。
  6. 生成镜像:通过objcopy去掉ELF头部,生成纯二进制的u-boot.bin。如果需要,还会用mkimage工具加上U-Boot头部,生成u-boot.img。

一个小技巧:如果你只想编译某个模块,比如只编译网卡驱动,可以执行make drivers/net/。U-Boot的Kbuild系统支持单目录编译,能省不少时间。

4.5 编译产物说明

编译完成后,你会在顶层目录看到一堆文件。我列个表,告诉你哪些是真正有用的:

文件 格式 用途
u-boot ELF 调试用,包含符号表
u-boot.bin 纯二进制 直接烧写到Flash,从0地址启动
u-boot.img 带头部 包含校验和、加载地址,适合网络加载
u-boot.srec S-Record 用于串口下载,每行包含地址和数据
.config 文本 当前配置,可以备份用于后续编译

我个人最常用的是u-boot.bin。直接烧到SPI Flash里,上电就能跑。但如果你用的是SD卡启动,可能需要u-boot.img,因为SPL会解析头部信息。

注意:不同板子的启动方式不同,需要的镜像格式也不同。比如有些TI的AM335x板子,需要MLO(SPL)和u-boot.img两个文件。别搞混了。

4.6 常见编译问题与解决

编译过程中,你可能会遇到这些问题。我把自己踩过的坑列出来:

  • 找不到头文件:检查include/configs/下有没有你的板级头文件。如果没有,说明配置没选对。
  • 链接报错:通常是某个驱动没编译进去,或者链接脚本里地址不对。检查.config里对应的CONFIG_XXX有没有打开。
  • 工具链版本不兼容:U-Boot对GCC版本有要求。太新的GCC可能会报一些奇怪的警告,甚至错误。我建议用Linaro提供的工具链,比较稳定。
  • 编译到一半卡住:可能是某个子目录的Makefile写错了。试试make V=1,打印详细日志,定位问题。

嗯,其实编译系统这块,说难也不难。你只要记住:配置靠Kconfig,编译靠Kbuild,工具链靠环境变量。这三条线理清了,U-Boot的编译对你来说就是小菜一碟。

好了,这一章就到这里。下一章我们聊聊U-Boot的启动流程——从ROM代码到SPL,再到U-Boot proper,看看芯片上电后到底发生了什么。


可可精品资料库