Skip to content

LV105-xxx-defconfig过程分析

在编译 uboot 之前要使用“make xxx_defconfig”命令来配置 uboot,那么这个配置过程是如何运行的呢?接下来就来了解一下吧。这个我们从这里开始:Makefile

一、版本与时间

1. version_h

先来看一些这个变量,它在 Makefile 中定义如下:

makefile
version_h := include/generated/version_autogenerated.h

这个变量保存版本号文件,此文件是自动生成的。文件 include/generated/version_autogenerated.h (编译后才会生成)内容如下:

c
#define PLAIN_VERSION "2019.04-gf32bcf5b"
#define U_BOOT_VERSION "U-Boot " PLAIN_VERSION
#define CC_VERSION_STRING "arm-linux-gnueabihf-gcc (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 8.3.0"
#define LD_VERSION_STRING "GNU ld (GNU Toolchain for the A-profile Architecture 8.3-2019.03 (arm-rel-8.36)) 2.32.0.20190321"

2. timestamp_h

变量 timestamp_h 在 Makefile 中定义如下:

makefile
timestamp_h := include/generated/timestamp_autogenerated.h

这个变量保存时间戳文件,此文件也是编译自动生成的。文件 include/generated/timestamp_autogenerated.h 内容如下:

c
#define U_BOOT_DATE "Nov 19 2024"
#define U_BOOT_TIME "21:45:10"
#define U_BOOT_TZ "+0800"
#define U_BOOT_DMI_DATE "11/19/2024"
#define U_BOOT_BUILD_DATE 0x20241119

二、三个变量

接下来看几个变量,定义在 Makefile

makefile
no-dot-config-targets := clean clobber mrproper distclean \
			 help %docs check% coccicheck \
			 ubootversion backup tests check qcheck

config-targets := 0
mixed-targets  := 0
dot-config     := 1

ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
	ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
		dot-config := 0
	endif
endif

ifeq ($(KBUILD_EXTMOD),)
        ifneq ($(filter config %config,$(MAKECMDGOALS)),)
                config-targets := 1
                ifneq ($(words $(MAKECMDGOALS)),1)
                        mixed-targets := 1
                endif
        endif
endif

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

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

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

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

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

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

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

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

makefile
$(words <text>)

MAKECMDGOALS 的单词个数是 1 个,所以条件不成立,mixed-targets 继续为 0。

综上所述,这些变量值如下:

makefile
config-targets = 1
mixed-targets = 0
dot-config = 1

三、三个变量的使用

上面确定了那三个变量的值,我们继续往下看 Makefile

makefile
ifeq ($(mixed-targets),1)
# (1)......
else
# (2)......
endif

如果变量 mixed-targets 为 1 的话条件成立, 这里显然不成立了,所以会走 2)这里,(2)这里在 Makefile 中是:

makefile
ifeq ($(config-targets),1)
# (2.1)......
else
# (2.2)......
endif

如果变量 config-targets 为 1 的话条件成立,很明显,条件成立,执行这个(2.1)分支。这个(2.1)在 Makefile 中的代码,我们下面接着分析。

四、%config

我们来看一下上面(2.1)在 Makefile 中的代码:

makefile
# *config targets only - make sure prerequisites are updated, and descend
# in scripts/kconfig to make the *config target

KBUILD_DEFCONFIG := sandbox_defconfig
export KBUILD_DEFCONFIG KBUILD_KCONFIG

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

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

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

第 10 行,有目标与之匹配,当输入“make xxx_defconfig”的时候就会匹配到%config 目标,目标“%config”依赖于 scripts_basic、 outputmakefile 和 FORCE。

1. FORCE

FORCE 在顶层 Makefile 中定义如下:

makefile
PHONY += FORCE
FORCE:

# Declare the contents of the .PHONY variable as phony.  We keep that
# information in a variable so we can use it in if_changed and friends.
.PHONY: $(PHONY)

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

2. scripts_basic

依赖 scripts_basic 在 Makefile 中定义如下:

makefile
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

# To avoid any implicit rule to kick in, define an empty command.
scripts/basic/%: scripts_basic ;

这是 scripts_basic 的规则,其对应的命令用到了变量 Q、MAKE 和 build,其中:

txt
Q=@或为空
MAKE=make

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

txt
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

经过前面的分析 可知,变量 srctree(Makefile · srctree) 为”.”,所以有:

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

scripts_basic 展开以后如下:

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

scripts_basic 会调用文件./scripts/Makefile.build,这个我们后面在分析。

3. %config 展开

上面的几个依赖我们都找到了,接下来吧%config 展开:

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

把 build 的值替换展开后是:

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

发现同样也跟文件./scripts/Makefile.build 有关,我们后面再分析此文件。

五、编译信息

我们在 uboot 源码目录下执行以下命令:

shell
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig

主要是看第二条命令的打印信息:

image-20241119220727685

可以看到里面有两条 make -f 开头的命令:

  • scripts_basic 目标对应的命令
