LV05-01-uboot-06-顶层Makefile-02-相关过程分析

本文主要是学习uboot顶层Makefile的相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
Win32DiskImager Win32DiskImager v1.0
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
https://elixir.bootlin.com/linux/latest/source 在线阅读linux kernel源码
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)

一、make xxx_defconfig 过程

在编译 uboot 之前要使用“make xxx_defconfig”命令来配置 uboot,那么这个配置过程是如何运行的呢?

1. 顶层Makefile内容

1.1 422~437行

image-20230730122233164

第 422 行定义了变量 version_h,这变量保存版本号文件,此文件是自动生成的。文件 include/generated/version_autogenerated.h(编译后才会生成)内容如下所示:

1
2
3
4
#define PLAIN_VERSION "2016.03-g7fd772f"
#define U_BOOT_VERSION "U-Boot " PLAIN_VERSION
#define CC_VERSION_STRING "arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2017.01) 4.9.4"
#define LD_VERSION_STRING "GNU ld (Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git"

第 423 行定义了变量 timestamp_h,此变量保存时间戳文件,此文件也是自动生成的。文件include/generated/timestamp_autogenerated.h 内容如下:

1
2
3
4
#define U_BOOT_DATE "Jul 30 2023"
#define U_BOOT_TIME "12:23:59"
#define U_BOOT_TZ "+0800"
#define U_BOOT_DMI_DATE "07/30/2023"

第 425 行定义了变量 no-dot-config-targets。

第 429 行定义了变量 config-targets,初始值为 0。

第 430 行定义了变量 mixed-targets,初始值为 0。

第 431 行定义了变量 dot-config,初始值为 1。

第 433 行将 MAKECMDGOALS 中不符合 no-dot-config-targets 的部分过滤掉,剩下的如果不为空的话条件就成立。 MAKECMDGOALS 是 make 的一个环境变量,这个变量会保存我们所指定的终极目标列表,比如执行“make mx6ull_14x14_evk_emmc_defconfig”,那么 MAKECMDGOALS就为mx6ull_14x14_evk_emmc_defconfig。很明显过滤后为空,所以条件不成立,变量 dot-config 依旧为 1。

1.2 439~446行

image-20230730122747654

第439行判断KBUILD_EXTMOD是否为空,如果KBUILD_EXTMOD为空的话条件成立,经过前面的分析,我们知道 KBUILD_EXTMOD 为空,所以条件成立。

第 440 行将 MAKECMDGOALS 中不符合“config”和“%config”的部分过滤掉,如果剩下的部分不为空条件就成立,很明显此处条件成立,变量 config-targets=1。

第 442 行统计 MAKECMDGOALS 中的单词个数,如果不为 1 的话条件成立。此处调用Makefile 中的 words 函数来统计单词个数, words 函数格式如下:

1
$(words <text>)

很明显, MAKECMDGOALS 的单词个数是 1 个,所以条件不成立, mixed-targets 继续为0。综上所述,这些变量值如下:

1
2
3
config-targets = 1
mixed-targets = 0
dot-config = 1

1.3 448~527行

image-20230730123504197

第 448 行如果变量 mixed-targets 为 1 的话条件成立,很明显,条件不成立。

第 465 行如果变量 config-targets 为 1 的话条件成立,很明显,条件成立,执行这个分支。

第 473 行,没有目标与之匹配,所以不执行。

第 476 行,有目标与之匹配,当输入“make xxx_defconfig”的时候就会匹配到%config 目标,目标“%config”依赖于 scripts_basic、 outputmakefile 和 FORCE。 FORCE 在顶层 Makefile的 1610 行有如下定义:

1
2
PHONY += FORCE
FORCE:

可以看出 FORCE 是没有规则和依赖的,所以每次都会重新生成 FORCE。当 FORCE 作为其他目标的依赖时,由于 FORCE 总是被更新过的,因此依赖所在的规则总是会执行的。

scripts_basic 和 outputmakefile

依赖 scripts_basic 和 outputmakefile 在顶层 Makefile 中的内容如下

image-20230730133422636

第 408 行,判断 KBUILD_SRC 是否为空,只有变量 KBUILD_SRC 不为空的时候outputmakefile 才有意义,经过我们前面的分析 KBUILD_SRC 为空,所以 outputmakefile 无效。只有 scripts_basic 是有效的。

第 396~398 行是 scripts_basic 的规则,其对应的命令用到了变量 Q、 MAKE 和 build,其中:

1
2
Q=@或为空
MAKE=make

变量 build 是在 scripts/Kbuild.include 文件中有定义,定义如下:

image-20230730133624015

从上图的示例代码(Kbuild.include 代码段)可以看出 build=-f $(srctree)/scripts/Makefile.build obj,经过前面的分析可知,变量 srctree 为”.”,因此:

