news 2026/10/17 1:13:18

u-boot配置命令与Makefile机制深度解析:从make xxx_config到编译链接

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
u-boot配置命令与Makefile机制深度解析:从make xxx_config到编译链接

1. 这块内容在整个移植流程里处于什么位置

做了嵌入式Linux系统移植的朋友应该都有印象,拿到一块新板子,第一步往往不是急着改代码,而是先把u-boot跑起来。跑u-boot的流程可以拆成三块:配置、编译、烧写。配置这一步,在2014年之前的u-boot版本里,靠的就是一句make BoardName_config。我在上一篇里讲完了环境准备和u-boot的整体结构,这篇把配置命令和Makefile彻底拆开,看看这条命令执行之后到底发生了什么,以及Makefile是怎么根据配置结果去组织整个编译过程的。

先明确一个核心认知:make xxx_config并不是在编译,它只是在做“预配置”。它做的事情本质上只有两件,一是生成几个配置文件,二是创建一些头文件软链接。至于真正把u-boot.bin编出来,是你接下来再执行一次make的事情。很多人卡在移植上,恰恰是因为不理解这两步之间的关系,直接在Makefile里乱改一通,结果越改越乱。

这篇文章适合谁看?想自己移植u-boot到新板子的嵌入式开发者,刚接触u-boot源码想搞懂Makefile逻辑的初学者,以及被各种“照着抄就能编译”的教程坑过、想真正搞清楚原理的人。我尽量用实际操作的视角来讲,把2010~2013年间最常用的u-boot版本里的机制讲透,顺带说一下它和新版本的区别,因为老版本机制是理解现代u-boot的一把钥匙,而很多芯片公司的BSP还沿用着老规矩。

2. make xxx_config命令的执行链路全拆解

2.1 顶层Makefile怎么“接住”这个目标

先看一个最基本的问题:你敲下make smdk2410_config,Makefile凭什么知道要干什么?

在老版本u-boot的顶层Makefile里,有一段关键规则,不同版本写法稍有差异,但逻辑一致。早一点的版本(2010.03之前)直接在Makefile里写死了每个板卡的配置规则,长这样:

smdk2410_config : unconfig @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

$(@:_config=)是Makefile的替换引用语法,意思是把目标名smdk2410_config里的_config去掉,得到smdk2410。于是这一条规则的命令实际执行的是:

./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0

到了2010.06之后,u-boot引入了boards.cfg文件,把所有板卡信息统一放到一个表里维护,Makefile里的规则就收敛成了通用模式:

%_config:: unconfig @$(MKCONFIG) -a $(@:_config=)

这里%是Makefile的模式匹配符,任何以_config结尾的目标都能命中。比如你敲make myboard_config,实际调用的就是:

./mkconfig -a myboard

看到没有,整个配置过程的核心其实就两个角色:顶层Makefile规则和mkconfig脚本。Makefile负责匹配目标、清理旧配置、调用脚本,mkconfig脚本负责真正干活的细节。

顺便说一个容易忽略的点,%_config规则后面跟着的unconfig是依赖,也就是说每次执行配置命令前,都会先执行unconfig目标。这个设计非常重要,要单独拎出来讲。

2.2 unconfig先清场:避免脏配置干扰

unconfig目标定义在顶层Makefile里,内容很简单:

