首页 / 资讯中心 / 文章详情

U-Boot移植实战:Kbuild构建体系与Kconfig配置详解

U-Boot移植实战:Kbuild构建体系与Kconfig配置详解 ★ FEATURED ARTICLE
1. 从一次编译报错说起为什么要啃 Kbuild第一次给一块新板子做 U-Boot 移植的时候我卡在了一个看起来特别蠢的地方改了board/xxx/下的一个Makefile加了一行obj-y my_driver.o结果编译死活不认报错说找不到目标。折腾了大半天才发现U-Boot 的构建系统早就不是传统那套递归 Makefile 了它用的是Kbuild——一套从 Linux 内核搬过来的、基于 Kconfig 配置加 Kbuild 规则的分层构建体系。你如果还按老思路去改 Makefile基本就是对着空气使劲。这篇东西就是把我踩过的坑、翻过的源码、验证过的操作整理出来专门讲清楚U-Boot 移植过程中 Kbuild 到底怎么工作、怎么改、怎么排查。它适合两类人一类是刚接手 U-Boot 移植、被一堆Makefile、Kconfig、.config、defconfig搞晕的嵌入式新手另一类是能跑通编译、但一遇到“为什么这个文件没被编进去”“为什么配置项不生效”就抓瞎的进阶玩家。看完你应该能做到自己给一块新板子加配置、加驱动、加目录并且知道每一步背后 Kbuild 在干什么。先把结论摆前面U-Boot 的构建系统核心就三样东西——Kconfig 负责“选什么”KbuildMakefile 体系负责“怎么编”defconfig 负责“默认选什么”。移植的本质就是让这三样东西为你的板子正确协同。下面我按“设计思路 → 核心细节 → 实操过程 → 问题排查”这条线一层层拆开讲。2. U-Boot 构建体系整体设计与思路拆解2.1 为什么 U-Boot 要抛弃传统 Makefile早年的 U-Boot 用的是手写递归 Makefile每个子目录一个Makefile靠obj-y、obj-$(CONFIG_XXX)这种变量拼编译目标。这套东西在板子少的时候还能忍但 U-Boot 支持的板子数量早就上千了配置项爆炸式增长传统 Makefile 的问题就暴露得很彻底配置和构建耦合太紧想加一个功能开关得同时改include/configs/xxx.h和一堆 Makefile容易漏。无法做依赖管理改一个配置项不知道哪些文件需要重编只能全量编译慢得要命。配置界面缺失没有统一的menuconfig那种可视化配置全靠手改头文件出错率极高。Linux 内核当年也遇到一模一样的问题解决方案就是Kconfig Kbuild。U-Boot 从 2014 年左右开始大规模引入这套体系到现在已经是绝对主流。它的核心思想是配置与构建分离Kconfig 文件描述“有哪些配置项、依赖关系是什么、默认值是什么”Kbuild 的 Makefile 只负责“根据配置结果决定编哪些文件”。提示如果你手里的 U-Boot 版本还在用include/configs/xxx.h里堆#define CONFIG_XXX的方式那基本是 2013 年以前的老版本。新版本里这些宏大部分已经迁移到 Kconfig头文件里只剩少量板级特有的定义。2.2 Kbuild 的三层结构顶层、架构层、板级层U-Boot 的 Kbuild 体系是分层组织的理解这个层次结构是移植的第一步。我把它拆成三层来看顶层是源码根目录的Makefile它负责解析命令行参数比如make xxx_defconfig、make CROSS_COMPILE...加载.config然后递归进入各个子目录。顶层 Makefile 里有一句很关键的话include $(srctree)/Makefile之后会去读scripts/Kbuild.include这里面定义了obj-y、obj-m、ccflags-y这些 Kbuild 专用变量和规则。架构层在arch/arm/以 ARM 为例下面包含Makefile、Kconfig、config.mk等。arch/arm/Kconfig定义了架构相关的配置项比如 CPU 类型、指令集、浮点支持等。arch/arm/Makefile则负责把架构相关的编译选项传给顶层。板级层在board/厂商/板名/下面每个板子一个目录里面有Makefile、Kconfig、xxx.c等。这是移植时改动最频繁的地方。板级Kconfig定义这块板子特有的配置项板级Makefile决定这块板子要编哪些文件。三层之间的关系可以用一句话概括顶层定规则架构层定选项板级层定内容。你移植一块新板子主要工作就是在板级层加东西偶尔需要动架构层顶层基本不碰。2.3 defconfig 与 .config 的关系别搞混了这是新手最容易混淆的地方我当初也栽过。defconfig和.config是两个完全不同的东西defconfig是“默认配置模板”放在configs/目录下命名规则是板名_defconfig。它是给人看的、可以提交到代码仓库的文本文件里面是一行行的CONFIG_XXXy或CONFIG_XXXn。.config是“实际生效的配置”放在源码根目录是make xxx_defconfig之后由 Kconfig 工具生成的。它包含了所有配置项的最终值包括那些你没在 defconfig 里写、但被 Kconfig 默认值或依赖关系推导出来的项。执行make xxx_defconfig的时候Kbuild 会做这几件事读取configs/xxx_defconfig结合所有Kconfig文件里的默认值和依赖关系生成完整的.config。之后每次make顶层 Makefile 都会读.config把里面的CONFIG_XXXy转换成编译时的宏定义和 Makefile 变量。注意.config是生成物不要手动改它也不要提交到仓库。要改配置改defconfig或者用make menuconfig改完再make savedefconfig回写。2.4 移植时为什么必须理解 Kbuild很多人觉得移植就是“抄一块相似的板子改改名字和地址”能跑就行。但实际项目里你迟早会遇到这些情况板子上加了一颗新芯片需要写驱动但不知道怎么让 U-Boot 把它编进去。某个功能默认开着但你的板子不需要想关掉却找不到在哪里关。编译出来的镜像太大想裁剪但不知道哪些配置项在起作用。换了一个编译器编译报错怀疑是某个编译选项没传对。这些问题全都指向 Kbuild。不理解 Kbuild你只能靠“试”试对了不知道为什么对试错了也不知道错在哪。理解了 Kbuild你就能精准定位是配置项没选对还是 Makefile 没写对还是依赖关系没理清。3. 核心细节解析与实操要点3.1 Kconfig 语法配置项是怎么定义的Kconfig 文件是 Kbuild 体系的“配置源”它用一种类似 DSL 的语法描述配置项。我拿一个实际例子来讲假设你要给板子加一个 LED 驱动需要在board/xxx/Kconfig里加这么一段config LED_MYBOARD bool Enable LED support for MyBoard depends on ARCH_MYBOARD default y help Say Y here to enable the LED driver for MyBoard. If unsure, say N.逐行拆解config LED_MYBOARD定义了一个配置项名字是LED_MYBOARD。最终在.config里会变成CONFIG_LED_MYBOARDy。bool ...类型是布尔值后面的字符串是menuconfig里显示的提示文字。depends on ARCH_MYBOARD依赖条件只有ARCH_MYBOARD被选中时这个选项才会出现。default y默认值。注意默认值只有在依赖满足时才生效。help帮助文本menuconfig里按?能看到。Kconfig 的类型不止bool常用的还有类型说明示例bool布尔值y 或 nbool Enable XXXtristate三态y/m/nU-Boot 里基本只用 y/ntristate XXXint整数int Stack size default 4096hex十六进制hex Load address default 0x80000000string字符串string Board name还有一个很重要的概念是select。select和depends on方向相反depends on是“我依赖别人”select是“我选中别人”。比如config ARCH_MYBOARD bool MyBoard architecture select CPU_V7 select DM这表示选中ARCH_MYBOARD时自动把CPU_V7和DM也选上。select用起来方便但有个坑它不会检查被选中项的依赖是否满足如果被选中项有depends on没满足Kconfig 会报警告。所以用select要谨慎确保被选中项的依赖链是完整的。3.2 Kbuild Makefile 规则obj-y 到底怎么用Kbuild 的 Makefile 和传统 Makefile 写法差别很大核心就是几个变量obj-y无条件编译进镜像的目标文件。obj-$(CONFIG_XXX)根据配置项决定是否编译。如果CONFIG_XXXy就等价于obj-y如果是n就等价于obj-n不编译。obj-$(CONFIG_XXX) foo.o这是最常见的写法。ccflags-y给本目录下所有目标文件加的编译选项。ccflags-$(CONFIG_XXX)条件编译选项。举个实际例子board/myboard/Makefile里可能长这样obj-y myboard.o obj-$(CONFIG_LED_MYBOARD) led.o obj-$(CONFIG_MMC) mmc.o ccflags-y -I$(srctree)/board/myboard/include这里myboard.o无条件编译led.o只在CONFIG_LED_MYBOARDy时编译mmc.o只在CONFIG_MMCy时编译。ccflags-y给这个目录下所有文件加了头文件搜索路径。提示obj-y后面跟的.o文件Kbuild 会自动去找同名的.c文件编译。如果.c文件在子目录里要写成obj-y subdir/Kbuild 会递归进入子目录。还有一个容易踩的坑Kbuild 的 Makefile 里不能用传统 Makefile 的规则比如all: foo.o这种写法在 Kbuild 里是无效的。Kbuild 只认obj-*、ccflags-*、libs-*这些特定变量。你如果写了传统规则要么被忽略要么报奇怪的错。3.3 目录层级与 Makefile 递归文件是怎么被找到的U-Boot 的编译是从顶层 Makefile 开始逐层递归进入子目录的。递归的入口是顶层 Makefile 里的这一句include $(srctree)/Makefile不对准确说是顶层 Makefile 会调用scripts/Makefile.build这个文件定义了obj-y的处理规则。当 Kbuild 处理一个目录时它会读当前目录的Makefile收集obj-y、obj-$(CONFIG_XXX)等变量。对每个obj-y里的.o文件找到对应的.c文件调用编译器编译。对每个obj-y里的子目录以/结尾递归进入该子目录重复步骤 1。这个递归过程决定了你的文件放在哪里、Makefile 怎么写。比如你的驱动放在drivers/led/下那drivers/led/Makefile里要写obj-$(CONFIG_LED_MYBOARD) led_myboard.o同时drivers/Makefile里要有obj-y led/才能递归进去。我见过有人把驱动文件放在board/myboard/下但忘了在board/myboard/Makefile里加obj-y led.o结果编译通过但驱动根本没被编进去烧到板子上发现功能不工作查了半天。这种问题就是没理解递归机制。3.4 配置项的依赖链为什么你的选项不生效Kconfig 的依赖关系是移植时最容易出问题的地方。一个配置项要生效必须满足两个条件它自己被选中且它的所有依赖都被满足。依赖链可能很长比如CONFIG_LED_MYBOARD depends on ARCH_MYBOARD depends on CPU_V7 depends on ARM如果你只选了LED_MYBOARD但ARCH_MYBOARD没选那LED_MYBOARD在menuconfig里根本不会出现.config里也不会有它。更隐蔽的是select链A select BB select C如果 C 的依赖没满足整个链可能断掉。排查依赖问题有个笨但有效的办法用make menuconfig进去按/搜索配置项名字它会显示这个项的依赖是什么、当前值是什么、为什么不可选。这个功能我强烈建议每个移植的人都用熟。4. 实操过程与核心环节实现4.1 环境准备与源码获取先说环境。我用的是一台 Ubuntu 20.04 的机器交叉编译器是gcc-arm-linux-gnueabihfU-Boot 源码用的是 2023.10 版本。你如果用的是别的版本大部分操作是一样的但具体文件路径可能有细微差别。sudo apt install gcc-arm-linux-gnueabihf bison flex libssl-dev wget https://ftp.denx.de/pub/u-boot/u-boot-2023.10.tar.bz2 tar xf u-boot-2023.10.tar.bz2 cd u-boot-2023.10bison、flex、libssl-dev这三个包是编译 Kconfig 工具和签名工具用的缺了会报错。我当初图省事没装libssl-dev编译到一半报openssl/ssl.h not found又回头装。4.2 基于相似板子创建新板级目录移植的第一步通常是找一块相似的板子“抄”。假设我要移植一块基于 ARM Cortex-A7 的板子叫myboard参考的是board/sunxi/下的某块板子。操作如下cp -r board/sunxi board/myboard cp configs/sunxi_defconfig configs/myboard_defconfig然后改board/myboard/下的文件把Kconfig里的板名、Makefile里的目标文件名、.c文件里的板级初始化代码都改成myboard相关的。这一步没什么技巧就是细心别漏改。改完之后在arch/arm/mach-sunxi/Kconfig里加一个ARCH_MYBOARD配置项或者在board/myboard/Kconfig里定义。我倾向于放在board/myboard/Kconfig因为这是板级特有的config TARGET_MYBOARD bool Support MyBoard select ARM select CPU_V7 select DM help Support for MyBoard.然后在board/myboard/Makefile里确保有obj-y myboard.o4.3 编写 defconfig 并生成 .configconfigs/myboard_defconfig是移植的核心文件之一。它不需要写全所有配置项只写那些和默认值不同的、或者必须显式指定的。一个典型的 defconfig 长这样CONFIG_ARMy CONFIG_TARGET_MYBOARDy CONFIG_SYS_TEXT_BASE0x4a000000 CONFIG_DEFAULT_DEVICE_TREEmyboard CONFIG_SYS_MALLOC_LEN0x1000000 CONFIG_BAUDRATE115200这里每一项都有讲究CONFIG_ARMy指定架构必须有。CONFIG_TARGET_MYBOARDy选中你的板子这会触发select链把ARM、CPU_V7、DM等自动选上。CONFIG_SYS_TEXT_BASEU-Boot 的加载地址必须和你的板子内存布局匹配。这个值算错了U-Boot 根本跑不起来。CONFIG_DEFAULT_DEVICE_TREE默认设备树名字要和arch/arm/dts/下的.dts文件名对应。CONFIG_SYS_MALLOC_LEN堆大小太小会导致驱动初始化失败太大浪费内存。CONFIG_BAUDRATE串口波特率调试必备。写完 defconfig执行make myboard_defconfig如果一切正常会生成.config并输出类似Configuration written to .config的提示。如果报错通常是 Kconfig 语法错误或者依赖不满足按提示去查对应的Kconfig文件。4.4 编译验证与镜像生成配置好了就编译make CROSS_COMPILEarm-linux-gnueabihf- -j8-j8是并行编译核多的话可以开更大。编译过程中如果报某个文件找不到大概率是 Makefile 里的obj-y写错了或者文件没放在正确目录。编译成功后会在根目录生成u-boot.bin、u-bootELF、u-boot.map等文件。u-boot.map是排查问题的利器它列出了所有符号的地址和大小。如果某个驱动没被编进去在 map 文件里搜符号名就能确认。我习惯编译完先grep一下关键符号确认该有的都有。4.5 用 menuconfig 微调配置make menuconfig是 Kbuild 体系最方便的地方。进去之后可以按/搜索配置项按?看帮助改完保存会更新.config。但注意menuconfig 改的是.config不是 defconfig。如果你想让改动永久生效改完要执行make savedefconfig cp defconfig configs/myboard_defconfigsavedefconfig会把当前.config里和默认值不同的项提取出来生成一个精简的defconfig。这个功能特别好用避免手动维护 defconfig 时漏项。5. 常见问题与排查技巧实录5.1 配置项不生效的排查思路这是最高频的问题。我总结了一个排查顺序确认配置项名字拼写正确。Kconfig 里是LED_MYBOARD.config里就是CONFIG_LED_MYBOARD多一个字母少一个字母都不行。确认依赖满足。用make menuconfig搜索看依赖链是否完整。确认 defconfig 里写了。如果没写且默认值是n那它就不会生效。确认 Makefile 里用了这个配置项。配置项生效了但 Makefile 里没引用文件照样不会被编。确认没有其他地方覆盖。比如arch/arm/config.mk里可能有条件判断把某个选项强制关掉了。5.2 编译报错“No rule to make target”怎么查这个报错通常意味着 Kbuild 找不到某个.o对应的.c文件。排查步骤看报错里提到的.o文件名去对应的Makefile里找是哪一行obj-y加的。确认同名的.c文件存在且路径正确。如果.c在子目录确认Makefile里写的是obj-y subdir/而不是obj-y subdir/foo.o。确认子目录的Makefile存在且写了obj-y foo.o。我遇到过一次是因为文件名大小写不一致Makefile 里写的是Led.o实际文件是led.o。Linux 下文件系统区分大小写直接报错。5.3 镜像过大怎么裁剪U-Boot 镜像过大通常是因为选了一堆用不到的功能。裁剪思路用make menuconfig逐个模块看把不需要的驱动、命令、文件系统支持关掉。重点关掉CONFIG_CMD_*里用不到的命令、CONFIG_USB_*、CONFIG_MMC_*、CONFIG_FS_*里用不到的文件系统。关掉CONFIG_DEBUG_*相关的调试选项这些会显著增大体积。用arm-linux-gnueabihf-size u-boot看各段大小定位大头。裁剪是个反复迭代的过程每次改完重新编译看大小别一次关太多否则可能把启动必需的项也关了。5.4 常见问题速查表问题现象可能原因排查方法配置项在 menuconfig 里看不到依赖不满足按/搜索看依赖链编译通过但功能不工作文件没被编进去查u-boot.map里有没有对应符号报错 No rule to make targetMakefile 里 obj-y 写错检查文件名、路径、大小写镜像过大选了多余功能menuconfig 逐项关看 size 输出defconfig 改了不生效没重新 make xxx_defconfig改完 defconfig 要重新生成 .config编译报头文件找不到ccflags-y 没加对路径检查 Makefile 里的 -I 选项5.5 几个我踩过的坑和独家技巧坑一改了 Kconfig 但 menuconfig 里没变化。原因是 Kconfig 文件的修改需要重新生成配置解析器。执行make mrproper清理后再make xxx_defconfig就好了。mrproper会删掉.config和所有生成物注意备份。坑二select 了一个有依赖的项但依赖没满足。Kconfig 会报警告但不报错编译时可能出奇怪的问题。解决办法是给被 select 的项也加上select它的依赖或者改用depends on。坑三并行编译-j开太大导致内存不足。编译 Kconfig 工具和链接阶段比较吃内存-j开太大可能被 OOM killer 杀掉。我一般用-j$(nproc)内存小的机器用-j4。技巧一用make V1看完整编译命令。默认编译输出很简洁看不到实际执行的命令。加V1会打印每条编译命令排查编译选项问题时特别有用。技巧二用make savedefconfig维护 defconfig。手动维护 defconfig 容易漏项用savedefconfig自动生成既精简又准确。技巧三把常用配置项做成脚本。移植多块板子的时候把make xxx_defconfig、make menuconfig、make savedefconfig这些步骤写成脚本减少重复劳动。6. 移植完成后的验证与扩展6.1 烧录与串口验证编译出u-boot.bin之后用烧录工具写到板子的启动介质上SPI Flash、SD 卡、eMMC 等取决于你的板子。上电后接串口波特率设成 defconfig 里配的CONFIG_BAUDRATE应该能看到 U-Boot 的启动日志。如果没输出先查串口线、波特率、TX/RX 有没有接反再查CONFIG_SYS_TEXT_BASE和内存布局是否匹配。启动日志里会打印 CPU 型号、内存大小、外设初始化情况。重点看有没有报错比如某个驱动 probe 失败、设备树解析失败等。这些信息是后续调试的基础。6.2 用 dm tree 检查驱动模型U-Boot 现在默认用驱动模型Driver ModelDM。启动后在 U-Boot 命令行里敲dm tree会打印所有设备的树状结构。如果你的驱动没出现在这棵树里说明它没被正确注册或初始化。dm tree配合dm uclass命令能快速定位驱动问题。6.3 后续扩展方向移植跑通只是第一步。后续你可能需要加新驱动按本文讲的 Kconfig Kbuild 流程加配置项、加 Makefile 规则、写驱动代码。支持设备树在arch/arm/dts/下加.dts文件在 defconfig 里指定CONFIG_DEFAULT_DEVICE_TREE。裁剪优化根据实际需求关掉多余功能减小镜像体积加快启动速度。支持启动 Linux配置bootcmd、bootargs让 U-Boot 能加载并启动内核。这些扩展都建立在 Kbuild 体系之上理解了 Kbuild后面的事情就是顺水推舟。我个人在实际操作中的体会是U-Boot 移植最耗时间的不是写代码而是搞清楚构建系统的规则。Kbuild 这套东西初看很绕但一旦理解了“Kconfig 管配置、Kbuild 管构建、defconfig 管默认”这个核心后面遇到问题就能快速定位。我建议新手不要急着改代码先花半天时间把scripts/Kbuild.include、scripts/Makefile.build这两个文件读一遍再拿一块现成的板子做实验改改配置、加加文件看编译结果怎么变。这种“动手验证”的学习方式比看十篇文档都管用。
阅读完成 · 觉得有帮助?
咨询建站