版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/119768181更多内容可关注微信公众号
./scripts/Makefile.build是通过make -f 而不是在Makefile中include使用的,其本身就是一个make的起始脚本
./scripts/Makefile.build在内核主要通过一个变量定义的方式使用,此变量定义在Kbuild.include中,所有include此文件的make脚本均可以使用此变量:
./scripts/Kbuild.include
build := -f $(srctree)/scripts/Makefile.build obj
此脚本通常在./Makefile, ./arch/Makefile,或Makefile.build自身中使用,其必须传入一个obj,如:
$(vmlinux-dirs): prepare
$(Q)$(MAKE) $(build)=$@ need-builtin=1
这里的obj=当前的编译目录,除obj之外基于Makefile.build启动submake也可以传入其他参数,如这里的need-builtin.
1. 源码解析
1.1: 确认编译目录与默认目标
## $(obj)是make -f传入的命令行参数,代表要编译的子目录
src := $(obj)
## 默认构建为__build, 没指定目录的情况下就是用这个
PHONY := __build
__build:
1.2:相关变量初始化
这些变量的赋值大部分来源于当前要编译目录$(obj)的Makefile,之后通过Makefiel.lib来处理,其中部分变量在用户输入中的含义和在此脚本中后续的含义不同,具体可参考<Kbuild系统源码分析—./scripts/Makefile.lib>
obj-y :=
obj-m :=
lib-y :=
lib-m :=
always :=
targets :=
subdir-y :=
subdir-m :=
EXTRA_AFLAGS :=
EXTRA_CFLAGS :=
EXTRA_CPPFLAGS :=
EXTRA_LDFLAGS :=
asflags-y :=
ccflags-y :=
cppflags-y :=
ldflags-y :=
subdir-asflags-y :=
subdir-ccflags-y :=
1.3:头文件包含及变量处理
## kbuild-dir是获取$(obj)对应源码目录的全路径
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
## 从源码目录依次查找kbuild或Makefile文件是否存在,第一个找到的文件的全路径记录到kbuild-file中
## 这也是当前make真正要编译的子目录中有效的makefile
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
## 包含当前要编译的目录中的Kbuild或Makefile文件
include $(kbuild-file)
## 包含Makefile.lib,其中会对1.2中各种变量进行处理,这些变量的赋值通常来自$(kbuild-file)
include scripts/Makefile.lib
## 若需要编译host相关的目标,则引入Makefile.host
ifneq ($(hostprogs-y)$(hostprogs-m)$(hostlibs-y)$(hostlibs-m)$(hostcxxlibs-y)$(hostcxxlibs-m),)
include scripts/Makefile.host
endif
## 若make参数中没有指定obj,则直接报错,当前文件使用错误
ifndef obj
$(warning kbuild: Makefile.build is included improperly)
endif
## lib-y在Makefile.lib中已经处理为包含 lib-y和lib-m中的所有内容,这里实际上加上lib-m应该没有意义
## 这里判断是否要编译库相关内容,若编译则设置 lib-target为此目录对应的库函数路径(xxx/lib.a)
## real-obj-y是真正要【编译出来】的目标,增加lib-kysm.o
ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)
lib-target := $(obj)/lib.a
real-obj-y += $(obj)/lib-ksyms.o
endif
## real-obj-y中定义的是需要被built-in到内核的目标,如果当前目录中存在要编译到内核的目标,或者当前目录传入参数中指定了need-builtin,那么则当前目录需要编译出 built-in.a文件
## 只有两种情况会导致need-builtin = 1:
## 1是需要编译vmlinux-dirs时,启动的submake默认就是need-builtin的
## 2是在编译到某个目录,发现其obj-y中指定了子目录,那么此子目录的编译也要传入need-builtin
ifneq ($(strip $(real-obj-y) $(need-builtin)),)
builtin-target := $(obj)/built-in.a
endif
## 若支持模块构建,则指定此变量以记录模块的编译顺序
ifdef CONFIG_MODULES
modorder-target := $(obj)/modules.order
endif
1.4:默认编译目标,__build
在不指定目标的情况下,./scripts/Makefile.build默认编译的就是此目标,其规则如下:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
__build规则的命令为空,也就是此目标本身不做任何操作,所有需要做的操作都在其依赖的构建中完成
此目标的依赖可以拆成3部分:
1.KBUILD_BUILTIN:
这本身是从Kbuild根Makefile(./Makefile)中export来的变量,代表当前是否要编译内核vmlinux,如果要编译内核linux,则需要构建:
* $(builtin-target): 即$(obj)/built-in.a,代表当前目录的所有要集成到内核的目标文件的归档(real-obj-y是built-in.a的依赖项,所以这里不用单独指定如obj-y)
* $(lib-target): 即$(obj)/lib.a(若有),代表当前目录所有要编译的作为库的目标文件的归档
* $(extra-y):代表不合入vmlinux,但在编译vmlinux中需要同时编译的目标
2.KBUILD_MODULES:
代表当前是否需要编译所有模块(其与KBUILD_BUINTIN并不冲突,二者可同时构建),若需要编译所有模块,则需要构建:
* $(obj-m): $(obj-m)在Makefile.lib中已经被过滤一遍了,这里剩下的是用户传入的obj-m变量中所有的非目录目标
* $(modorder-target): 输出的模块顺序文件
3.无条件依赖项:
* $(subdir-ym): 记录的是用户输入的obj-y和obj-m中的所有目录,不论二者谁指定了目录,在编译时总是要编译此目录,但此目录下具体哪些文件在模块/内核编译时编译,则是由子目录makefile中obj-y和obj-m决定的.
* $(always): always中记录的是不集成到模块和内核中,但二者编译过程中需要的其他目标.
1.5:具体的编译目标的定义
目标较多,这里不完全列举了,见2,3中的分析
1.6:通过targets引入上次编译命令
此文件中所有targets的赋值如下:
targets :=
## 在所有真正的目标中过滤掉所有子目录目标 %/built-in.a,正常应该只剩下各种 *.o
targets += $(filter-out $(subdir-obj-y), $(real-obj-y)) $(real-obj-m) $(lib-y)
## targets增加 extra-y中的目标 make中参数指定的默认目标,always中的目标
targets += $(extra-y) $(MAKECMDGOALS) $(always)
## 包含当前目录的默认built-in目标 $(obj)/built-in.a
targets += $(builtin-target)
## 包含当前目录的 $(objs)/lib.a
targets += $(lib-target)
targets += $(obj)/lib-ksyms.o
## 包含模块的所有复合目标
targets += $(multi-used-m)
## 去除所有的伪目标
targets := $(filter-out $(PHONY), $(targets))
targets += $(call intermediate_targets, .asn1.o, .asn1.c .asn1.h) \
$(call intermediate_targets, .dtb.o, .dtb.S .dtb) \
$(call intermediate_targets, .lex.o, .lex.c) \
$(call intermediate_targets, .tab.o, .tab.c .tab.h)
targets中包含的内容包括:
* 所有obj-y/m中的real目标,以及所有lib-y中的非目录目标(正常应该都是*.o)
* extra-y,make参数中的目标,always中指定的目标
* 当前编译目录的$(obj)/built-in.a; 和$(obj)/lib.a和$(obj)/lib-ksyms.o
* 所有复合目标
* 去除所有的伪目标等
existing-targets中存的是targets中所有本地文件存在的目标,对于这些目标说明之前编译过,则引入对应的*.cmd文件,此*.cmd文件中记录着上一次的编译命令和依赖项.
## targets中已经去除了伪目标,这里主要是找本地文件存在的目标
existing-targets := $(wildcard $(sort $(targets)))
##这里include了所有的*.cmd
-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)
1.7:创建目标所在目录
若发现当前编译目录非源码目录,此脚本的最后一步会调用shell构建所有targets中目标所在的,但尚未创建的目录
## 若源码目录非当前编译目录,则
ifneq ($(srctree),.)
# Create directories for object files if they do not exist
## 所以目标需要的目录
obj-dirs := $(sort $(obj) $(patsubst %/,%, $(dir $(targets))))
# If targets exist, their directories apparently exist. Skip mkdir.
## 所有应存在的目录
existing-dirs := $(sort $(patsubst %/,%, $(dir $(existing-targets))))
## 获得不存在的目录
obj-dirs := $(strip $(filter-out $(existing-dirs), $(obj-dirs)))
## 构建目录
ifneq ($(obj-dirs),)
$(shell mkdir -p $(obj-dirs))
endif
endif
2.:具体编译目标的定义
2.1:Makefile.build中的函数调用:
以下几个函数是在Makefile.build中被经常调用的函数,这里说明其具体原理:
1.cmd函数:
cmd函数定义在./Kbuild.include中:
cmd = @set -e; $(echo-cmd) $(cmd_$(1))
故所有的 $(call cmd, xxx) 实际上最终都是变量 cmd_xxx的展开,如$(call cmd,asn1_compiler)实际上是对变量 $(cmd_asn1_compiler)的展开,如下:
quiet_cmd_asn1_compiler = ASN.1 $@
cmd_asn1_compiler = $(objtree)/scripts/asn1_compiler $< \
$(subst .h,.c,$@) $(subst .c,.h,$@)
$(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler
$(call cmd,asn1_compiler)
2.if_changed/if_changed_dep/if_changed_rule
if_changed系列函数都是同样定义在./Kubild.include中(if_changed在Makefile.lib中有解释):
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
$(cmd); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
if_changed_dep = $(if $(strip $(any-prereq) $(arg-check)),$(cmd_and_fixdep),@:)
cmd_and_fixdep = \
$(cmd); \
scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).cmd;\
rm -f $(depfile)
if_changed_rule = $(if $(strip $(any-prereq) $(arg-check)),$(rule_$(1)),@:)
三者的相同点在于,具体的命令被调用时都要满足两个前提,即 $(any-prereq)和$(arg-check)至少有一个非空,其中:
* any-prereq:记录当前目标的所有非伪目标依赖中比当前目标更新,或不存在的依赖项
* arg-check:记录当前目标的编译是否改变(与上次编译的.cmd对比),若未改变则arg-check为空
这两个判断条件联合在一起,就是: 对于编译命令没有改变,且非伪目标依赖项没有更新或不存在的情况下,cmd命令不需执行,其他情况下cmd命令均需执行.
而三者的区别在于:
* if_changed: 只是简单的将当前目标的具体编译命令记录到了*.cmd中,这种情况通常用于binutil或一些二进制工具的调用中(如cmd_gzip,cmd_symlink等)
* if_changed_dep: 除了记录具体的编译命令,还需要记录当前目标的头文件依赖,这种情况通常仅用于.c/.s文件的编译中,且目标不是.o的情况
- 头文件依赖实际上是在执行$(cmd)命令时由gcc编译器生成的,其名字定义在$(depfile)中,默认为$(dot-target).d
- 在编译的几个x_flgas中都通过-Wp,-MD,$(depfile)指定了将依赖头文件输出到$(dot-target).d文件中,如下:
- if_changed_dep在命令执行完,调用fixdep,将命令字符串和依赖项全部输出到$(dot-target).cmd中
./Kbuild.include
depfile = $(subst $(comma),_,$(dot-target).d)
./Makefile.lib
c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) -include $(srctree)/include/linux/compiler_types.h $(_c_flags) $(modkern_cflags) $(basename_flags) $(modname_flags)
a_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(_a_flags) $(modkern_aflags)
cpp_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(_cpp_flags)
if_changed_dep生成的*.cmd如下:
cmd_init/init_task.o := aarch64-linux-gnu-gcc -Wp,-MD,init/.init_task.o.d ... -c -o init/.tmp_init_task.o init/init_task.c
## if_changed只有上面的一项,if_changed_dep会生成如下项
source_init/init_task.o := init/init_task.c
deps_init/init_task.o := \
$(wildcard include/config/posix/timers.h) \
$(wildcard include/config/arch/task/struct/on/stack.h) \
$(wildcard include/config/thread/info/in/task.h) \
$(wildcard include/config/smp.h) \
$(wildcard include/config/cgroup/sched.h) \
...
init/init_task.o: $(deps_init/init_task.o)
## 这里定义了存在的*.h为目标,也就是要求*.h必须存在
$(deps_init/init_task.o):
* if_changed_rule:和if_changed类似,只不过前者直接调用的是rule_$(1)的命令,而不是cmd_$(1)开头的命令,其主要也是用于cc的编译,但和if_changed的区别在于通常用于.c/.s生成.o的过程
在Makefile.build中只有两个rule开头的规则,二者最终都还是调用了cmd_$(1):
define rule_cc_o_c
$(call cmd,checksrc)
## 最终调用cmd_cc_o_c来生成.o文件
$(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
define rule_as_o_S
$(call cmd_and_fixdep,as_o_S)
$(call cmd,gen_ksymdeps)
$(call cmd,objtool)
$(call cmd,modversions_S)
endef
2.2:Makefile.build中其他目标的规则定义
在Makefile.build后面则是一些其他目标规则的定义,这里分类记录如下(忽略quiet模式):
1..c生成其他文件的规则
* .c生成.s的规则
由.c生成.s文件的规则就一条:
cmd_cc_s_c = $(CC) $(filter-out $(DEBUG_CFLAGS), $(c_flags)) $(DISABLE_LTO) -fverbose-asm -S -o $@ $<
$(obj)/%.s: $(src)/%.c FORCE
$(call if_changed_dep,cc_s_c)
* .c生成.i的规则
.i是预处理文件,其规则只有一条:
$(obj)/%.i: $(src)/%.c FORCE
$(call if_changed_dep,cpp_i_c)
* .c生成.o的规则
一共有两条,如下:
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)
## 静态规则,这里是记录obj-m中所有的单目标的规则,obj-m中所有单目标均只依赖于对应的.c文件
$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)
@{ echo $(@:.o=.ko); echo $@; \
$(cmd_undef_syms); } > $(MODVERDIR)/$(@F:.o=.mod)
其中静态规则的优先级高于普通规则,也就是如果目标在$(single-used-m)中,那么使用静态规则,否则使用上面的规则.
* .c生成CRC的规则
$(obj)/%.symtypes : $(src)/%.c FORCE
$(call cmd,cc_symtypes_c)
* .c的其他规则
## ll是LLVM的文件类型,用于clang编译
$(obj)/%.ll: $(src)/%.c FORCE
$(call if_changed_dep,cc_ll_c)
$(obj)/%.lst: $(src)/%.c FORCE
$(call if_changed_dep,cc_lst_c)
2..s生成其他文件的规则
* .s生成.o的规则
$(obj)/%.o: $(src)/%.S $(objtool_dep) FORCE
$(call if_changed_rule,as_o_S)
* .s生成CRC的规则
$(obj)/%.symtypes : $(src)/%.S FORCE
$(call cmd,cc_symtypes_S)
3. 其他.o文件的生成规则
* lib-ksyms.o的生成规则
$(obj)/lib-ksyms.o: $(lib-target) FORCE
$(call if_changed,export_list)
* 模块中复合目标的生成规则
$(multi-used-m): FORCE
$(call if_changed,link_multi-m)
@{ echo $(@:.o=.ko); echo $(filter %.o,$^); \
$(cmd_undef_syms); } > $(MODVERDIR)/$(@F:.o=.mod)
$(call multi_depend, $(multi-used-m), .o, -objs -y -m)
4.归档文件的生成规则
* $(builtin-target)生成规则(xxx/built-in.a)
## built-in.a依赖于所有真实要编译的.o文件
$(builtin-target): $(real-obj-y) FORCE
$(call if_changed,ar_builtin)
* $(lib-target)生成规则(xxx/lib.a)
## lib.a依赖于所有的lib-y中目标生成的.o
$(lib-target): $(lib-y) FORCE
$(call if_changed,ar)
5.子目录规则
## 子目录的递归编译
$(subdir-ym):
$(Q)$(MAKE) $(build)=$@ need-builtin=$(if $(findstring $@,$(subdir-obj-y)),1)
6.模块顺序文件规则
$(modorder-target): $(subdir-ym) FORCE
$(Q)(cat /dev/null; $(modorder-cmds)) > $@
7.链接脚本编译规则
$(obj)/%.lds: $(src)/%.lds.S FORCE
$(call if_changed_dep,cpp_lds_S)
8.其他规则
* ans1生成其对应.c/.h的规则
$(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler
$(call cmd,asn1_compiler)