shell
make -f ./scripts/Makefile.build obj=scripts/basic
  • %config 目标对应的命令
shell
make -f ./scripts/Makefile.build obj=scripts/kconfig mx6ull_14x14_evk_defconfig

六、Makefile.build 脚本

接下来我们就来看一下 Makefile.build 脚本里面都有什么。从前边知道“ make xxx_defconfig“配置 uboot 的时候如下两行命令会执行脚本 scripts/Makefile.build:

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

1. scripts_basic 目标对应的命令

scripts_basic 目标对应的命令为:

makefile
 @make -f ./scripts/Makefile.build obj=scripts/basic
  • src 与 prefix 变量

先看一下 scripts/Makefile.build 最开始的两个变量:

makefile
# Modified for U-Boot
prefix := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif

第 2 行,变量 prefix 值为 tpl。

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

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

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

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

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

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

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

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

  • kbuild-dir 与 kbuild-file

我们继续往下看 scripts/Makefile.build,这里有两个变量:

makefile
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

我们前面分析过 srctree =.,src = scripts/basic, 将 kbuild-dir 展开后为:

makefile
kbuild-dir := $(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic)

因为没有以“/”为开头的单词,所以$(filter /%, scripts/basic)的结果为空,kbuild-dir =./scripts/basic。得到这个变量后,我们将 kbuild-file 展开后为:

makefile
kbuild-file := $(if $(wildcard ./scripts/basic/Kbuild), ./scripts/basic/Kbuild, ./scripts/basic/Makefile)

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

makefile
include ./scripts/basic/Makefile

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

  • __build

我们继续向下看 scripts/Makefile.build,会看到这个__build:

makefile
# We keep a list of all modules in $(MODVERDIR)

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
	 $(subdir-ym) $(always)
	@:

__build 是默认目标,因为下面这条命令

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

没有指定目标,所以会使用到默认目标:__build。在顶层 Makefile 中,KBUILD_BUILTIN 为 1,KBUILD_MODULES 为 0,因此展开后目标__build 为:

makefile
__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 中输入以下代码:

makefile
# We keep a list of all modules in $(MODVERDIR)

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
	 $(subdir-ym) $(always)
	@:
	@echo "builtin-target="$(builtin-target) 
	@echo "lib-target="$(lib-target) 
	@echo "extra-y="$(extra-y)
	@echo "subdir-ym="$(subdir-ym) 
	@echo "always="$(always)

然后我重新执行以下命令:

shell
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig

会看到有如下打印信息出现:

shell
builtin-target=
lib-target=
extra-y=
subdir-ym=
always=scripts/basic/fixdep

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

makefile
__build:scripts/basic/fixdep
	@:

__build 依赖于 scripts/basic/fixdep,所以要先编译 scripts/basic/fixdep.c,生成 fixdep,前面已经读取了 scripts/basic/Makefile 文件。

总的来说 scripts_basic 目标的作用就是编译出 scripts/basic/fixdep 这个软件。

2. %config 目标对应的命令

%config 目标对应的命令为:

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

按照前面的分析,相关的变量值如下所示:

txt
src= scripts/kconfig
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile

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

makefile
%_defconfig: $(obj)/conf
	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

# Added for U-Boot (backward compatibility)
%_config: %_defconfig
	@:

这几行在 scripts/kconfig/Makefile · %_defconfig 附近。目标%_defconfig 刚好和我们输入的 xxx_defconfig 匹配,所以会执行这条规则。依赖为 $(obj)/conf,展开后就是 scripts/kconfig/conf。

接下来就是检查并生成依赖 scripts/kconfig/conf。 conf 是主机软件,到这里我们就停下来,不要纠结 conf 是怎么编译出来的,否则就越陷越深,太绕了,像 conf 这种主机所使用的工具类软件我们一般不关心它是如何编译产生的。如果一定要看是 conf 是怎么生成的,可以输入如下命令重新配置 uboot,在重新配置 uboot 的过程中就会 输出 conf 编译信息。

shell
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig

会有下面的打印信息:

shell
# ......
  cc -Wp,-MD,scripts/kconfig/.zconf.tab.o.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer  -std=gnu11   -D_GNU_SOURCE -D_DEFAULT_SOURCE -I/usr/include/ncursesw -DCURSES_LOC="<ncurses.h>" -DNCURSES_WIDECHAR=1 -DLOCALE  -Iscripts/kconfig -c -o scripts/kconfig/zconf.tab.o scripts/kconfig/zconf.tab.c
  cc  -o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o
scripts/kconfig/conf  --defconfig=arch/../configs/mx6ull_14x14_evk_defconfig Kconfig
#


# configuration written to .config
#

这里的 cc -o scripts/kconfig/conf 这行就是在编译生成 conf 软件。得到 scripts/kconfig/conf 以后就要执行目标%_defconfig 的命令:

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

相关的变量值如下:

txt
silent=-s 或为空
SRCARCH=..
Kconfig=Kconfig

将其展开就是:

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

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

七、总结一下

image-20241119221119307