unconfig: @rm -f include/config.h include/config.mk \ board/*/config.h include/config.h

为什么要先删除?因为u-boot的配置状态是“全局”的。如果你之前给A板卡执行过配置,生成了对应的config.h和config.mk,不清理就直接配置B板卡,mkconfig脚本在创建软链接时可能会有残留,config.h里的内容也会新旧混杂。实际踩坑经验告诉我,移植调试期间来回切换板卡配置,最典型的问题就是include/asm软链接指向混乱,编译报出各种找不到头文件。

所以这个unconfig就很像你做饭之前先把灶台擦干净,保证一个干净的起点。这也是为什么推荐在换板卡配置的时候,先执行make distclean(它除了删配置还会删编译产物),再执行新的make xxx_config。

2.3 MKCONFIG的调用与参数传递

接着看规则里那个$(MKCONFIG),它的定义在顶层Makefile靠上的位置:

MKCONFIG := $(SRCTREE)/mkconfig

就是指向源码根目录下的mkconfig脚本。这个脚本是u-boot移植里必须读懂的第二个文件,第一个当然是顶层Makefile。

调用时还带了一个-a参数,它的含义是让mkconfig自己去boards.cfg里检索板卡信息。这在2010.06之后是很重要的设计变化:以前每次新增板卡都要去顶层Makefile里加一条规则,有了boards.cfg之后,只需要在这个文本文件里加一行,Makefile规则完全不用动。这种“数据驱动配置”的思路,比之前把配置写死在Makefile里要清爽得多。

mkconfig脚本接收到-a myboard这个参数后,处理流程大致是解析参数、读取boards.cfg、在include目录下建软链接、写config.mk和config.h。下一章逐段拆。

3. mkconfig脚本逐段解读

3.1 从boards.cfg检索板级信息

u-boot的boards.cfg是个纯文本文件,每一行描述一款板卡,格式固定,用空格或Tab分隔字段。这是2010.06之后版本的格式:

# Target ARCH CPU Board name Vendor SoC Options smdk2410 arm arm920t smdk2410 NULL s3c24x0

字段含义我给整理成了一张表:

字段含义说明
Targetmake命令用的目标名比如make smdk2410_config里的smdk2410
ARCH处理器架构对应源码里的lib_xxx和include/asm-xxx
CPUCPU核心/模块对应源码里的cpu/xxx目录
Board name板卡名对应board/厂商/板卡目录名
Vendor厂商名对应board/厂商目录名
SoC片上系统型号对应include/asm-xxx/arch-xxx软链接目标
Options附加选项通常为空,个别板卡用

你可能会问,mkconfig是怎么把myboard这个目标映射到一行配置的?脚本里的做法是:拿参数里的目标名去boards.cfg逐行匹配,匹配到Target字段相等的那一行,就把这一行的ARCH、CPU、Board name、Vendor、SoC字段全部解析出来,作为后续操作的依据。

这块有个值得注意的坑:boards.cfg里的字段数量必须对齐,不要多写或少写字段。我见过有人在Target前面多加了一个空格,结果匹配不上,make xxx_config执行后什么反应都没有,或者报awk解析错误。排查了半小时才发现是文件格式问题。

3.2 软链接怎么建以及为什么这么建

mkconfig脚本拿到参数之后,会进到include目录下去创建一系列软链接。这是配置过程中最核心、也最让新手懵的操作。以arm架构为例,主要创建这几个链接:

rm -f asm ln -s asm-arm asm ln -s arch-s3c24x0 asm-arm/arch ln -s proc-armv asm-arm/proc

第一条:include/asm指向include/asm-arm,目的是让源码里统一写#include <asm/xxx.h>,无论是arm、mips还是powerpc,都不用改代码,只要软链接指对了架构,头文件自然找到对应的架构目录。这是一种“间接层”设计,跟c语言里用宏一样,隔离变化。

第二条:asm-arm/arch指向arch-s3c24x0,这条的含义更深。u-boot源码里,板级或者SoC相关的寄存器定义、硬件地址宏,放在include/asm-arm/arch-xxx/目录下。不同的SoC,寄存器布局天差地别,通过软链接把arch统一指向当前SoC,代码里写#include <asm/arch/regs.h>,实际读到的就是当前SoC的寄存器定义。

第三条:asm-arm/proc指向proc-armv,这跟具体SoC关系不大,是arm架构公有的处理器相关头文件。

为什么要用软链接而不是直接把头文件复制过来?因为软链接是“活的”,切换板卡配置时只需要改链接指向,不用复制大量文件。而且多个板卡可以共享同一套SoC头文件,硬链接或复制会造成严重的代码冗余和维护噩梦。

这里还要补充一个容易出问题的点:如果你在Windows的共享目录、NTFS分区或者某些不支持符号链接的文件系统上解压u-boot源码,执行make xxx_config时会报ln错误,或者生成出来的“软链接”是一个普通文本文件,后续编译直接找不到头文件。解决办法很简单,把源码放到Linux原生文件系统里操作,或者用支持符号链接的挂载方式。

3.3 config.mk和config.h到底生成了什么

软链接搞定之后,mkconfig脚本在include目录下生成两个关键文件。

第一个是config.mk,内容长这样:

ARCH = arm CPU = arm920t BOARD = smdk2410 SOC = s3c24x0

这四个变量会被顶层Makefile直接include,进而决定编译哪些目录、链接哪些库。注意这里没有Vendor字段,因为示例板卡的Vendor是NULL,mkconfig会跳过为空的值。

第二个是config.h,内容是两行:

/* Automatically generated - do not edit */ #include <configs/smdk2410.h>

