uboot-编译defconfig分析

1 版本号#

image
Top Makefile的开头会有版本描述,VERSION 是主版本号,PATCHLEVEL 是补丁版本号,SUBLEVEL 是次版本号,这三个一 起构成了 uboot 的版本号,比如当前的 uboot 版本号就是2016.03EXTRAVERSION 是附加 版本信息,NAME 是和名字有关的,一般不使用这两个。

2 MAKEFLAGS#

image
有两个特殊的变量:SHELLMAKEFLAGS,这两个变量除非使用unexport声明, 否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。
MAKEFLAGS 追加了一些值,-rR表示禁止使用内置的隐 含规则和变量定义,--include-dir指明搜索路径,$(CURDIR)表示当前目录。
image

3 设置命令输出详细程度#

①uboot 默认编译使用短命令。
image
②设置变量V=1来实现完整的命令输出。
image
image
make -s设置成静默输出,将会silent_输出,不打印任何提示信息。如下:
image
MAKE_VERSION就是make版本号,我这里是4.2.1。因此filter 4.%,$(MAKE_VERSION)得到的过滤结果不为空。
image

当使用make -s编译的时候,-s会作为 MAKEFLAGS 变量的一部分传递给 Makefile。因此$(firstword x$(MAKEFLAGS))得到xrRs,最后quiet=silent_,否则不使用silent_输出。最后使用 export 导出变量 quiet、Q 和 KBUILD_VERBOSE
image
image

4 设置编译结果输出位置#

在 make 的时候使用O来指定 输出目录,这么做是为了将源文件 和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在 同一个目录内。
image
判断O是否来自于命令行,如果来自命令行的话,KBUILD_OUTPUT 就为$(O),也就是输出目录。
image
一开始判断KBUILD_OUTPUT 是否为空。 如果指定了输出目录就调用 mkdir 命令创建目录。

5 代码检查#

命令make C=1使能代码检查,检查那些需要重新编译的文 件。make C=2用于检查所有的源码文件。
image

6 子模块编译#

使用命令make M=dir即可,旧语法make SUBDIRS=dir也是支持的。
image
如果定 义 了 SUBDIRS或者M,那么KBUILD_EXTMOD就会被赋值。然后进入目标_all 依赖 modules,要先编译出 modules,也就是编译模块。
否则KBUILD_EXTMOD为空,进入all编译。
判断KBUILD_SRC是否为空,如果为空的话就设置变量srctree为当前目录,否则srctree就是KBUILD_SRC。一般不设置 KBUILD_SRC
设置变量src obj,都为当前目录,设置VPATH,导出量 scrtree、objtree 和 VPATH

7 获取主机架构系统信息#

image
最终开发服务器主机架构和操作系统信息如下:
image

8 设置目标架构、工具链和配置文件#

HOSTARCH是x86_64,我们编译make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-就是用于设置目标 ARCH 和 CROSS_COMPILE
image
KCONFIG_CONFIG,这里设置配置文件为.config,.config 默认是没有的,需要使用命令make xxx_defconfig 对 uboot 进行配置,配置完成以后就会在 uboot 根目录下生成.config
设置主机编译器HOSTCC,HOSTCXX等。

9 调用 scripts/Kbuild.include#

在整个Kbuild系统中,scripts/Kbuild.include 提供了大量通用函数以及变量的定义,这些定义将被 Makefile.build 、Makefile.lib 和 top Makefile频繁调用,以实现相应的功能,scripts/Kbuild.include 参与整个内核编译的过程,是编译的核心脚本之一。

image
Kbuild.include定义了很多变量:
image
里面定了build变量,后面产生配置文件时会用到:
image

9.1 build变量#

build := -f $(srctree)/scripts/Makefile.build obj

例如:

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

当我们执行make menuconfig时:

1
2
menuconfig: scripts_basic outputmakefile FORCE
make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig

9.1.1 调用scripts/Makefile.build#

参见后面12.3.1.1节再详细介绍。

build变量相类似的,还有以下的编译指令:

$(modbuiltin):

1
modbuiltin := -f $(srctree)/scripts/Makefile.modbuiltin obj

$(dtbinst):

1
dtbinst := -f $(srctree)/scripts/Makefile.dtbinst obj

$(clean):