1
build=-f ./scripts/Makefile.build obj

scripts_basic 展开以后如下:

1
2
3
scripts_basic:
@make -f ./scripts/Makefile.build obj=scripts/basic # 也可以没有@,视配置而定
@rm -f . tmp_quiet_recordmcount #也可以没有@

scripts_basic 会调用文件./scripts/Makefile.build 这个后边会再分析。

接着回到继续看上边顶层Makefile中的%config 处,内容如下:

1
2
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@

将命令展开就是 :

1
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

同样也跟文件./scripts/Makefile.build 有关,我们后面再分析此文件。使用如下命令配置 uboot,并观察其配置过程:

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig

然后就可以看到如下配置过程:

image-20230730134520374

(1)scripts_basic 目标对应的命令

1
@make -f ./scripts/Makefile.build obj=scripts/basic

(2) %config 目标对应的命令

1
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig 

2. Makefile.build 脚本分析

从前边知道“ make xxx_defconfig“配置 uboot 的时候如下两行命令会执行脚本scripts/Makefile.build:

1
2
@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig

2.1  scripts_basic 目标对应的命令

2.1.1 8~17行

scripts_basic 目标对应的命令为: @make -f ./scripts/Makefile.build obj=scripts/basic。打开文件 scripts/Makefile.build,有如下代码:

image-20230730134805520

第 9 行定义了变量 prefix 值为 tpl。

第 10 行定义了变量 src,这里用到了函数 patsubst,此行代码展开后为:

1
$(patsubst tpl/%,%, scripts/basic)

patsubst 是替换函数,格式如下:

1
$(patsubst <pattern>,<replacement>,<text>)

此函数用于在 text 中查找符合 pattern 的部分,如果匹配的话就用 replacement 替换掉。pattenr 是可以包含通配符“%”,如果 replacement 中也包含通配符“%”,那么 replacement 中的这个“%”将是 pattern 中的那个“%”所代表的字符串。函数的返回值为替换后的字符串。因此,第 10 行就是在“scripts/basic”中查找符合“tpl/%”的部分,然后将“tpl/”取消掉,但是“scripts/basic”没有“tpl/”,所以 src= scripts/basic。

第 11 行判断变量 obj 和 src 是否相等,相等的话条件成立,很明显,此处条件成立。

第 12 行和第 9 行一样,只是这里处理的是“spl”,“scripts/basic”里面也没有“spl/”,所以src 继续为 scripts/basic。

第 15 行因为变量 obj 和 src 相等,所以 prefix=.。

2.1.2 56~59行

image-20230730135048080

将 kbuild-dir 展开后为:

1
$(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic)

因为没有以“ / ”为开头的单词,所以$(filter /%, scripts/basic)的结果为空, kbuilddir=./scripts/basic。

将 kbuild-file 展开后为:

1
$(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile)

因为 scrpts/basic 目录中没有 Kbuild 这个文件,所以 kbuild-file= ./scripts/basic/Makefile。最后将 59 行展开,即:

1
include ./scripts/basic/Makefile

也就是读取 scripts/basic 下面的 Makefile 文件。

2.1.3 116~119行

image-20230730135331282

__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标: __build。在顶层 Makefile 中, KBUILD_BUILTIN 为 1,KBUILD_MODULES 为 0,因此展开后目标__build 为:

1
2
__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:

可以看出目标__build 有 5 个依赖: builtin-target、 lib-target、 extra-y、 subdir-ym 和 always。这 5 个依赖的具体内容我们就不通过源码来分析了,直接在 scripts/Makefile.build 中输入图所示内容,将这 5 个变量的值打印出来:

image-20230730135605771

执行以下命令:

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
image-20230730135721373

从上图可以看出,只有 always 有效,因此__build 最终为:

1
2
__build: scripts/basic/fixdep
@:

__build 依赖于 scripts/basic/fixdep,所以要先编译 scripts/basic/fixdep.c,生成 fixdep,前面已经读取了 scripts/basic/Makefile 文件。综上所述, scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 这个软件。

2.2 %config 目标对应的命令

%config 目 标 对 应 的 命 令 为 :

1
@make -f ./scripts/Makefile.build obj=scripts/kconfigxxx_defconfig

各个变量值如下:

1
2
3
4
src= scripts/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile

可以看出, Makefilke.build 会读取 scripts/kconfig/Makefile 中的内容,此文件有如下所示内容:

image-20230730140006404

目标%_defconfig 刚好和我们输入的 xxx_defconfig 匹配,所以会执行这条规则。依赖为$(obj)/conf,展开后就是 scripts/kconfig/conf。接下来就是检查并生成依赖 scripts/kconfig/conf。conf 是主机软件,到这里我们就打住,不要纠结 conf 是怎么编译出来的,否则就越陷越深,太绕了,像 conf 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是 conf 是怎么生成的,可以输入如下命令重新配置 uboot,在重新配置 uboot 的过程中就会输出 conf 编译信息。

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
image-20230730140144991

