恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
U-Boot移植教程:make xxx_config配置原理与Makefile核心机制详解
首页
资讯中心
/
U-Boot移植教程:make xxx_config配置原理与Makefile核心机制详解
U-Boot移植教程:make xxx_config配置原理与Makefile核心机制详解
发布时间:2026/10/17 1:14:11
做系统移植的朋友大概都经历过这么一幕从某个渠道拿到一份BSP压缩包解压后第一件事不是打开源码看代码而是先在终端里敲一句make xxx_config等屏幕上出现 Configuring for xxx board...再执行make编译。在老版本U-Boot的世界里这句make BoardName_config基本是所有移植工作的起点。早年我刚开始接触U-Boot时对这句命令只会用、不懂原理直到有一次把板级目录名写错配置阶段照样通过真正编译时却冒出一堆找不到头文件的错误我才意识到配置命令和Makefile之间的关系没搞清楚移植过程就只能靠试错。这篇是系统移植系列的第二篇目标是把make BoardName_config这条命令和它背后的顶层Makefile、板级Makefile彻底讲透。重点会拆解配置脚本的执行过程、关键Makefile规则的含义以及新板卡加入源码树时必须要改动的几个位置。适合正在移植老版本U-Boot、或者拿着一份基于1.1.x年代代码的BSP无从下手的同学阅读。1. U-Boot的三步走移植框架为什么配置文件决定了编译的成败1.1 配置、编译、链接是一条完整流水线很多从Linux内核转过来的朋友会下意识认为U-Boot的配置和内核的make menuconfig差不多生成一个.config完事。老版U-Boot完全不同它没有交互式配置界面也没有自动探测一切都是手工指定、脚本生成。整个构建过程可以拆成三个阶段配置、编译、链接。配置阶段由make BoardName_config完成它调用mkconfig脚本生成三个关键产物include/config.mk记录板卡架构信息、include/config.h指向板级头文件的入口、以及一组include/asm*符号链接把通用头文件路径映射到具体架构。这三个产物不是可有可无的装饰而是后续编译阶段的前提。编译阶段里顶层Makefile会读取include/config.mk根据里面的ARCH、CPU、BOARD、SOC变量决定交叉编译工具链、CPU相关编译选项、以及需要递归进入哪些子目录。链接阶段则依赖编译阶段生成的一大堆.o文件和静态库通过u-boot.lds链接脚本把它们拼成最终的u-boot.bin。这里的关键点在于三个阶段是层层依赖的关系。配置一旦出错编译阶段的表现往往是莫名其妙的——不是找不到头文件就是链接地址不对甚至工具链都选错。反过来如果你把配置阶段的产物看清楚很多编译报错就能一眼定位到根因。1.2 BoardName_config命令在整个移植流程里的位置在完整的一次板卡移植中make BoardName_config处在拿到源码和执行编译之间位置非常微妙。它的实质是告诉U-Boot源码树我要编译哪块板子、用什么架构、什么CPU、什么SoC然后mkconfig脚本根据这些信息在源码树里搭建一套按板卡名检索的索引结构。举个实际场景你拿到一块新板子芯片方案是某ARM9核心的SoC板级代码放在board/myboard/目录下。此时你敲make myboard_config系统会做什么它不仅会生成配置文件还会把include/asm指向include/asm-arm再把include/asm-arm/arch指向对应的SoC头文件目录。这样一来源码里凡是写#include asm/io.h的地方预处理器都会自动去include/asm-arm/io.h找文件而你不需要在业务代码里改动任何路径。这就是配置命令最核心的价值把架构相关的复杂性通过符号链接 小配置文件封装起来让上层的通用代码保持平台无关的写法。移植一块新板子时你需要做的不是去改几十个#include而是保证配置阶段产生的这几个链接和变量正确无误。2. make BoardName_config 的逐层拆解从Makefile规则到mkconfig脚本2.1 Makefile规则入口与参数替换技巧既然叫make BoardName_config那第一步自然是去顶层Makefile里找到这条规则。老版本U-Boot里每一块板子都会对应一条形如下面的规则myboard_config: unconfig ./mkconfig myboard arm arm920t myboard NULL mysoc也有的版本写成模式规则%_config:: unconfig $(MKCONFIG) $(:_config) arm arm920t myboard NULL mysoc这里有两个初学者容易忽略的细节。第一个是$(:_config)这是Makefile的变量替换语法意思是取当前目标名myboard_config把其中的_config替换为空结果就是myboard。这个值会作为第一个参数传给mkconfig脚本代表板卡名。第二个细节是unconfig这个前置依赖它保证每次执行make xxx_config之前都会把上一次的配置产物清掉避免旧配置残留导致明明改了板子、编出来还是旧参数的诡异问题。注意规则里的前缀它表示执行这条命令时不回显命令行本身但脚本自己的输出比如 Configuring for myboard board...仍然会显示。所以配置命令运行完你看到的不是长长的shell命令而是一句简短的板卡确认信息。这算是老版本U-Boot留下的一个交互习惯看到这句确认基本说明脚本前半段参数解析没问题。2.2 mkconfig脚本到底做了什么四类产物的生成逻辑mkconfig本质上是一个朴素的shell脚本它的逻辑不复杂但每一步都有明确目的。脚本接收六个位置参数含义如下参数含义示例值最终写入位置$1板卡名myboardconfig.h 的头文件包含语句$2架构arminclude/config.mk 的 ARCH$3CPU核心arm920tinclude/config.mk 的 CPU$4板级目录名myboard决定读取 board/ 下哪个子目录$5厂商目录名NULLinclude/config.mk 的 VENDOR$6SoC名mysocinclude/config.mk 的 SOC脚本拿到参数后先创建include目录如果不存在然后干四件事。第一件是生成include/config.h内容固定是自动生成提示和一行板级头文件包含 config.h echo /* Automatically generated - do not edit */ config.h echo #include configs/$1.h config.h这里configs/$1.h对应的就是include/configs/myboard.h。写U-Boot板级配置时CONFIG_SYS_*这堆宏定义就在这个文件里。第二件事是生成include/config.mkcat config.mk EOF ARCH $2 CPU $3 BOARD $4 VENDOR ${5:-} SOC ${6:-} EOF这个文件是编译阶段的决定性输入顶层Makefile全靠它才知道该往哪个方向走。第三和第四件事是建立符号链接原理上一节已经讲过include/asm指向asm-$2即asm-arminclude/asm-arm/arch指向arch-$6即arch-mysocARM平台还会额外建立include/asm-arm/proc指向proc-armv。2.3 配置后目录结构的变化与自查方法配置完成后建议你先不要急着编译按下面几步自查一次。这些动作应该成为肌肉记忆因为移植过程中很多坑都是配置产物不对导致的。cat include/config.mk cat include/config.h ls -l include/asm include/asm-arm/arch include/asm-arm/proc正常情况下include/config.mk会显示四行变量定义include/config.h会显示两行内容自动生成注释 板级头文件包含三个符号链接都是有效的软链接。如果ls -l结果显示链接是红色的表示目标不存在多半是arch-$(SOC)或proc-armv目录不存在需要检查对应的SoC目录名是否写错。我曾经遇到过一次很隐蔽的问题符号链接本身存在但指向的arch-mysoc目录里缺少某个头文件。这种情况配置阶段完全正常编译阶段却报asm/arch/xxx.h: No such file or directory。排查时如果你只看链接是否存在很容易被带偏要沿着链接继续往下检查目标目录里的文件清单才能找到真正缺失的东西。3. 顶级Makefile的阅读地图变量、包含关系与目标依赖3.1 版本号变量与version_autogenerated.h打开老版本U-Boot的顶层Makefile第一眼看到的就是版本号定义VERSION 1 PATCHLEVEL 1 SUBLEVEL 6 EXTRAVERSION U_BOOT_VERSION $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) VERSION_FILE $(obj)include/version_autogenerated.hversion这个目标会在每次编译时生成include/version_autogenerated.h里面写的是#define U_BOOT_VERSION 1.1.6之类的字符串。这个头文件会被version.c等源码引用最终体现在U-Boot启动时打印的版本信息里。如果你在移植时改了版本号记得把旧编译产物清理干净再重新编否则version_autogenerated.h可能因为时间戳没变化而不重新生成导致启动信息还是旧版本号。这个机制本身不难但它提醒我们一个重要的阅读方法看Makefile不要只盯着目标名要追着每个目标生成了哪个文件这条线索走。version生成版本头文件depend生成各个子目录的.depend依赖文件config相关目标生成配置文件这样一梳理Makefile的骨架就清晰了。3.2 include/config.mk如何左右整个Makefile的走向顶层Makefile里最关键的一段包含逻辑是这样的include $(obj)include/config.mk export ARCH CPU BOARD VENDOR SOC ifeq ($(ARCH),arm) CROSS_COMPILE ? arm-linux- endif第一行强制包含include/config.mk如果这个文件不存在make 会直接报错退出——这就是不配置就编译最常见的报错来源。第二行把四个核心变量导出为环境变量让递归进入子目录的 make 进程也能看到。第三行是关键中的关键只有当ARCHarm时交叉编译工具链前缀才会默认为arm-linux-。也就是说如果你配置文件里写的架构不是arm那么make会尝试用宿主机的gcc去编译U-Boot出来一堆奇怪的错误。很多朋友在移植时遇到的第一个坑就是这里明明装好了交叉编译工具链但忘了先执行make xxx_config或者配置时第二个参数写错结果CROSS_COMPILE没有被正确设置为arm-linux-。这时候你去看make的输出会发现调用的编译器根本不是交叉编译器。解决方式很简单先检查include/config.mk里的ARCH是否正确。随后Makefile会include顶层config.mk注意它和include/config.mk是两个文件而顶层config.mk又会根据CPU和BOARD变量继续包含架构和板级的配置片段include $(TOPDIR)/cpu/$(CPU)/config.mk include $(TOPDIR)/board/$(BOARDDIR)/config.mkcpu/arm920t/config.mk里一般定义编译ARM920T核心需要的-march参数而board/myboard/config.mk里定义的就是板级链接地址TEXT_BASE。这一层套一层的包含关系就是U-Boot构建系统灵活性的来源你只要改include/config.mk里的几个变量整个工具链选型和编译参数都会跟着变。3.3 核心目标规则all、depend、version、unconfig顶层Makefile的真正核心是几个互相咬合的目标。先看all和u-bootall: $(ALL) $(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT) $(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) \ --start-group $(LIBS) --end-group $(PLATFORM_LIBS) \ -Map u-boot.map -o u-bootall是默认目标最终依赖u-boot.bin等产物。而u-boot这个目标前置依赖一口气列了五类东西depend负责生成依赖文件version负责生成版本头文件SUBDIRS是tools等需要先编译的主机工具目录OBJS是顶层直接参与链接的目标文件比如cpu/arm920t/start.oLIBS是各个子目录编译出来的静态库列表。链接时LD会按u-boot.lds指定的内存布局把这些目标文件和库按顺序拼装成u-boot可执行文件再经过objcopy转成u-boot.bin。depend目标会递归进入每个子目录执行make depend生成.depend文件记录源文件与头文件的依赖关系。这保证了当你修改某个头文件时make 能准确知道哪些源文件需要重新编译。如果你在移植中改了某个头文件却发现明明改了却不生效很可能就是.depend机制被破坏了比如手动删除了某个depend文件。unconfig目标则负责清理配置产物它做的事情正好是mkconfig的逆操作删除include/config.h、include/config.mk、以及三个asm相关链接。这解释了为什么每个_config规则都要把unconfig列为前置依赖——每次重新配置时先彻底清场再重新生成保证配置一定是最新状态。4. 板级Makefile的写法新板卡从零加入源码树的完整操作4.1 板级目录的标准结构与lib库的生成当你准备把一块新板卡加入U-Boot源码树时需要新建的板级目录通常长这样board/myboard/ ├── Makefile ├── config.mk ├── myboard.c ├── flash.c ├── lowlevel_init.S ├── mem_setup.S └── u-boot.lds这些文件里config.mk定义TEXT_BASE等链接期参数u-boot.lds是链接脚本lowlevel_init.S和mem_setup.S一般是汇编写的底层初始化代码myboard.c和flash.c是C语言写的板级驱动逻辑。而目录里的Makefile作用是把这些源文件打包成一个静态库供顶层链接使用。一个典型的板级Makefile是这样include $(TOPDIR)/config.mk LIB $(obj)lib$(BOARD).a OBJS : myboard.o flash.o SOBJS : lowlevel_init.o mem_setup.o all: $(LIB) $(SOBJS) $(LIB): $(OBJS) $(SOBJS) $(AR) $(ARFLAGS) $ $(OBJS) $(SOBJS) clean: rm -f $(OBJS) $(SOBJS) $(LIB)注意这里LIB的名字是lib$(BOARD).a也就是libmyboard.a。顶层Makefile的LIBS列表里会有一行board/myboard/libmyboard.a对应这个库。你新建板卡时必须保证两边名字一致否则顶层链接时找不到这个库就会报No rule to make target ...之类的问题。4.2 cpu级start.o与板级代码的分工理解板级源码的组织方式还要搞明白cpu/$(CPU)/start.S和板级代码之间的关系。老版本U-Boot里start.S是启动入口一般放在cpu/arm920t/start.S由顶层Makefile的OBJS变量直接引用而不是编译进某个库。start.S负责的是架构通用的初始化设置CPU模式、关闭看门狗、初始化中断向量、设置栈指针、拷贝自身到RAM等。这些操作对同一种CPU核心来说是通用的不管你是哪块板子这些步骤都大差不差。而板级代码lowlevel_init.S、mem_setup.S负责的是板卡特有的部分具体的内存控制器寄存器配置、GPIO初始化、时钟频率设置等这些跟硬件贴着的东西必须由板级目录自己提供。链接时u-boot.lds会把这些目标文件放到指定地址。TEXT_BASE定义了代码段的起始链接地址如果这个值设置得和板载SDRAM的实际映射地址不一致链接过程可能不报错因为链接器不知道你的DDR在哪里但烧录到板子上后U-Boot在上电瞬间就会跑飞。这也是为什么移植时第一个要确认的事情就是TEXT_BASE。4.3 添加新板卡时的五处修改清单结合上面的分析给新板卡接入U-Boot源码树的最简操作我总结成五处修改照着做基本不会漏在顶层Makefile中添加myboard_config: unconfig规则规则内容调用mkconfig并传入正确的六个参数。新建board/myboard/目录放入板级源文件、Makefile、config.mk和u-boot.lds。新建include/configs/myboard.h定义板级宏比如CONFIG_SYS_*相关参数。在board/myboard/config.mk中定义TEXT_BASE这个地址必须与板子RAM映射一致。确认顶层Makefile的LIBS列表里有board/myboard/libmyboard.a并且名字和板级Makefile里的LIB变量一致。第5步最容易被忽略。很多人新建了板级目录、写好了源码却忘了把库加入顶层LIBS结果编译时板级文件一个都没编进来链接时各种undefined reference。检查顺序应该是先make myboard_config再执行make如果链接阶段报出和板级函数有关的未定义引用优先检查顶层LIBS是否包含对应的板级库。5. 配置与编译阶段的高频报错定位思路与解决记录5.1 配置阶段报错的三种典型场景配置阶段的报错相对好定位因为这一步涉及的东西很少无非是规则、脚本、参数三个环节。第一种执行make myboard_config提示No rule to make target myboard_config。这说明顶层Makefile里压根没有这条规则要么是板卡名拼写错误要么是规则根本没有添加。处理方式很直接去顶层Makefile搜索_config看看有没有对应的规则。第二种执行配置命令时提示./mkconfig: Permission denied。这是脚本权限问题给mkconfig加上执行权限即可chmod x mkconfig第三种配置命令执行了但输出里出现架构不匹配的提示比如Cant set ARCH to arm (currently i386)。这说明你在一个已经配置过其他架构的源码树里强行改配置或者环境变量里已经导出了ARCH。解决方法是先执行make unconfig清掉旧配置再执行新的配置命令。这些报错虽然简单但处理时有一个共同原则配置脚本和环境变量是最先要排查的对象。不要一上来就怀疑编译选项先确认include/config.mk里的四行变量是不是你预期的值。5.2 编译阶段链接地址错误与头文件缺失的典型案例编译阶段的报错就五花八门了这里说两个最典型的。第一个是链接地址错误。表现是链接过程成功生成的u-boot.bin大小也正常但烧录后板子完全没反应或者启动信息乱码。这种问题九成出在TEXT_BASE上。调试方法是先用arm-linux-objdump -x u-boot查看入口地址确认start.o的链接地址和board/myboard/config.mk里定义的TEXT_BASE是否一致。如果发现入口地址完全不对基本就是TEXT_BASE写错了或者它没有被正确传递到链接脚本里。第二个是头文件缺失。表现为编译某个文件时提示某个头文件找不到但你去源码树里找文件明明存在。这时候要立刻怀疑符号链接include/asm是否指向了正确的asm-arminclude/asm-arm/arch是否指向了正确的SoC目录可以用我们前面提到的那组ls -l命令快速检查。如果链接指向正确再检查头文件是否真的在目标目录里。我曾遇到过一次arch-soc目录里少了一个hardware.h的情况就是排查到这里才发现的。报错样例优先排查点常见根因No rule to make target xxx_config顶层Makefile规则规则未添加或板卡名拼错Cant set ARCH ...环境变量、旧配置残留未先执行 unconfigxxx.h: No such file or directoryinclude 符号链接asm/arch 链接指向错误undefined reference to board_init顶层LIBS、板级Makefile板级库未加入LIBS保存后板子无反应TEXT_BASE、链接脚本链接地址与RAM不匹配5.3 一套可以反复使用的验证流程移植做多了以后我逐渐养成了固定的验证流程。这里分享给你照着做可以省掉不少返工时间。第一步配置完成后先别急着make执行三句检查命令确认include/config.mk、include/config.h和三个符号链接的状态。这个步骤大概耗时十秒却能避免后面几十秒的报错排查。第二步直接执行make注意观察第一屏输出重点看编译器是不是arm-linux-gcc而不是宿主机的gcc。如果编译器错了立刻停下来检查CROSS_COMPILE。第三步如果编译顺利通过用arm-linux-objdump -x u-boot检查入口地址和段布局确认TEXT_BASE无误。第四步也是很多人会跳过的把源码树整体make clean后重新完整编译一遍。这样做是为了排除增量编译带来的假成功——有些依赖关系没触发重编导致你测试的二进制其实还是旧版本。第五步把u-boot.bin烧录到板子上重点观察串口输出。如果启动信息里版本号正确、DRAM大小识别正确、能进入U-Boot命令行这次移植的配置和编译阶段就算真正过关了。移植U-Boot这个事说白了就是跟配置产物和Makefile依赖较劲。你在实际动手过程中可能会遇到比文章里更刁钻的报错但只要始终抓住三个检查点——include/config.mk里的架构变量、include/asm*符号链接、顶层LIBS与板级库的对应关系——绝大多数问题都能在几分钟内定位。我自己每次拿到一块新板子都是靠着这套流程稳扎稳打走下来的希望能给你省下一些踩坑的时间。