1
clean := -f $(srctree)/scripts/Makefile.clean obj

$(hdr-inst):

1
hdr-inst := -f $(srctree)/scripts/Makefile.headersinst obj

这四条指令,分别对应 內建模块编译dtb文件安装目标清除头文件安装.

9.2

9.2 filechk变量#

1
2
3
4
5
6
7
8
9
10
11
define filechk
$(Q)set -e; \
mkdir -p $(dir $@); \
{ $(filechk_$(1)); } > $@.tmp; \
if [ -r $@ ] && cmp -s $@ $@.tmp; then \
rm -f $@.tmp; \
else \
$(kecho) ' UPD $@'; \
mv -f $@.tmp $@; \
fi
endef
  1. mkdir -p $(dir $@):如果$@目录不存在,就创建目录,$@是编译规则中的目标部分。($@ 在 Makefile 表目标文件)
  2. 执行filechk_$(1),然后将执行结果保存到 $@.tmp
  3. 对比$@.tmp$@是否有更新,有更新就使用 $@.tmp,否则删除 $@.tmp

9.3 if_changed变量#

下一节有详细解析介绍:

uboot顶层makefile-2编译过程 - fuzidage - 博客园 (cnblogs.com)

uboot-编译过程Make分析 | Hexo (fuzidage.github.io)

9.4 scripts/Makefile.lib文件作用#

9.4.1 定义编译选项#

1
2
3
4
5
6
7
asflags-y  += $(EXTRA_AFLAGS)
ccflags-y += $(EXTRA_CFLAGS)
cppflags-y += $(EXTRA_CPPFLAGS)
ldflags-y += $(EXTRA_LDFLAGS)

KBUILD_AFLAGS += $(subdir-asflags-y)
KBUILD_CFLAGS += $(subdir-ccflags-y

9.4.2 去重#

如果某个模块已经被定义在obj-y中,就没必要再编译了。

1
2
3
4
# 去除obj-m中已经定义在obj-y中的部分
obj-m := $(filter-out $(obj-y),$(obj-m))
# 去除lib-y中已经定义在obj-y中的部分
lib-y := $(filter-out $(obj-y), $(sort $(lib-y) $(lib-m)))

9.4.3 modules.order#

1
2
3
4
#将obj-y中的目录 dir 修改为 dir/modules.order赋值给modorder,
#将obj-m中的.o修改为.ko赋值给modorder。
modorder := $(patsubst %/,%/modules.order,\
$(filter %/, $(obj-y)) $(obj-m:.o=.ko))

内核将编译的外部模块全部记录在 modules.order 文件中,以便 modprobe 命令在加载卸载时查询使用。

1
2
3
kernel/drivers/input/mouse/psmouse.ko
kernel/drivers/input/misc/100ask_adxl345-spi.ko
kernel/drivers/input/evbug.ko

9.4.4 目录的处理#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#挑选出obj-y 和 obj-m 中的纯目录部分,然后添加到subdir-y和subdir-m中。  
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)

__subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m)))
subdir-m += $(__subdir-m)

#需要被递归搜寻的子路径,带有可编译内部和外部模块的子目录。
subdir-ym := $(sort $(subdir-y) $(subdir-m))

#obj-y 中纯目录部分则将其改名为dir/build-in.a,obj-y的其他部分则不变。
obj-y := $(patsubst %/, %/built-in.a, $(obj-y))

#将obj-m中的纯目录部分剔除掉(因为已经在上面加入到subdir-m中了)。
obj-m := $(filter-out %/, $(obj-m))

obj-y 和 obj-m 的定义中同时夹杂着目标文件和目标文件夹,文件夹当然是不能直接参与编译的,所以需要将文件夹提取出来。

obj-y or obj-m中以"/"结尾的纯目录部分提取出来,并赋值给 subdir-ym.

9.4.5 设备树相关#

1
2
3
4
5
6
7
extra-y             += $(dtb-y)
extra-$(CONFIG_OF_ALL_DTBS) += $(dtb-)

ifneq ($(CHECK_DTBS),)
extra-y += $(patsubst %.dtb,%.dt.yaml, $(dtb-y))
extra-$(CONFIG_OF_ALL_DTBS) += $(patsubst %.dtb,%.dt.yaml, $(dtb-))
endif