得到 scripts/kconfig/conf 以后就要执行目标%_defconfig 的命令:

1
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

相关的变量值如下:

1
2
3
silent=-s 或为空
SRCARCH=..
Kconfig=Kconfig

将其展开就是 :

1
@ scripts/kconfig/conf --defconfig=arch/../configs/xxx_defconfig Kconfig

上述命令用到了 xxx_defconfig 文件,比如 mx6ull_14x14_evk_emmc_defconfig 。这里会将mx6ull_14x14_evk_emmc_defconfig 中的配置输出到.config 文件中,最终生成 uboot 根目录下的.config 文件。

3. 过程总结

这个就是命令 make xxx_defconfig 执行流程 :

image-20230730140440227

二、 make过程分析

1. 目标

配置好 uboot 以后就可以直接 make 编译了,因为没有指明目标,所以会使用默认目标,主Makefile 中的默认目标如下:

image-20230730140953313

目标_all 又依赖于 all,如下所示 :

image-20230730141035174

如果 KBUILD_EXTMOD 为空的话 _all 依赖于 all 。这里不编译模块,所 以KBUILD_EXTMOD 肯定为空, _all 的依赖就是 all。在主 Makefile 中 all 目标规则如下:

image-20230730141153467

从 802 行可以看出, all 目标依赖$(ALL-y),而在顶层 Makefile 中, ALL-y 如下 :

image-20230730141403808

可以看出, ALL-y 包含 u-boot.srec、 u-boot.bin、 u-boot.sym、System.map、 u-boot.cfg 和 binary_size_check 这几个文件。根据 uboot 的配置情况也可能包含其他的文件,比如:

1
ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin

CONFIG_ONENAND_U_BOOT 就是 uboot 中跟 ONENAND 配置有关的,如果我们使能了ONENAND,那么在.config 配置文件中就会有“CONFIG_ONENAND_U_BOOT=y”这一句。相当于 CONFIG_ONENAND_U_BOOT 是个变量,这个变量的值为“y”,所以展开以后就是:

1
ALL-y += u-boot-onenand.bin

这个就是.config 里面的配置参数的含义,这些参数其实都是变量,后面跟着变量值,会在顶层 Makefile 或者其他 Makefile 中调用这些变量。

2. u-boot.bin

ALL-y 里面有个 u-boot.bin,这个就是我们最终需要的 uboot 二进制可执行文件,所作的所有工作就是为了它。在顶层 Makefile 中找到 u-boot.bin 目标对应的规则,如下所示:

image-20230730141540677

第 825 行判断 CONFIG_OF_SEPARATE 是否等于 y,如果相等,那条件就成立,在.config中搜索“CONFIG_OF_SEPARAT”,没有找到,说明条件不成立。

第 832 行就是目标 u-boot.bin 的规则,目标 u-boot.bin 依赖于 u-boot-nodtb.bin,命令为$(call if_changed,copy) , 这 里 调 用 了 if_changed , if_changed 是 一 个 函 数 , 这个函数在 scripts/Kbuild.include 中有定义,而顶层 Makefile 中会包含 scripts/Kbuild.include 文件,这个前面已经说过了。 if_changed 在 Kbuild.include 中的定义如下:

点击查看if_changed 定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
226 ###
227 # if_changed - execute command if any prerequisite is newer than
228 # target, or command line has changed
229 # if_changed_dep - as if_changed, but uses fixdep to reveal dependencies
230 # including used config symbols
231 # if_changed_rule - as if_changed but execute rule instead
232 # See Documentation/kbuild/makefiles.txt for more info
233
234 ifneq ($(KBUILD_NOCMDDEP),1)
235 # Check if both arguments has same arguments. Result is empty string if equal.
236 # User may override this check using make KBUILD_NOCMDDEP=1
237 arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
238 $(filter-out $(cmd_$@), $(cmd_$(1))) )
239 else
240 arg-check = $(if $(strip $(cmd_$@)),,1)
241 endif
242
243 # Replace >$< with >$$< to preserve $ when reloading the .cmd file
244 # (needed for make)
245 # Replace >#< with >\#< to avoid starting a comment in the .cmd file
246 # (needed for make)
247 # Replace >'< with >'\''< to be able to enclose the whole string in '...'
248 # (needed for the shell)
249 make-cmd = $(call escsq,$(subst \#,\\\#,$(subst $$,$$$$,$(cmd_$(1)))))
250
251 # Find any prerequisites that is newer than target or that does not exist.
252 # PHONY targets skipped in both cases.
253 any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)
254
255 # Execute command if command has changed or prerequisite(s) are updated.
256 #
257 if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
258 @set -e; \
259 $(echo-cmd) $(cmd_$(1)); \
260 printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
261

第 227 行为 if_changed 的描述,根据描述,在一些先决条件比目标新的时候,或者命令行有改变的时候, if_changed 就会执行一些命令。

第 257 行就是函数 if_changed, if_changed 函数引用的变量比较多,也比较绕,我们只需要知道它可以从 u-boot-nodtb.bin 生成 u-boot.bin 就行了。

3. u-boot-nodtb.bin

既然 u-boot.bin 依赖于 u-boot-nodtb.bin,那么肯定要先生成 u-boot-nodtb.bin 文件,顶层Makefile 中相关代码如下:

image-20230730142244988

目标 u-boot-nodtb.bin 又依赖于 u-boot,顶层 Makefile 中 u-boot 相关规则如下:

image-20230730142326658

目标 u-boot 依赖于 u-boot_init、 u-boot-main 和 u-boot.lds, u-boot_init 和 u-boot-main 是两个变量,在顶层 Makefile 中有定义,值如下:

image-20230730142402882

$(head-y)跟 CPU 架构有关,我们使用的是 ARM 芯片,所以 head-y 在 arch/arm/Makefile 中被指定为:

1
head-y := arch/arm/cpu/$(CPU)/start.o

根据《LV05-01-uboot-06-顶层Makefile-01-基础分析》——六、变量分析一小节的分析,我们知道 CPU=armv7,因此 head-y 展开以后就是:

1
head-y := arch/arm/cpu/armv7/start.o

所以就有:

1
u-boot-init= arch/arm/cpu/armv7/start.o

$(libs-y)在顶层 Makefile 中被定义为 uboot 所有子目录下 build-in.o 的集合,代码如下:

image-20230730142755244

从上面的代码可以看出, libs-y 都是 uboot 各子目录的集合,最后:

1
libs-y := $(patsubst %/, %/built-in.o, $(libs-y))

这里调用了函数 patsubst,将 libs-y 中的“/”替换为”/built-in.o”,比如“drivers/dma/”就变为了“drivers/dma/built-in.o”,相当于将 libs-y 改为所有子目录中 built-in.o 文件的集合。那么 uboot-main 就等于所有子目录中 built-in.o 的集合。

这个规则就相当于将以 u-boot.lds 为链接脚本,将 arch/arm/cpu/armv7/start.o 和各个子目录下的 built-in.o 链接在一起生成 u-boot。u-boot.lds 的规则如下:

image-20230730143037482

接下来的重点就是各子目录下的 built-in.o 是怎么生成的,以 drivers/gpio/built-in.o 为例,在drivers/gpio/目录下会有个名为.built-in.o.cmd 的文件:

image-20230730143302250

此文件内容如下:

1
cmd_drivers/gpio/built-in.o :=  arm-linux-gnueabihf-ld.bfd   -r -o drivers/gpio/built-in.o drivers/gpio/mxc_gpio.o

从命令“cmd_drivers/gpio/built-in.o”可以看出, drivers/gpio/built-in.o 这个文件是使用 ld 命令由文件 drivers/gpio/mxc_gpio.o 生成而来的,mxc_gpio.o 是 mxc_gpio.c 编译生成的.o 文件,这个是 NXP 的 I.MX 系列的 GPIO 驱动文件。这里用到了 ld 的“-r”参数,参数含义如下:-r –relocateable: 产生可重定向的输出,比如,产生一个输出文件它可再次作为‘ld’ 的输入,这经常被叫做“部分链接”,当我们需要将几个小的.o 文件链接成为一个.o 文件的时候,需要使用此选项。

最终将各个子目录中的 built-in.o 文件链接在一起就形成了 u-boot,使用如下命令编译 uboot就可以看到链接的过程:

1
2
3
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
image-20230730143707862

可以看出最终是用 arm-linux-gnueabihf-ld.bfd 命令将 arch/arm/cpu/armv7/start.o 和其他众多的 built_in.o 链接在一起,形成 u-boot。目标 all 除了 u-boot.bin 以外还有其他的依赖,比如 u-boot.srec 、 u-boot.sym 、 System.map、u-boot.cfg 和 binary_size_check 等等,这些依赖的生成方法和 u-boot.bin 很类似,这里就不再详细说明了。

4. 过程总结

image-20230730143850408

make xxx_defconfig: 用于配置 uboot,这个命令最主要的目的就是生成.config 文件。
make:用于编译 uboot,这个命令的主要工作就是生成二进制的 u-boot.bin 文件和其他的一些与 uboot 有关的文件,比如 u-boot.imx 等等。