4. U-Boot移植基础

U-Boot的配置与编译,说白了就是一套「选菜-下单-炒菜」的流程。Kconfig让你选要什么功能,defconfig是提前配好的菜单,Makefile负责把菜炒出来,交叉编译工具链则是你的炉灶。这一章,我把这套体系拆开揉碎了讲给你听。

核心脉络:Kconfig定义选项 → defconfig保存配置 → Makefile解析构建 → 交叉工具链完成编译。四者缺一不可。

4.1 Kconfig体系:U-Boot的「点菜系统」

Kconfig不是U-Boot独有的,Linux内核也用这套。它的作用很简单:让你通过make menuconfig这种图形界面,勾选或取消各种功能。

每个功能模块都有自己的Kconfig文件,散落在各个子目录里。比如网卡驱动在drivers/net/Kconfig,文件系统支持在fs/Kconfig。顶层Kconfig通过source语句把它们串起来。

我刚开始接触Kconfig时,最头疼的是依赖关系。你选了一个选项,结果它依赖的另一个选项没选,编译直接报错。后来我养成了一个习惯:每次改配置后,先跑make olddefconfig自动补全依赖,能省掉很多麻烦。

# 一个典型的Kconfig条目示例
config CMD_BOOTM
    bool "bootm command"
    default y
    select CMD_BOOTI if ARM64
    help
      Boot an application image from memory.
      这个命令用于从内存启动内核镜像。

我的经验:Kconfig里的depends on和select要分清。depends on是「我依赖你,你不选我就不显示」;select是「我选了你,就强制把你带上」。滥用select会导致配置爆炸,我曾经在一个项目里因为select链太长,导致编译出来的U-Boot大了200KB。

4.2 defconfig文件:你的「标准菜单」

每次都用make menuconfig手动配置?太慢了。实际项目中,我们用的是defconfig文件。它本质上是.config的精简版,只保存了非默认值的选项。

U-Boot源码里,configs/目录下有成百上千个*_defconfig文件。比如stm32mp157d_atk_defconfig,就是针对某块开发板的默认配置。

文件类型 位置 作用
defconfig configs/ 板级默认配置,只保存非默认值
.config 源码根目录 完整配置,由defconfig生成
autoconf.mk include/ Makefile可读的配置,由.config转换

创建自己的defconfig很简单:

# 基于现有配置修改
make stm32mp157d_atk_defconfig
make menuconfig  # 调整你需要的选项
make savedefconfig  # 生成精简版defconfig
# 然后复制到 configs/myboard_defconfig

注意:千万不要手动编辑.config文件!格式复杂,依赖关系容易搞乱。我见过有人手动改了.config里的一个CONFIG_XXX=y,结果因为依赖没满足,编译出来U-Boot启动就挂。老老实实用menuconfig或者savedefconfig。

4.3 Makefile构建流程:从源码到二进制

U-Boot的Makefile体系,是我见过最复杂的之一。顶层Makefile负责全局调度,子目录的Makefile负责具体编译。整个流程可以概括为三步:

  1. 配置阶段:读取defconfig,生成.config和autoconf.mk
  2. 编译阶段:根据autoconf.mk决定编译哪些文件,链接成u-boot
  3. 打包阶段:生成u-boot.bin、u-boot.img等最终镜像

这里有个关键点:U-Boot是分段链接的。SPL(Secondary Program Loader)先被编译,它负责初始化DDR和加载完整的U-Boot。如果你的板子有SPL,那编译流程会多一层。

# 编译命令示例
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- stm32mp157d_atk_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

避坑指南:我曾经在编译时忘了指定CROSS_COMPILE,结果用了本机的gcc编译ARM代码。链接阶段报了一堆莫名其妙的错误,折腾了半天才发现是工具链没设对。嗯,从那以后我写了个脚本,每次编译前自动检查环境变量。

4.4 交叉编译工具链配置

交叉编译工具链,就是在一台机器(x86)上编译出能在另一台机器(ARM)上运行的代码。配置它,核心就两件事:路径和前缀。

工具链的前缀通常是这样的:arm-linux-gnueabihf-。后面跟gcc、ld、objcopy等命令。U-Boot通过CROSS_COMPILE变量来指定这个前缀。

工具链组件 命令示例 作用
C编译器 arm-linux-gnueabihf-gcc 编译C源码
链接器 arm-linux-gnueabihf-ld 链接目标文件
反汇编器 arm-linux-gnueabihf-objdump 查看二进制内容
二进制转换 arm-linux-gnueabihf-objcopy 生成.bin/.img文件

配置方式有两种:

# 方式一:命令行指定
make CROSS_COMPILE=arm-linux-gnueabihf- -j4

# 方式二:环境变量(推荐)
export CROSS_COMPILE=arm-linux-gnueabihf-
make -j4

我的习惯:把工具链路径加到~/.bashrc里,然后export CROSS_COMPILE也写进去。这样每次打开终端,工具链就自动配好了。省得每次编译都要敲一长串。

4.5 整体流程可视化

下面这张图,把整个配置编译流程串起来了。你仔细看,每个环节的输入输出都很清楚。

U-Boot配置与编译流程 Kconfig 选项定义 defconfig 默认配置 交叉工具链 make menuconfig / make xxx_defconfig 生成 .config / autoconf.mk make ARCH=arm CROSS_COMPILE=... u-boot.bin / u-boot.img / SPL 配置决定编译什么,工具链决定怎么编译,Makefile负责执行

4.6 实战中的常见问题

讲完了原理,说说我实际调试中遇到最多的几个坑:

  • 配置不生效:改了defconfig,但编译出来的U-Boot还是老样子。原因往往是没清理缓存。记得跑make mrproper后再重新配置。
  • 工具链版本不匹配:太新的gcc可能会编译出问题,太老的又缺少某些特性。我一般用Linaro提供的工具链,版本选gcc 7.x或9.x,比较稳。
  • SPL和U-Boot配置不一致:SPL的配置在spl/目录下有自己的Kconfig,和主U-Boot是分开的。如果你改了DDR参数,记得两边都要改。

重要提醒:U-Boot的编译错误,80%以上是配置问题,不是代码问题。遇到编译报错,先检查.config里的选项是否合理,再检查工具链路径。我见过有人花了两天调试一个驱动,最后发现是CONFIG_DM没开——驱动模型都没启用,驱动当然不工作。

这一章的内容,说白了就是让你理解U-Boot的「配置-编译」骨架。Kconfig是菜单,defconfig是点好的菜,Makefile是厨师,工具链是厨具。四者配合好了,你才能顺利地把U-Boot跑起来。