将所有的dtb-y赋值给extra-y

9.4.6 添加路径#

1
2
3
4
5
6
7
8
9
10
11
12
extra-y     := $(addprefix $(obj)/,$(extra-y))
always := $(addprefix $(obj)/,$(always))
targets := $(addprefix $(obj)/,$(targets))
modorder := $(addprefix $(obj)/,$(modorder))
obj-m := $(addprefix $(obj)/,$(obj-m))
lib-y := $(addprefix $(obj)/,$(lib-y))
subdir-obj-y := $(addprefix $(obj)/,$(subdir-obj-y))
real-obj-y := $(addprefix $(obj)/,$(real-obj-y))
real-obj-m := $(addprefix $(obj)/,$(real-obj-m))
single-used-m := $(addprefix $(obj)/,$(single-used-m))
multi-used-m := $(addprefix $(obj)/,$(multi-used-m))
subdir-ym := $(addprefix $(obj)/,$(subdir-ym))

文件的处理最后,给所有的变量加上相应的路径,以便编译的时候进行索引。

Makefile.lib 通常都被包含在于 Makefile.build中,这个变量继承了Makefile.buildobj 变量。而 Makefile.build obj变量则是通过调用 $(build) 时进行赋值的。

9.5 scripts/Makefile.build文件作用#

9.5.1 包含 include/config/auto.conf#

包含include/config/auto.conf文件,这个文件的内容是这样的:

1
2
3
4
5
6
CONFIG_RING_BUFFER=y
CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
CONFIG_SND_PROC_FS=y
CONFIG_SCSI_DMA=y
CONFIG_TCP_MD5SIG=y
CONFIG_KERNEL_GZIP=y

9.5.2 包含 scripts/Kbuild.include#

9.5.3 处理编译目标#

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
#主机程序,在前期的准备过程中可能需要用到,比如make menuconfig时需要准备命令行的图形配置。
ifneq ($(hostprogs-y)$(hostprogs-m)$(hostlibs-y)$(hostlibs-m)$(hostcxxlibs-y)$(hostcxxlibs-m),)
include scripts/Makefile.host
endif

#判断obj,如果obj没有指定则给出警告
ifndef obj
$(warning kbuild: Makefile.build is included improperly)
endif

#如果有编译库的需求,则给lib-target赋值,并将 $(obj)/lib-ksyms.o 追加到 real-obj-y 中。
ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)
lib-target := $(obj)/lib.a
real-obj-y += $(obj)/lib-ksyms.o
endif

#如果需要编译 将要编译进内核(也就是obj-y指定的文件) 的模块,则赋值 builtin-target
ifneq ($(strip $(real-obj-y) $(need-builtin)),)
builtin-target := $(obj)/built-in.a
endif

#如果定义了 CONFIG_MODULES,则赋值 modorder-target。
ifdef CONFIG_MODULES
modorder-target := $(obj)/modules.order
endif

modorder-target将被赋值为$(obj)/modules.order module.order这个文件记录了可加载模块在Makefile中出现的顺序,主要是提供给modprobe程序在匹配时使用。

9.5.4 默认编译目标_build#

例子见12.3.1.1.1

9.5.4.1builtin-target#
1
2
$(builtin-target): $(real-obj-y) FORCE
$(call if_changed,ar_builtin)
1
2
real-prereqs = $(filter-out $(PHONY), $^)
cmd_ar_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS) $@ $(real-prereqs)

ar_builtin就是执行cmd_ar_builtin, 就是使用ar指令打包成 $(builtin-target),也就是$(obj)/built-in.a。它的依赖文件被保存在 $(real-obj-y) 中。

$(real-obj-y) scritps/Makefile.lib被处理出来的变量, 对应目录下的所有目标文件,不包含文件夹。

9.5.4.2 lib-target#
1
2
$(lib-target): $(lib-y) FORCE
$(call if_changed,ar)

同理,最终将调用cmd_ar命令:

1
2
real-prereqs = $(filter-out $(PHONY), $^)
cmd_ar = rm -f $@; $(AR) rcsTP$(KBUILD_ARFLAGS) $@ $(real-prereqs)

将本模块中的目标全部打包成$(lib-target),也就是 $(obj)/lib.a

9.5.4.3 extra-y#