这个config.h是整个编译过程中所有.c文件都会间接包含的头文件。它唯一的使命就是把include/configs/板卡名.h引进来。而configs/smdk2410.h才是你真正要手动维护的板级配置头文件,里面定义了几百个CONFIG_xxx宏,控制着u-boot的功能开关、内存地址、环境变量分区等。

所以整个配置环节的逻辑链就清楚了:

boards.cfg提供板卡元信息 -> mkconfig解析并写config.mk -> Makefile读config.mk决定编译路径

include/configs/板卡名.h提供功能配置 -> 被config.h引用 -> 被所有源文件包含

这其实就是一个两层的“数据-配置”分离结构,一层管“编什么”,一层管“怎么编”。

4. 配置结果如何驱动后续编译

4.1 config.mk被谁include,变量如何导出

执行完make xxx_config后,你直接执行make,顶层Makefile刚开始就会遇到这一句:

include $(obj)include/config.mk

这句话把刚才生成的文件读进来,ARCH、CPU、BOARD、SOC这几个变量在Makefile里就变成了可用状态。紧接着还有一处导出:

export ARCH CPU BOARD VENDOR SOC

export的作用是让这些变量传递到子Makefile里。u-boot编译并不是只编译顶层,它会递归进入各个子目录,比如cpu/arm920t/、board/samsung/smdk2410/、lib_arm/,每个子目录都有自己的Makefile。如果这些变量不export,子Makefile就没法根据架构和板卡名去做条件判断。

关于include的时机,这里有个细节。顶层Makefile里include config.mk之前会先判断文件是否存在,如果不存在会给出错误提示,意思就是让你先去执行make xxx_config。这个设计保证了“先配置后编译”的顺序不会被绕过。

4.2 LIBS变量是怎么从无到有的

接下来是Makefile里最核心的变量之一:LIBS。它就是最终链接u-boot时用到的一堆静态库的合集。看一段典型定义:

LIBS = lib_generic/libgeneric.a LIBS += lib_$(ARCH)/lib$(ARCH).a LIBS += cpu/$(CPU)/lib$(CPU).a LIBS += board/$(BOARDDIR)/lib$(BOARD).a

同样一段代码,当你配置的是arm平台时,lib_$(ARCH)展开成lib_arm,于是编译lib_arm目录;当你配置的是mips平台,就变成lib_mips,编译lib_mips目录。所有这一切,都源于config.mk里那一个简单的ARCH = arm。

BOARDDIR的构成也挺有意思:

BOARDDIR = $(VENDOR)/$(BOARD)

如果你的Vendor是samsung,Board是smdk2410,那board/$(BOARDDIR)展开就是board/samsung/smdk2410。如果你的Vendor是NULL,那BOARDDIR就等于$(BOARD),对应board/smdk2410这种结构。所以你看,boards.cfg里那行配置,不仅决定了配置文件名,还直接决定了源码里board目录的路径查找规则。

在新版本u-boot里,LIBS这个变量被改成了u-boot-libs这种目标,底层逻辑其实差不多,理解了老版本的组织方式,新版本看代码会容易很多。

4.3 链接脚本和start.o的选择逻辑

除了编译哪些库,Makefile还要解决另外两个问题:从哪里找入口代码,用谁的链接脚本。

入口代码就是start.o,它的路径在顶层Makefile里是这样组织的:

OBJS = cpu/$(CPU)/start.o

CPU变量是arm920t时,编译器就会去cpu/arm920t/start.S汇编生成start.o。这个文件是u-boot的第一段代码,负责设置CPU模式、初始化栈指针、关中断、拷贝自身到RAM等一系列最底层的操作。它的实现跟CPU架构强相关,所以放在cpu目录而不是board目录。

链接脚本的选择逻辑也类似。顶层Makefile里有这么一段逻辑:默认链接脚本路径是board/$(BOARDDIR)/u-boot.lds,如果这个文件不存在,则回退到cpu/$(CPU)/u-boot.lds。如果你移植的板卡对内存布局有特殊要求,直接在board目录下放一个自己的u-boot.lds覆盖默认规则即可。

