1 版本号
Top Makefile的开头会有版本描述,VERSION 是主版本号,PATCHLEVEL 是补丁版本号,SUBLEVEL 是次版本号,这三个一 起构成了 uboot 的版本号,比如当前的 uboot 版本号就是“2016.03”。EXTRAVERSION 是附加 版本信息,NAME 是和名字有关的,一般不使用这两个。
2 MAKEFLAGS
有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明, 否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。
MAKEFLAGS 追加了一些值,“-rR”表示禁止使用内置的隐 含规则和变量定义,“--include-dir”指明搜索路径,”$(CURDIR)”表示当前目录。
3 设置命令输出详细程度
①uboot 默认编译使用短命令。
②设置变量“V=1“来实现完整的命令输出。
③make -s设置成静默输出,将会silent_输出,不打印任何提示信息。如下:
MAKE_VERSION就是make版本号,我这里是4.2.1。因此filter 4.%,$(MAKE_VERSION)得到的过滤结果不为空。
当使用“make -s”编译的时候,“-s”会作为 MAKEFLAGS 变量的一部分传递给 Makefile。因此$(firstword x$(MAKEFLAGS))得到xrRs,最后quiet=silent_,否则不使用silent_输出。最后使用 export 导出变量 quiet、Q 和 KBUILD_VERBOSE。
4 设置编译结果输出位置
在 make 的时候使用“O”来指定 输出目录,这么做是为了将源文件 和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在 同一个目录内。
判断“O”是否来自于命令行,如果来自命令行的话,KBUILD_OUTPUT 就为$(O),也就是输出目录。
一开始判断KBUILD_OUTPUT 是否为空。 如果指定了输出目录就调用 mkdir 命令创建目录。
5 代码检查
命令“make C=1”使能代码检查,检查那些需要重新编译的文 件。“make C=2”用于检查所有的源码文件。
6 子模块编译
使用命令“make M=dir”即可,旧语法“make SUBDIRS=dir”也是支持的。
如果定 义 了 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 7.获取主机架构系统信息
最终开发服务器主机架构和操作系统信息如下:
8 设置目标架构、工具链和配置文件
HOSTARCH是x86_64,我们编译make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”就是用于设置目标 ARCH 和 CROSS_COMPILE。
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
参与整个内核编译的过程,是编译的核心脚本之一。
Kbuild.include定义了很多变量:
里面定了build变量,后面产生配置文件时会用到:
9.1 build变量
build := -f $(srctree)/scripts/Makefile.build obj
例如:
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
当我们执行make menuconfig
时:
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)
:
modbuiltin := -f $(srctree)/scripts/Makefile.modbuiltin obj
$(dtbinst)
:
dtbinst := -f $(srctree)/scripts/Makefile.dtbinst obj
$(clean)
:
clean := -f $(srctree)/scripts/Makefile.clean obj
$(hdr-inst)
:
hdr-inst := -f $(srctree)/scripts/Makefile.headersinst obj
这四条指令,分别对应 內建模块编译
、dtb文件安装
、目标清除
和头文件安装
.
9.2
9.2 filechk
变量
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
mkdir -p $(dir $@)
:如果$@
目录不存在,就创建目录,$@
是编译规则中的目标部分。($@ 在 Makefile 表目标文件)- 执行
filechk_$(1)
,然后将执行结果保存到$@.tmp
中 - 对比
$@.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 定义编译选项
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中,就没必要再编译了。
# 去除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
#将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
命令在加载卸载时查询使用。
kernel/drivers/input/mouse/psmouse.ko
kernel/drivers/input/misc/100ask_adxl345-spi.ko
kernel/drivers/input/evbug.ko
9.4.4 目录的处理
#挑选出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 设备树相关
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 添加路径
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.build
的 obj
变量。而 Makefile.build
的obj
变量则是通过调用 $(build)
时进行赋值的。
9.5 scripts/Makefile.build
文件作用
9.5.1 包含 include/config/auto.conf
包含include/config/auto.conf
文件,这个文件的内容是这样的:
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 处理编译目标
#主机程序,在前期的准备过程中可能需要用到,比如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
$(builtin-target): $(real-obj-y) FORCE
$(call if_changed,ar_builtin)
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
$(lib-target): $(lib-y) FORCE
$(call if_changed,ar)
同理,最终将调用cmd_ar
命令:
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
相关的编译:
extra-y += $(dtb-y)
extra-$(CONFIG_OF_ALL_DTBS) += $(dtb-)
Makefile.lib
中可以找到对应的实现:
$(obj)/%.dtb: $(src)/%.dts $(DTC) FORCE
$(call if_changed_dep,dtc,dtb)
调用了 cmd_dtc
:
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 文件,所以它的编译是通过模式规则的匹配完成的。
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)
执行rule_cc_o_c
:
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
, 这个命令就是真正的编译指令:
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
9.5.4.5 modorder-target
modorder-target值为
$(obj)/modules.order,也就是大多数目录下都存在这么一个
modules.orders `文件,来提供一个该目录下编译模块的列表。
$(modorder-target): $(subdir-ym) FORCE
$(Q)(cat /dev/null; $(modorder-cmds)) > $@
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
$(subdir-ym):
$(Q)$(MAKE) $(build)=$@ need-builtin=$(if $(findstring $@,$(subdir-obj-y)),1)
这就是Kbuild递归遍历子目录编译的策略,对于每个需要递归进入编译的目录,都调用:
$(Q)$(MAKE) $(build)=$@ need-builtin=$(if $(findstring $@,$(subdir-obj-y)),1)
10 设置交叉编译器
11 核心变量导出
在顶层 Makefile 会导出很多变量:
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
打印出export的这些变量:
①这些变量来自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
.config如下:
12 make xxx_defconfig 配置过程
输入命令进行配置:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
12.1 产生版本日期信息
先产生version_h和timestamp_h。version_h记录uboot版本和编译器版本。timestamp_h记录uboot的日期时间戳信息。打开这两个头文件:
12.2 配置变量
MAKECMDGOALS 是 make 的一个环境变量,这个变量会保存你所指 定的终极目标列表,比如执行“make mx6ull_alientek_emmc_defconfig”,那么 MAKECMDGOALS 就为 mx6ull_alientek_emmc_defconfig。filter函数将 MAKECMDGOALS 中符合 no-dot-config-targets 的部分过滤出来,可以看到为空,所以dot-config还是等于1.
KBUILD_EXTMOD前面提到没有编译子模块,KBUILD_EXTMOD为空,进入,在判断
ifneq ($(filter config %config,$(MAKECMDGOALS),)
当make mx6ull_alientek_emmc_defconfig, 明显过滤出来不为空,因此config-targets=1.
words $(MAKECMDGOALS)等于1,最后得到:
config-targets = 1
mixed-targets = 0
dot-config = 1
12.3 执行3个依赖
KBUILD_DEFCONFIG等于mx6ull_14x14_ddr512_emmc_defconfig, KBUILD_KCONFIG等于空,导出。
匹配到%config, 执行scripts_basic、outputmakefile 和 FORCE 3个依赖.
12.3.1 scripts_basic
scripts/Kbuild.include定义了build变量。$Q$(MAKE) $(build)=scripts/basic展开build变量后得:
make -f ./scripts/Makefile.build obj=scripts/basic
12.3.1.1 scripts/Makefile.build
scripts_basic 会调用文件./scripts/Makefile.build,打开这个文件:
这里$(obj)=scripts/basic, patsubst 是替换函数在“scripts/basic”中查找符合“tpl/%”的部分,然后将“tpl/”取消掉,但是 “scripts/basic”没有“tpl/”,所以 src= scripts/basic。两个ifeq ($(obj),$(src))都满足条件,最终prefix等于.。
kbuild-dir 展开后为:
$(if $(filter /%, scripts/basic), scripts/basic, ./scripts/basic)
因为没有以“/”为开头的单词,所以$(filter /%, scripts/basic)的结果为空,kbuilddir=./scripts/basic。
kbuild-file 展开后为:
$(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:
__build 是默认目标,因为命令“@make -f ./scripts/Makefile.build obj=scripts/basic”没有指定目标,所以会使用到默认目标:__build。
顶层 Makefile 中,KBUILD_BUILTIN 为 1, KBUILD_MODULES 为 0,因此展开后目标__build 为:
__build:$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:
直接打印出这5个目标:所以最终只有一个依赖scripts/basic/fixdep要执行,产生fixdep.
执行打印如下:
至此第一个依赖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目标,可以看到什么都不执行。
FORCE的作用:
可以看到它也是一个目标,没有依赖文件且没有命令部分,由于它没有命令生成FORCE,所以每次都会被更新。
所以它的作用就是:FORCE作为依赖时,就导致依赖列表中每次都有FORCE依赖被更新,导致目标每次被重新编译生成。
12.4 产生.config
回到%config 处:
$(Q)$(MAKE) $(build)=scripts/kconfig $@展开后
make -f ./scripts/Makefile.build obj=scripts/kconfig mx6ull_14x14_ddr512_emmc_defconfig
再次进入scripts/Makefile.build, 用(echo)得到一些变量值:
kbuild-dir = ./scripts/kconfig
kbuild-file = ./scripts/kconfig/Makefile
include ./scripts/kconfig/Makefile
打开./scripts/kconfig/Makefile:
%_defconfig: $(obj)/conf匹配到我们的mx6ull_14x14_ddr512_emmc_defconfig,先编译依赖scripts/kconfig/conf,编译生成scripts/kconfig/conf如下:
最终利用conf工具从configs目录找到mx6ull_14x14_ddr512_emmc_defconfig,将配置写成.config文件。
总结defconfig配置过程:
12.5 制作defconfig
如果没有对应的defconfig可以找一个与自己板级信息类似的defconfig生成一个.config,再通过menuconfig来完成自己board的配置,并最后通过savedefconfig保存为自己board的defconfig:
make CROSS_COMPILE=aarch64-linux-gnu- evb-rk3399_defconfig
make menuconfig
make savedefconfig
cp defconfig configs/my_defconfig