$(extra-y) Makefile.lib中被确定,主要负责dtb相关的编译:

1
2
extra-y             += $(dtb-y)
extra-$(CONFIG_OF_ALL_DTBS) += $(dtb-)

Makefile.lib 中可以找到对应的实现:

1
2
$(obj)/%.dtb: $(src)/%.dts $(DTC) FORCE
$(call if_changed_dep,dtc,dtb)

调用了 cmd_dtc:

1
2
3
4
5
6
7
8
cmd_dtc = mkdir -p $(dir ${dtc-tmp}) ; \
$(HOSTCC) -E $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \

$(DTC) -O $(2) -o $@ -b 0 \
$(addprefix -i,$(dir $<) $(DTC_INCLUDE)) $(DTC_FLAGS) \
-d $(depfile).dtc.tmp $(dtc-tmp) ; \

cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile)

$(2) 为 dtb,-O dtb 表示输出文件格式为 dtb 。 -o $@, $@ 为目标文件,表示输出目标文件,输入文件则是对应的 $<

9.5.4.4 obj-m#

由于它是一系列的 .o 文件,所以它的编译是通过模式规则的匹配完成的。

1
2
3
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)

执行rule_cc_o_c:

1
2
3
4
5
6
7
8
9
define rule_cc_o_c
$(call cmd,checksrc)
$(call cmd_and_fixdep,cc_o_c)
$(call cmd,gen_ksymdeps)
$(call cmd,checkdoc)
$(call cmd,objtool)
$(call cmd,modversions_c)
$(call cmd,record_mcount)
endef

cmd 函数其实就是执行 cmd_$1,那么也就是上述命令中分别执行 cmd_checksrc,cmd_gen_ksymdeps等等。

其中最重要的指令就是 : $(call cmd_and_fixdep,cc_o_c),它对目标文件执行了 fixdep,生成依赖文件,然后执行了 cmd_cc_o_c, 这个命令就是真正的编译指令:

1
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
9.5.4.5 modorder-target#