链接脚本决定了u-boot各个段(代码段、数据段、bss段)在内存里的排布,特别是入口地址和加载地址的区别,这部分做DDR初始化调试时至关重要。很多新手看到u-boot编译出来但是go命令跑不起来,一大半原因就是链接脚本里的加载地址和实际运行地址对不上。

5. 移植一块新板的完整实操流程

5.1 修改boards.cfg添加自己的板卡

理论部分讲完了,下面走一遍完整的新板移植流程。假设我要在一块新做的板子上跑u-boot,SoC用的是s3c24x0系列(这只是个例子,换成你自己的SoC型号原理一样),我给它起个代号myboard。

第一步是在boards.cfg里添加一行。最稳妥的做法是找到和你的板子最接近的参考板,复制那一行再改。比如参考板是这样的:

smdk2410 arm arm920t smdk2410 NULL s3c24x0

我就改成:

myboard arm arm920t myboard NULL s3c24x0

注意Target必须和之后的make xxx_config的xxx完全一致,Board name字段决定board目录的名字。这里的字段顺序和数量一定不要搞错,mkconfig解析时是按固定位置取的。

5.2 新建board目录与Makefile

第二步是把参考板的board目录整体复制一份。老办法,复制是最省事的:

cp -r board/samsung/smdk2410 board/myboard

然后进入board/myboard目录,把目录下所有文件里的smdk2410字样全局替换成myboard。这里要注意的是Makefile里的目标名,比如:

obj-y += smdk2410.o

要改成:

obj-y += myboard.o

同时把smdk2410.c改名成myboard.c。

为什么建议复制而不是从零写?因为u-boot的板级代码里藏着大量和具体硬件相关的初始化细节,比如GPIO配置、时钟使能、DDR参数,这些不是看几篇教程就能凭空写对的。基于参考板修改,保留大部分逻辑,只调整差异部分,是效率最高也最不容易出错的方式。

如果板上换了DDR芯片或改了时钟频率,重点要改的文件是lowlevel_init.S里的内存控制器参数,以及myboard.c里的时钟初始化函数。这部分需要对照芯片手册逐项核对,是移植中最耗时的一环。

5.3 编写include/configs/xxx.h

第三步是配置头文件。同样从参考板复制:

cp include/configs/smdk2410.h include/configs/myboard.h

然后重点检查这些宏:

  • CONFIG_SYS_TEXT_BASE:u-boot的链接起始地址,一般等于你的DDR映射地址,如果DDR基地址变了,这里必须改。
  • CONFIG_SYS_LOAD_ADDR:内核加载地址,跟你内核在内存里的位置有关。
  • CONFIG_BOOTCOMMAND:自动启动命令,一般默认从Flash或SD卡读取内核镜像。
  • CONFIG_BOOTARGS:传给内核的启动参数,里面包含内存大小、控制台串口等。

头文件里动辄几百个宏,新手刚接触容易被淹没。我的建议是先把关键的十几项搞清楚,其它保持默认值,等板子能启动到u-boot命令行之后再逐项裁剪优化。

5.4 配置、编译与烧写的完整命令序列

一切文件就绪后,操作序列是这样的:

make distclean make myboard_config make

执行完make myboard_config后,可以快速检查一下配置是否正确:

cat include/config.mk ls -l include/asm

如果输出正常,make编译结束后会生成u-boot.bin。烧写方式取决于板子的启动介质,可能是通过JTAG烧到Nor Flash,可能是通过SD卡烧到MMC,也可能是通过串口用厂商的烧写工具。

这里分享一个实用技巧:交叉编译工具链的版本会影响老版本u-boot的编译。如果你用较新的GCC编译老版本u-boot,经常会在链接阶段遇到莫名其妙的错误。建议先查一下u-boot版本对应的文档或社区说明,选用合适版本的交叉编译器,通常指定CROSS_COMPILE=arm-linux-gnueabi-或arm-none-eabi-就能解决一大部分问题。

6. 常见问题与排查技巧实录

6.1 配置阶段的典型报错

配置阶段报错通常集中在这几类:

现象一:执行make xxx_config报awk: cannot open file boards.cfg

