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负责具体编译。整个流程可以概括为三步:
- 配置阶段:读取defconfig,生成.config和autoconf.mk
- 编译阶段:根据autoconf.mk决定编译哪些文件,链接成u-boot
- 打包阶段:生成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 整体流程可视化
下面这张图,把整个配置编译流程串起来了。你仔细看,每个环节的输入输出都很清楚。
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跑起来。