modorder-target值为 $(obj)/modules.order,也就是大多数目录下都存在这么一个 modules.orders `文件,来提供一个该目录下编译模块的列表。

1
2
$(modorder-target): $(subdir-ym) FORCE
$(Q)(cat /dev/null; $(modorder-cmds)) > $@
1
2
3
4
modorder-cmds =                     \
$(foreach m, $(modorder), \
$(if $(filter %/modules.order, $m), \
cat $m;, echo kernel/$m;))

该操作的目的就是将需要编译的.ko的模块以kernel/$(dir)/*.ko为名记录到 obj-m 指定的目录下。

9.5.4.6 subdir-ym#
1
2
$(subdir-ym):
$(Q)$(MAKE) $(build)=$@ need-builtin=$(if $(findstring $@,$(subdir-obj-y)),1)

这就是Kbuild递归遍历子目录编译的策略,对于每个需要递归进入编译的目录,都调用:

1
$(Q)$(MAKE) $(build)=$@ need-builtin=$(if $(findstring $@,$(subdir-obj-y)),1)

10 设置交叉编译器#

image

11 核心变量导出#

在顶层 Makefile 会导出很多变量:

1
2
3
4
5
6
7
8
9
export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
export MAKE AWK PERL PYTHON
export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS

export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS KBUILD_AFLAGS

image
打印出export的这些变量:
image
①这些变量来自config.mk,里面定义了ARCH,CPU,BOARD,VENDOR,SOC,BOARDDIR等变量。变 量 ARCH , 值 为 $(CONFIG_SYS_ARCH:"%"=%) , 也 就 是 提 取 CONFIG_SYS_ARCH 里面双引号“”之间的内容。比如 CONFIG_SYS_ARCH=“arm”的话, ARCH=arm。经过展开确定了CPUDIR=arch/arm/cpu/armv7
②这里有一个sinclude指令,sinclude 和 include 的功能类似,在 Makefile 中都是读取指定文件内容,这里读取 文件$(srctree)/arch/$(ARCH)/config.mk 的内容。sinclude 读取的文件如果不存在的话不会报错。
③依次包含arch,cpu,soc,vendor,board相关的config.mk
image
.config如下:
image

12 make xxx_defconfig 配置过程#

输入命令进行配置:

1
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig

image

12.1 产生版本日期信息#

先产生version_h和timestamp_hversion_h记录uboot版本和编译器版本。timestamp_h记录uboot的日期时间戳信息。打开这两个头文件:
image
image

12.2 配置变量#

MAKECMDGOALS 是 make 的一个环境变量,这个变量会保存你所指 定的终极目标列表,比如执行make mx6ull_alientek_emmc_defconfig,那么 MAKECMDGOALS 就为 mx6ull_alientek_emmc_defconfigfilter函数将 MAKECMDGOALS 中符合no-dot-config-targets的部分过滤出来,可以看到为空,所以dot-config还是等于1.

KBUILD_EXTMOD前面提到没有编译子模块,KBUILD_EXTMOD为空,进入,在判断

1
ifneq ($(filter config %config,$(MAKECMDGOALS),)

make mx6ull_alientek_emmc_defconfig, 明显过滤出来不为空,因此config-targets=1.

words $(MAKECMDGOALS)等于1,最后得到:

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

12.3 执行3个依赖#

image
KBUILD_DEFCONFIG等于mx6ull_14x14_ddr512_emmc_defconfig KBUILD_KCONFIG等于空,导出。
匹配到%config, 执行scripts_basic、outputmakefile 和 FORCE 3个依赖.
image

12.3.1 scripts_basic#

image

scripts/Kbuild.include定义了build变量。$Q$(MAKE) $(build)=scripts/basic展开build变量后得:

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

scripts_basic 会调用文件./scripts/Makefile.build,打开这个文件:
image

这里$(obj)=scripts/basic, patsubst 是替换函数在scripts/basic中查找符合tpl/%的部分,然后将tpl/取消掉,但是 scripts/basic没有tpl/,所以 src= scripts/basic。两个ifeq ($(obj),$(src))都满足条件,最终prefix等于.
image
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.
scripts/Makefile.build文件包含/scripts/basic/Makefile

12.3.1.1.1 找到默认目标_build#

再继续分析scripts/Makefile.build
image

__build 是默认目标,因为命令@make -f ./scripts/Makefile.build obj=scripts/basic没有指定目标,所以会使用到默认目标:__build
image

顶层 Makefile 中,KBUILD_BUILTIN 为 1KBUILD_MODULES 为 0,因此展开后目标__build 为:

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

直接打印出这5个目标:所以最终只有一个依赖scripts/basic/fixdep要执行,产生fixdep.
image

执行打印如下:
image
至此第一个依赖scripts_basic就结束了。
总结:scripts_basic就是利用scripts/Makefile.build去找到_build目标,然后去scripts/basic目录编译出fixdep.

12.3.2 outputmakefile#

由于KBUILD_SRC为空,不执行。否则会为源码路径创建source这个符号链接,执行mkmakefile

12.3.3 FORCE#

Makefile最底下定了FORCE目标,可以看到什么都不执行。
image

FORCE的作用:

可以看到它也是一个目标,没有依赖文件且没有命令部分,由于它没有命令生成FORCE,所以每次都会被更新。

所以它的作用就是:FORCE作为依赖时,就导致依赖列表中每次都有FORCE依赖被更新,导致目标每次被重新编译生成。

12.4 产生.config#

回到%config 处:
$(Q)$(MAKE) $(build)=scripts/kconfig $@展开后

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

再次进入scripts/Makefile.build, 用echo得到一些变量值:

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

打开./scripts/kconfig/Makefile
image
%_defconfig: $(obj)/conf匹配到我们的mx6ull_14x14_ddr512_emmc_defconfig,先编译依赖scripts/kconfig/conf,编译生成scripts/kconfig/conf如下:
image
最终利用conf工具从configs目录找到mx6ull_14x14_ddr512_emmc_defconfig,将配置写成.config文件。
总结defconfig配置过程:
image

12.5 制作defconfig#

如果没有对应的defconfig可以找一个与自己板级信息类似的defconfig生成一个.config,再通过menuconfig来完成自己board的配置,并最后通过savedefconfig保存为自己board的defconfig

1
2
3
4
make CROSS_COMPILE=aarch64-linux-gnu- evb-rk3399_defconfig
make menuconfig
make savedefconfig
cp defconfig configs/my_defconfig