原因是你不在源码根目录,或者源码路径里有空格。u-boot的Makefile默认以当前目录作为源码目录,如果你在子目录里执行配置命令,mkconfig找不到boards.cfg。解决方法是回到根目录执行,或者使用O=指定输出目录时注意路径写法。

现象二:执行完配置命令,却发现include目录下没有生成config.mk

这种情况先查看命令输出,确认是否提示了Configuring for myboard board...。如果提示了但没有生成文件,多半是mkconfig在写文件时缺少目录权限。如果连提示都没有,说明boards.cfg里根本没匹配到你的Target,仔细检查那一行的拼写和空格。

现象三:软链接创建失败,报ln: failed to create symbolic link

最常见的原因是文件系统不支持符号链接,比如Windows共享目录、某些U盘挂载点。还有一种情况是你之前配置过别的板卡,旧链接删不掉。解决办法是把源码放到Linux原生分区,或者先手动清理include目录下的残留软链接。

6.2 编译阶段的典型报错

编译阶段常见问题更多,这里挑最高频的几个说。

现象一:fatal error: asm/arch/xxx.h: No such file or directory

这个错误基本可以断定是asm-arm/arch软链接没建对。比如你用的SoC是s3c24x0,这个链接应该指向arch-s3c24x0。查看一下:

ls -l include/asm-arm/arch

如果指向错误或不存在,重新执行make xxx_config。

现象二:链接时提示undefined reference to '__aeabi_unwind_cpp_pr0'之类的错误

这类错误通常是交叉编译器的ARM EABI选项和u-boot编译参数不匹配。老版本u-boot未必支持新的编译参数,可以在顶层Makefile里找到PLATFORM_CPPFLAGS相关定义,补充或修改对应的浮点、ABI选项。如果工具链实在太新,换一个4.x版本的交叉编译器往往是最快的路子。

现象三:u-boot.bin生成后烧进板子毫无输出

这种情况硬件问题居多,但软件侧要先确认两个地方:串口波特率是不是和CONFIG_SYS_BAUDRATE_TABLE里设置的一致;start.S里的时钟和DDR初始化是否适合你的板子。很多板卡移植第一轮都是“黑屏无输出”,不要急着怀疑工具链或源码,拿示波器量量时钟、测测DDR的CS片选信号,比改代码更有效果。

6.3 查阅配置结果的小技巧

分享几个我平时排查配置问题时常用的手段,能省不少时间。

第一,make -n xxx_config可以“演习”一下配置命令,输出将要执行的每一条命令,不用真的执行就能知道mkconfig要干什么。这在调试mkconfig参数传递时特别有用。

第二,配置完别急着编译,先看三个东西:

cat include/config.mk cat include/config.h ls -l include/asm include/asm-arm/arch include/asm-arm/proc

三行命令能把配置结果完整测一遍。凡是移植过程中出现不明所以的头文件找不到问题,先执行这三条准能发现线索。

第三,如果改了boards.cfg后不生效,记得make distclean再重新配置。cofig.h和config.mk的生成规则是基于时间戳的,但mkconfig里的软链接操作不受make依赖管理约束,残留旧链接会干扰新配置,直接干净重来最省心。

我个人做了这么多板子的移植,最大的体会是:u-boot的配置系统看起来绕,但它把一个非常复杂的问题拆得很清晰。boards.cfg管“有哪些板卡”,mkconfig管“如何生成配置”,顶层Makefile管“如何依据配置编译”,新版本u-boot的Kconfig体系本质上也是沿着这个思路走的,只是把配置源从文本文件换成了更结构化、可交互的Kconfig。把老版本这条链路吃透了,再去接触2014.04之后的make xxx_defconfig流程,你会觉得很多东西都是相通的,只是工具换了。最后再啰嗦一句,刚开始移植时尽量找一块和你的板子最接近的参考板,先把u-boot跑起来,再做减法优化,这比从零开始写要靠谱得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 1:12:38

基于MP2642与STM32的两串电池主动均衡方案设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:11:51

基于DataBinding的RecyclerView Adapter基类封装与列表优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:11:49

POJ 1700快速渡河问题:贪心策略经典解析与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:11:25

FastAdmin 实战避坑手册:权限、CRUD、API 接口高频问题全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:11:03

基于MP2642GR-Z与TM4C1294的被动均衡BMS设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:10:38

豆瓣Top250爬虫实战:从请求伪装到数据落库的完整工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华