Kbuild系统源码分析(四)—./scripts/Makefile.build

版权声明:本文为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)
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值