vlambda博客
学习文章列表

Kbuild系统源码分析(二)—./Makefile

上接 <Kbuild系统源码分析(一)—./Makefile>


5.对非xxx_config目标的编译
5.1:设置./Makefile的默认目标
  ./Makefile的开头可知,其默认的编译目标是_all,之前的处理流程中处理了对混合目标,xxx_config等编译的情况,而大部分情况都是走到这里,这里要先设置make的默认目标,在整个./Makefile中,_all的编译仅在这里唯一设置了一次,根据当前是否是编译外部模块,_all中的默认目标有所区别:
  * 如果编译的是外部模块,那么_all只依赖于伪目标modules
  * 如果是编译非外部模块,那么_all只依赖于伪目标all

PHONY += allifeq ($(KBUILD_EXTMOD),)## 当编译in-tree的代码时,默认的目标是伪目标all,all是只有在编译非外部模块才会用到的目标(语义上)_all: allelse## 编译out-tree的代码时,默认的目标是伪目标modules,modules是在编译外部或非外部模块均有可能用到的目标_all: modulesendif
  • 其中modules是在编译外部/非外部模块均有可能用到的伪目标,因此其在./Makefile中也存在不同的规则定义:

    • 编译外部模块时,有单独定义的modules规则

    • 编译非外部模块,且定义了CONFIG_MODULES时,有单独定义的modules规则

    • 编译非外部模块,且未定义CONFIG_MODULES时,有单独定义的modules规则

  • 其中all只用于非外部模块编译的目标,具体依赖项取决于内核配置:

    • all:vmlinux all总是拥有vmlinux作为依赖项,即编译all则需要编译vmlinux

    • all: headers_check 是否依赖于headers_check取决于CONFIG_HEADERS_CHECK是否配置

    • all: dtbs 是否依赖于dtbs取决于CONFIG_OF_EARLY_FLATTREE是否配置

    • all: modules 是否依赖于modules取决于CONFIG_MODULES是否配置

    • all: scripts_gdb 是否依赖于scripts_gdb取决于CONFIG_GDB_SCRIPTS是否配置

5.2: KBUILD_BUILTIN,KBUILD_MODULES
  KBUILD_BUILTIN代表的是当前需要编译BUILTIN相关的目标,体现在obj-y等都参与编译,此变量默认为1,只有当前的目标仅仅是模块,且没有开启CRC时才可能为空,此时的模块编译不需要编译任何BUINTIN相关的目标,对于外部模块编译,KBUILD_BUILTIN总是为1.
  KBUILD_MODULES代表当前是否需要编译模块相关的目标,体现在obj-m等都参与编译

## KBUILD_BUILTIN和KBUILD_MODULES指的是非默认目标情况下(非all/modules用户指定了某个目标),是否需要编译linux内核(vmlinux)相关目标,和(外部/内部)模块## 二者并不冲突,有的情况下要求二者均需要编译KBUILD_MODULES :=## 无论是什么目标,认为内核是默认编译的,除非手动去除KBUILD_BUILTIN := 1
## 若用户指定的编译目标包含modules,那么如果需要生成CRC,则默认也需要编译内核vmlinuxifeq ($(MAKECMDGOALS),modules) KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)endif
## 这里filter是过滤掉这三个目标外的其他目标,也就是说,若当前编译目标是 all _all modules 之一,那么 KBUILD_MODULES=1## 其他情况下这里不设置 KBUILD_MODULES=1,若用户指定的目标是all/_all或 modules 则后续编译过程中需要编译模块ifneq ($(filter all _all modules,$(MAKECMDGOALS)),) KBUILD_MODULES := 1endif
## 如果目标为空,那么默认也需要编译模块(就是all或modules的情况,取决于KBUILD_EXTMODifeq ($(MAKECMDGOALS),) KBUILD_MODULES := 1endif
export KBUILD_MODULES KBUILD_BUILTIN

5.3:向Makefile引入内核配置项
  include了auto.conf后,内核的配置项变为了Makefile中的变量,此后的Makefile中可以直接使用CONFIG_XXX来判断内核配置是否开启了.

## 到这里当前的编译目标一定不是xxx_config(否则前面的流程就结束了)## 此时如果需要使用.config的话,则直接包含auto.conf,将内核的CONFIG配置引入到Makefile的配置中ifeq ($(dot-config),1)include include/config/auto.confendif

5.4:初始化非外部模块编译的子目录

ifeq ($(KBUILD_EXTMOD),)# Objects we will link into vmlinux / subdirs we need to visit## 这里是需要连接到vmlinux的目录的变量,这里并不全,如fs都没有init-y := init/drivers-y := drivers/ sound/drivers-$(CONFIG_SAMPLES) += samples/net-y := net/libs-y := lib/core-y := usr/virt-y := virt/endif # KBUILD_EXTMOD

5.5:设置all:vmlinux

all:vmlinux

5.6:设置部分编译选项

CFLAGS_GCOV := -fprofile-arcs -ftest-coverage \ $(call cc-option,-fno-tree-loop-im) \ $(call cc-disable-warning,maybe-uninitialized,)export CFLAGS_GCOV
# The arch Makefiles can override CC_FLAGS_FTRACE. We may also append it later.## 这个应该是给trace用的ifdef CONFIG_FUNCTION_TRACER CC_FLAGS_FTRACE := -pgendif
## retpoline是防specture攻击的相关特性RETPOLINE_CFLAGS_GCC := -mindirect-branch=thunk-extern -mindirect-branch-registerRETPOLINE_VDSO_CFLAGS_GCC := -mindirect-branch=thunk-inline -mindirect-branch-registerRETPOLINE_CFLAGS_CLANG := -mretpoline-external-thunkRETPOLINE_VDSO_CFLAGS_CLANG := -mretpolineRETPOLINE_CFLAGS := $(call cc-option,$(RETPOLINE_CFLAGS_GCC),$(call cc-option,$(RETPOLINE_CFLAGS_CLANG)))RETPOLINE_VDSO_CFLAGS := $(call cc-option,$(RETPOLINE_VDSO_CFLAGS_GCC),$(call cc-option,$(RETPOLINE_VDSO_CFLAGS_CLANG)))export RETPOLINE_CFLAGSexport RETPOLINE_VDSO_CFLAGS

5.7: 引入平台相关Makefile,初始化平台相关flags
  按照描述,平台相关的FLAGS应该是可以覆盖KBUILD的默认值(ARCH相关的FLAGS是放到KBUILD_XXFLAGS的最后的,见5.9,在gcc中同一个flag最后出现的覆盖之前出现的配置),且arch下的makefile,如./arch/arm64/Makefile是直接include到当前make的,也就是说arch相关的目标如 Image:vmlinux的规则相当于直接在当前./Makefile中定义了.

# The arch Makefile can set ARCH_{CPP,A,C}FLAGS to override the default# values of the respective KBUILD_* variables## 可以单独指定ARCH相关的FLAGS,其会覆盖KBUILD_XXXFLAGSARCH_CPPFLAGS :=ARCH_AFLAGS :=ARCH_CFLAGS :=##引入对应ARCH下的makefile, 如arch/arm64/Makefile,所以Image:vmlinux等规则相当于是直接引入到./makefile中的include arch/$(SRCARCH)/Makefile

5.8:auto.conf编译规则
   这一步是 *.conf和*.h的生成规则,大体原理参考2.3,这里先不做详细说明

## 若当前编译的目标需要使用.config文件ifeq ($(dot-config),1)## 若当前需要重构 *.conf *.h文件ifeq ($(may-sync-config),1)## 这里面是定义了 deps_config目标,其是所有Kconfig的依赖项include include/config/auto.conf.cmd
## 若KCONFIG_CONFIG文件不存在,则直接报错,否则由于没有依赖,这里不必构建$(KCONFIG_CONFIG): @echo >&2 '***' @echo >&2 '*** Configuration file "$@" not found!' @echo >&2 '***' @echo >&2 '*** Please run some configurator (e.g. "make oldconfig" or' @echo >&2 '*** "make menuconfig" or "make xconfig").' @echo >&2 '***' @/bin/false
## 这里是 如auto.conf.cmd的生成命令,实际上是执行 syncconfig,这个会匹配到当前文件的 %config%/auto.conf %/auto.conf.cmd %/tristate.conf: $(KCONFIG_CONFIG) $(Q)$(MAKE) -f $(srctree)/Makefile syncconfigelse## 这里是不需要 syncconfig的情况## auto.conf 加入到伪目标PHONY += include/config/auto.conf
## auto.config的生成规则, 只是检测autoconf.h文件是否存在, autoconfig.h也是自动生成的## autoconfig.h里面是对各种config的define,如 #define CONFIG_XXX 1## 若此时autoconf.h不存在则报错include/config/auto.conf: $(Q)test -e include/generated/autoconf.h -a -e $@ || ( \ echo >&2; \ echo >&2 " ERROR: Kernel configuration is invalid."; \ echo >&2 " include/generated/autoconf.h or $@ are missing.";\ echo >&2 " Run 'make oldconfig && make prepare' on kernel src to fix it."; \ echo >&2 ; \ /bin/false)endif # may-sync-configendif # $(dot-config)

5.9:根据配置,设置各种内核编译flags
  这里只列举部分flags(全部开关可参考源码),如:
  * include scripts/Makefile.gcc-plugins 引入了gcc插件的相关flags
  * -fstack-protector等栈溢出保护相关开关
  * 对于clang编译器引入部分开关
  * include了scripts目录下 Makefile.kasan,Makefile.extrawarn,Makefile.ubsan中的配置
  最后将ARCH相关的配置放到KBUILD_XXXflags的最后,使得ARCH的配置可以覆盖KBUILD的默认值,这里也代表KBUILD_CPP/A/CFLAGS配置的结束(但LD相关或其他的的config在后面还可以配置)

## 这里根据编译器情况增加配置开关KBUILD_CFLAGS += $(call cc-option,-fno-delete-null-pointer-checks,)KBUILD_CFLAGS += $(call cc-disable-warning,frame-address,)KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation)## 溢出检查相关开关KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow)KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member)## 优化级别开关ifdef CONFIG_CC_OPTIMIZE_FOR_SIZEKBUILD_CFLAGS += -OselseKBUILD_CFLAGS += -O2endif## 包含kcov的配置,此文件中没有新增规则include scripts/Makefile.kcov
## 包含插件配置,此文件中没有新增规则include scripts/Makefile.gcc-plugins
## 栈溢出保护相关开关stackp-flags-$(CONFIG_CC_HAS_STACKPROTECTOR_NONE) := -fno-stack-protectorstackp-flags-$(CONFIG_STACKPROTECTOR) := -fstack-protectorstackp-flags-$(CONFIG_STACKPROTECTOR_STRONG) := -fstack-protector-strongKBUILD_CFLAGS += $(stackp-flags-y)
## clang编译器相关开关ifdef CONFIG_CC_IS_CLANGKBUILD_CPPFLAGS += -Qunused-arguments......KBUILD_CFLAGS += -mno-global-mergeelse## 非clang则加这个## 未使用但初始化的变量不报错KBUILD_CFLAGS += -Wno-unused-but-set-variableendif
include scripts/Makefile.kasaninclude scripts/Makefile.extrawarninclude scripts/Makefile.ubsan
## 将平台相关的KBUILD_CPPFLAGS += $(ARCH_CPPFLAGS) $(KCPPFLAGS)KBUILD_AFLAGS += $(ARCH_AFLAGS) $(KAFLAGS)KBUILD_CFLAGS += $(ARCH_CFLAGS) $(KCFLAGS)

5.10:定义模块strip,compress和sign的命令

......## 模块的压缩命令mod_compress_cmd = trueifdef CONFIG_MODULE_COMPRESS ifdef CONFIG_MODULE_COMPRESS_GZIP mod_compress_cmd = gzip -n -f endif # CONFIG_MODULE_COMPRESS_GZIP ifdef CONFIG_MODULE_COMPRESS_XZ mod_compress_cmd = xz -f endif # CONFIG_MODULE_COMPRESS_XZendif # CONFIG_MODULE_COMPRESSexport mod_compress_cmd
## 构造模块签名的目标和对应签名命令ifdef CONFIG_MODULE_SIG_ALL$(eval $(call config_filename,MODULE_SIG_KEY))mod_sign_cmd = scripts/sign-file $(CONFIG_MODULE_SIG_HASH) $(MODULE_SIG_KEY_SRCPREFIX)$(CONFIG_MODULE_SIG_KEY) certs/signing_key.x509elsemod_sign_cmd = trueendifexport mod_sign_cmd......

5.11: 非外部模块时的规则定义
5.12: 外部模块时的规则定义
  这两节内容比较多,单独放到 6,7中说明


6. 非外部模块的规则定义
6.1: 变量定义与处理
  在5.4中已经初始化了非外部模块编译时的部分变量,先列举如下:

ifeq ($(KBUILD_EXTMOD),)init-y := init/drivers-y := drivers/ sound/drivers-$(CONFIG_SAMPLES) += samples/net-y := net/libs-y := lib/core-y := usr/virt-y := virt/endif # KBUILD_EXTMOD
这里在此基础上定义新的变量并对已有变量做处理,包括:PHONY += prepare0
## 6中主要定义的就是这个宏包裹的非外部模块的规则ifeq ($(KBUILD_EXTMOD),)## 这里就重新为core-y增加了目录,前面core-y中只有usr/目录core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
## 这里过滤掉所有目录字符串尾部的/,也就是说vmlinux-dirs全是目录名,但没有 /结尾## vmlinux-dirs在定义上应该包括:nit-y/m, core-y/m, drivers-y/m, net-y/m, libs-y/m, virt-y## 但实际上m的变量基本上都没有,在Kbuild中目前用的是y,不清楚设计本意中二者的区别vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))
## vmlinux-alldirs中除了包含$(vmlinux-dirs)中的目录外,还包含了document目录## 以及xxx-目录,这些目录通常都是config没有配置为y或m的目录## 也就是说vmlinux-alldirs是vmlinux-dir目录中出现的最大可能情况 + document目录vmlinux-alldirs := $(sort $(vmlinux-dirs) Documentation \ $(patsubst %/,%,$(filter %/, $(init-) $(core-) \ $(drivers-) $(net-) $(libs-) $(virt-))))
## 将所有的目录%/都替换为 %/built-in.ainit-y := $(patsubst %/, %/built-in.a, $(init-y))core-y := $(patsubst %/, %/built-in.a, $(core-y))drivers-y := $(patsubst %/, %/built-in.a, $(drivers-y))net-y := $(patsubst %/, %/built-in.a, $(net-y))## libs-y1记录的是所有的lib.a文件路径, libs-y2记录的是所有built-in.a文件libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))libs-y2 := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))virt-y := $(patsubst %/, %/built-in.a, $(virt-y))
# Externally visible symbols (used by link-vmlinux.sh)## 这里是一堆built-in.a文件,只有arch平台的head-y中可能会有.o或.lds等其他文件## 这些是构建内核需要构建的目标export KBUILD_VMLINUX_OBJS := $(head-y) $(init-y) $(core-y) $(libs-y2) \ $(drivers-y) $(net-y) $(virt-y)
## 这个是构建内核需要构建的所有库文件(xxx/lib.a)export KBUILD_VMLINUX_LIBS := $(libs-y1)
## 平台相关的vmlinux连接脚本的全路径export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
## 这是链接vmlinux时用到的LD选项export LDFLAGS_vmlinux
# used by scripts/package/Makefile## 这里排除了arch下的目录,包含了kernel自身的一些编译相关的目录,这个只在scripts/package/Makefile中使用了export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) LICENSES arch include scripts tools)
## 这个是编译vmlinux的依赖文件 这里都是.a,不同版本的问题## vmlinux-deps: arch/arm64/kernel/vmlinux.lds arch/arm64/kernel/head.o init/built-in.a ... arch/arm64/lib/lib.a lib/lib.a## vmlinux的依赖是链接脚本,各种built-in.a和各种lib.avmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS)


6.2:vmlinux的编译
  根据vmlinux的编译规则可知,其一共包含三个依赖项:
   1.scripts/link-vmlinux.sh: 这是编译命令中具体执行的脚本,其没什么规则,必须存在
   2.autoksyms_recursive: 若要drop没有用到的内核和模块的导出符号,则此目标中会有命令
   3.$(vmlinux-deps): 包括arch相关的vmlinux链接脚本(如arch/arm64/kernel/vmlinux.lds),各种built-in.a和各种lib.a文件
   对于依赖来说,其中最主要的是,若想编译vmlinux,那其前提就是$(vmlinux-deps)中的所有目标(vmlinux.lds, */built-in.a, */lib.a)都要编译先编译出来(这些目录名的规则见下).
  而其编译命令一共有两条:
   1.执行scripts/link-vmlinux.sh编译vmlinux
   2.若平台存在Makefile.postlink,则在构建vmlinux后执行make -f Makefile.postlink

PHONY += autoksyms_recursiveautoksyms_recursive: $(vmlinux-deps)## 内核和模块可能会通过EXPORT_SYMBOL及其变种为其他其他模块导出符号,所有这些符号并不是每次编译都用到的, 这取决于当前内核配置了编译哪些模块. ## CONFIG_TRIM_UNUSED_KSYMS的作用就是将当前编译中所有未使用的EXPORT的符号drop掉. 这会为编译器提供更多的优化可能(尤其是LTO),以减少代码和二进制大小,## 同时也有一定的安全优势, 但如果需要编译out-of-tree 模块的话,则不能开启此configifdef CONFIG_TRIM_UNUSED_KSYMS $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \ "$(MAKE) -f $(srctree)/Makefile vmlinux"endififdef CONFIG_TRIM_UNUSED_KSYMS KBUILD_vmlinuxMODULES := 1endifautoksyms_h := $(if $(CONFIG_TRIM_UNUSED_KSYMS), include/generated/autoksyms.h)$(autoksyms_h): $(Q)mkdir -p $(dir $@) $(Q)touch $@
## 平台可指定一个Makefile,链接后通过make -f 执行ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)
## 这是最终链接vmlinux的命令,其中$<是第一个依赖项,也就是 script/link-vmlinux.sh 这里要用CONFIG_SHELL来执行## 如果平台存在Makefiel.postlink,则编译完vmlinux后还要通过make -f 执行postlinkcmd_link-vmlinux = \ $(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux) ; \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
## vmlinux 的编译命令是 cmd_link-vmlinux## 其依赖项一共有三项:## 1.scripts/link-vmlinux.sh: 这是编译命令中具体执行的脚本,其没什么规则,必须存在## 2.autoksyms_recursive: 若要drop没有用到的内核和模块的导出符号,则此目标中会有命令## 3.$(vmlinux-deps): 包括arch相关的vmlinux链接脚本(如arch/arm64/kernel/vmlinux.lds),各种built-in.a和各种lib.a文件vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE ## 也就是说这个link-vmlinux总是要执行的,最后实际上是执行了一个: ## scripts/link-vmlinux.sh $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux) +$(call if_changed,link-vmlinux)
## vmlinux加入到 targets中,if_changed宏才能生效targets := vmlinux

6.3:vmlinux主要依赖项$(vmlinux-dirs)的生成
  这里需要注意的是vmlinux-deps中的任何一项(不论是*.lds,*/built-in.a,*/lib.a)都依赖于整个$(vmlinux-dirs),而vmlinux-dirs记录的是所有要编译的子目录名,最终会通过 make -f 对每个子目录进行submake.vmlinux-dirs还依赖于prepare,见6.4

## 这里是为vmlinux-deps里面的 built-in.a指定依赖项,其依赖于vmlinux-dirs,$(vmlinux-dirs)里面全是目录名,但结尾没有/## 需要注意的是,这里没有通配符,也就是说每一个xxx.built-in.a,lib.a甚至vmlinux.lds其依赖中都是所有需要编译的目录!$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
## 这些目录名,都是伪目标PHONY += $(vmlinux-dirs)
## 这里的make展开如 make build := -f $(srctree)/scripts/Makefile.build obj=init need-builtin=1## 每一个子目录名都要执行一次此make$(vmlinux-dirs): prepare $(Q)$(MAKE) $(build)=$@ need-builtin=1

6.4:编译vmlinux时的prepare
  prepare目标和其名字一样,其中记录着内核和内部/外部模块构建之前必须的准备工作:
  * 内核和内部模块的编译规则中都直接或间接的依赖了prepare
  * 外部模块的编译中实际上是隐式依赖了这个prepare:
    - 外部模块依赖的prepare是重新定义的,并不是这里的这个prepare(见后)
    - 这是由于当前这个prepare必须在编译外部模块之前已经编译过(隐式条件)
    - 根据modules.txt文档记录,外部模块编译前,内核必须已经构建过,或者执行过make modules_prepare为外部模块准备了编译环境
      而这个modules_prepare实际上就是在构建这里的prepare
  整个prepare的依赖关系图如下:


   这里拆解如下(=>代表规则的依赖项,*代表规则的命令):

 prepare * 在内部模块编译时,prepare只有依赖,没有具体命令,即只需要构建其依赖即可 => prepare-objtool * 命令为空 => 有可能依赖于tools/objtool或者为空,先pass => prepare0  * 编译script/mod目录,此目录主要包含工具 modpost  * 编译当前目录(也就是源码目录),当前文件是./Makefile,但由于源码目录存在./Kbuild,故这里实际上是编译的./Kbuild中的目标 => archprepare 此目标中的依赖项多少都和平台有点关系 * archprepare命令为空,同样只编译依赖 => archheaders: 通常应该是给平台编译特定.h/.c文件预留的,在arm64上为空,在arm平台编译了$(build)=arch/arm/tools uapi => archscripts: 应该是跟平台编译特定二进制预留的,目前仅在x86上看到有 $(Q)$(MAKE) $(build)=arch/x86/tools relocs => scripts: * 编译./scripts目录, $(Q)$(MAKE) $(build)=./scripts => 编译 ./scripts/basic目录,主要包括 fixdep(给.cmd中输出的头文件依赖做修正的,其只有在if_changed_dep,rule_cc_o_c等位置会调用, 见Kbuild系统源码分析—./scripts/Makefile.build,还有一些其他情况也会生成.cmd,见if_changed) => 编译 ./scripts/dtc目录 //按理说这两个直接加到./scripts目录的subdir编译即可..不知道为什么这里单独加了依赖 => prepare1 * $(cmd_crmodverdir): 创建$(MODVERDIR)目录(默认是./tmp_versions),并清除其中所有内容,此目录是后续用来存放模块信息的 => prepare3 * 若源码目录非输出目录,则检查源码目录是否存在.config或include/config目录或generated目录,若存在则代表源码目录不干净, 会提示使用make mrproper命令清理源码树 => include/config/kernel.release * 调用setlocalversion将内核版本信息存到kernel.release文件中,如:4.14.174-00251-g1de6fe1a466f-dirty => outputmakefile 在非源码目录编译时: * 将源码目录软链接到./out/source * 通过mkmakefile为out目录生成makefile * 为out目录创建.gitignore文件 => asm-generic:是用来处理include下一些头文件是具体使用内核目录还是arch目录同名头文件的,细节参考源码 => $(version_h): 调用filechk_version.h生成 include/generated/uapi/linux/version.h 文件,其中内容见下面代码 => $(autoksyms_h): touch此文件 => include/generated/utsrelease.h
## 这里生成如 4.14.174-00251-g1de6fe1a466f-dirty 应该是通过setlocalversion生成的filechk_kernel.release = \ echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
## filechk定义在Kbuild.include中,最终展开是执行 filechk_$(1), 这里最终通过setlocalversion将版本信息输出到kernel.release文件中## 内容如 4.19.109-g6c232927e-dirtyinclude/config/kernel.release: FORCE $(call filechk,kernel.release)
PHONY += scripts## scripts的构建命令,最终实际上是直接构建scripts目录,同时还构建了其子目录scripts/basic和scripts/dtc,前者主要包含fixdep的编译(修复依赖项的)scripts: scripts_basic scripts_dtc $(Q)$(MAKE) $(build)=$(@)
PHONY += prepare archprepare prepare1 prepare3## prepare3的依赖为 include/config/kernel.release,其是通过setlocalversion工具生成的,见filechk_kernel.release## 若指定了out目录,但源码目录存在.config,include/config目录或generated目录,则代表源码树不干净,报错清空源码树prepare3: include/config/kernel.releaseifneq ($(srctree),.) @$(kecho) ' Using $(srctree) as source for kernel' ## 若源码树中存在.config或 include/config目录或 generated目录,则代表源码树不干净 ## 会提示要执行make mrproper来清空源码树 $(Q)if [ -f $(srctree)/.config -o \ -d $(srctree)/include/config -o \ -d $(srctree)/arch/$(SRCARCH)/include/generated ]; then \ echo >&2 " $(srctree) is not clean, please run 'make mrproper'"; \ echo >&2 " in the '$(srctree)' directory.";\ /bin/false; \ fi;endif
## prepare1的命令是构建并清空 MODVERDIR目录(./tmp_versions)## 其依赖项:## * outputmakefile是在非源码目录编译时,链接源码目录到source,构建out目录的makefile,创建.gitignore文件等## * asm-generic是用来处理include下一些头文件是具体使用内核目录还是arch目录同名头文件的## * prepare3 包含两个操作:## 1.是生成include/config/kernel.release 文件## 2.是当编译目录非源码目录时,确保源码目录是干净的(没有.config或 include/config目录或 generated目录)## * $(version_h)是include/generated/uapi/linux/version.h头文件的生成## * $(autoksyms_h)是include/generated/autoksyms.h文件的生成prepare1: prepare3 outputmakefile asm-generic $(version_h) $(autoksyms_h) \ include/generated/utsrelease.h $(cmd_crmodverdir)
## 在arm64平台archheaders是空,在arm平台是编译了$(Q)$(MAKE) $(build)=arch/arm/tools uapi## archscripts只在x86平台有值,为 $(Q)$(MAKE) $(build)=arch/x86/tools relocs## * archheaders应该是给平台编译特定头文件预留的## * archscripts应该是给平台编译特定二进制预留的## * prepare1见上## * scripts目标最终实际上是直接构建scripts/basic 和 scripts/dtc 目录,前者主要包含fixdep的编译(修复依赖项的)archprepare: archheaders archscripts prepare1 scripts
## make -f $(srctree)/scripts/Makefile.build obj=scripts/mod, 这里主要包含的工具就是modpost## make -f $(srctree)/scripts/Makefile.build obj=. , 这里使用了当前目录的Kbuild,里面包括的是时钟相关的一些目标prepare0: archprepare $(Q)$(MAKE) $(build)=scripts/mod $(Q)$(MAKE) $(build)=.
## prepare-objtool默认只有 tools/objtoolprepare: prepare0 prepare-objtool
## 这个是变量,后面是目标,二者不一样asm-generic := -f $(srctree)/scripts/Makefile.asm-generic objPHONY += asm-generic uapi-asm-generic
## 此命令用来确定obj=arch/$(SRCARCH)/include/generated/asm 中的头文件,哪些是需要来自于include/asm-generic目录的,需要使用## include/asm-generic目录中的文件的话,则在$(obj)目录下生成对应的.h文件,在其中写入一句代码<include asm-generic/$*.h>## 而如果需要使用arch自己的asm目录下的话,这里没有直接处理asm-generic: uapi-asm-generic $(Q)$(MAKE) $(asm-generic)=arch/$(SRCARCH)/include/generated/asm \ generic=include/asm-generic
## 这里执行的命令是: make -f $(srctree)/scripts/Makefile.asm-generic obj=arch/$(SRCARCH)/include/generated/uapi/asm generic=include/uapi/asm-generic## 原理和asm-generic基本相同,只不过处理的是arch/$(SRCARCH)/include/generated/uapi/asm目录uapi-asm-generic: $(Q)$(MAKE) $(asm-generic)=arch/$(SRCARCH)/include/generated/uapi/asm \ generic=include/uapi/asm-generic
PHONY += prepare-objtoolprepare-objtool: $(objtool_target)
uts_len := 64define filechk_utsrelease.h if [ `echo -n "$(KERNELRELEASE)" | wc -c ` -gt $(uts_len) ]; then \ echo '"$(KERNELRELEASE)" exceeds $(uts_len) characters' >&2; \ exit 1; \ fi; \ echo \#define UTS_RELEASE \"$(KERNELRELEASE)\"endef
define filechk_version.h echo \#define LINUX_VERSION_CODE $(shell \ expr $(VERSION) \* 65536 + 0$(PATCHLEVEL) \* 256 + 0$(SUBLEVEL)); \ echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))'endef
## 这个是生成version.h文件的,里面记录变量LINUX_VERSION_CODE 和KERNEL_VERSION宏## $ cat ./include/generated/uapi/linux/version.h## #define LINUX_VERSION_CODE 265902## #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))$(version_h): FORCE $(call filechk,version.h) $(Q)rm -f $(old_version_h)
include/generated/utsrelease.h: include/config/kernel.release FORCE $(call filechk,utsrelease.h)


6.5:header_install及相关目标
   header_install主要是用来给用户态输出内核文件用的,其中的调用到scripts/headers_install.sh实际上主要是去除了内核头文件参数中的__user等,正常用户态的UAPI应该是由内核得来的

PHONY += headerdep
## 处理include中所有头文件## xargs --max-args 每次处理一个参数, 也就是对源码include目录下的*.h## 分别执行 $(srctree)/scripts/headerdep.pl -I$(srctree)/include xxx.h操作## 这个应该是检查头文件中的指令循环的??先passheaderdep: $(Q)find $(srctree)/include/ -name '*.h' | xargs --max-args 1 \ $(srctree)/scripts/headerdep.pl -I$(srctree)/include
# ---------------------------------------------------------------------------# Kernel headers#Default location for installed headersexport INSTALL_HDR_PATH = $(objtree)/usr
# If we do an all arch process set dst to include/arch-$(SRCARCH)hdr-dst = $(if $(KBUILD_HEADERS), dst=include/arch-$(SRCARCH), dst=include)
## arch自己的head和script,先pass## arch如果有自己的tools,则可以放到arch_headers中,脚本可以放到archscripts中## 若没有的话也没关系,伪目标没有规则是可以的,有的话就会自动构建PHONY += archheaders archscripts
PHONY += __headers__headers: $(version_h) scripts_basic uapi-asm-generic archheaders archscripts $(Q)$(MAKE) $(build)=scripts build_unifdef
PHONY += headers_install_allheaders_install_all: $(Q)$(CONFIG_SHELL) $(srctree)/scripts/headers.sh install
PHONY += headers_install## 这个目标是在help中指定的目标headers_install: __headers $(if $(wildcard $(srctree)/arch/$(SRCARCH)/include/uapi/asm/Kbuild),, \ $(error Headers not exportable for the $(SRCARCH) architecture)) $(Q)$(MAKE) $(hdr-inst)=include/uapi dst=include $(Q)$(MAKE) $(hdr-inst)=arch/$(SRCARCH)/include/uapi $(hdr-dst)
PHONY += headers_check_allheaders_check_all: headers_install_all $(Q)$(CONFIG_SHELL) $(srctree)/scripts/headers.sh check
PHONY += headers_checkheaders_check: headers_install $(Q)$(MAKE) $(hdr-inst)=include/uapi dst=include HDRCHECK=1 $(Q)$(MAKE) $(hdr-inst)=arch/$(SRCARCH)/include/uapi $(hdr-dst) HDRCHECK=1
ifdef CONFIG_HEADERS_CHECKall: headers_checkendif

6.6:kselftest相关规则
  在make help中,kselftest共有三个目标,分别为kselftest,kselftest-clean,kselftest-merge

PHONY += kselftestkselftest: $(Q)$(MAKE) -C $(srctree)/tools/testing/selftests run_tests
PHONY += kselftest-cleankselftest-clean: $(Q)$(MAKE) -C $(srctree)/tools/testing/selftests clean
PHONY += kselftest-mergekselftest-merge: $(if $(wildcard $(objtree)/.config),, $(error No .config exists, config your kernel first!)) $(Q)find $(srctree)/tools/testing/selftests -name config | \ xargs $(srctree)/scripts/kconfig/merge_config.sh -m $(objtree)/.config +$(Q)$(MAKE) -f $(srctree)/Makefile olddefconfig


6.7: device tree相关目标

ifneq ($(wildcard $(srctree)/arch/$(SRCARCH)/boot/dts/),)dtstree := arch/$(SRCARCH)/boot/dtsendif
## 若dstree非空ifneq ($(dtstree),)
%.dtb: prepare3 scripts_dtc $(Q)$(MAKE) $(build)=$(dtstree) $(dtstree)/$@
PHONY += dtbs dtbs_install dt_binding_checkdtbs dtbs_check: prepare3 scripts_dtc $(Q)$(MAKE) $(build)=$(dtstree)
dtbs_check: export CHECK_DTBS=1dtbs_check: dt_binding_check
dtbs_install: $(Q)$(MAKE) $(dtbinst)=$(dtstree)
ifdef CONFIG_OF_EARLY_FLATTREEall: dtbsendifendif
PHONY += scripts_dtcscripts_dtc: scripts_basic $(Q)$(MAKE) $(build)=scripts/dtcdt_binding_check: scripts_dtc $(Q)$(MAKE) $(build)=Documentation/devicetree/bindings

6.8:开启模块支持(CONFIG_MODULES)时的规则
6.8.1:模块编译(modules目标)
  在非外部模块编译时,若make没有指定目标(默认为all),且前开启了CONFIG_MODULES,那么modules会作为默认目标之一(也就是说内核in-tree的所有模块都会默认编译).
  modules一共有三个依赖项:
 

 => $(vmlinux-dirs): 记录的是所有内核编译时的子目录,对于模块编译来说,需要依赖于子目录中所有obj-m等指定的目标,obj-m等指定的是要编译成模块 的目标,其目标文件(*.o)时在构建$(vmlinux-dirs)的目录时生成的(1 在此依赖构建完毕之后,实际上obj => vmlinux: 对于内部模块编译,若需要生成MODVERSIONS,则需要依赖于vmlinux文件,若不需要则没有此依赖项 => modules.builtin: 其中记录了obj-$(CONFIG_XXX)中哪些被buildin到内核了(这些在m的情况下可以build到模块,所以这里叫做builtin的模块) 此文件是递归的,所有子目录的modules.builtin最终层层叠加到根目录的./modules.builtin文件中 而模块编译的命令为: * 将所有子目录的modules.order写到输出目录的$(objtree)/modules.order(此文件代表模块的编译顺序) * 编译*.mod.o,并配合前面的步骤(1)中生成的.o,最终链接(ld -r)出模块的*.ko文件 * 检查系统中编译出的所有ko中是否有重名模块
## 默认的编译规则中包含模块的编译all: modules
PHONY += modules## 编译模块时如果指定了KBUILD_BUILTIN,则代表依赖中添加了vmlinux## 模块的依赖项包括:## 1. $(vmlinux-dirs),即所有vmlinux编译的源码目录,这里面的obj-m等都是给模块编译用的,此步会编译出## 模块需要的.o文件,在后面的./scripts/Makefile.modpost中会将.o文件最终链接成.ko文件## 2. vmlinux,如果设置了KBUILD_BUILTIN,那么需要依赖于vmlinux## 3. modules.builtin, 记录了obj-$(CONFIG_XXX)中哪些用了y被builtin到内核了,所有这样的目标可在./modules.builtin## 中看到(每个目录都有./modules.builtin,但父目录包含子目录所有内容)modules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux) modules.builtin ## 整理所有目录下模块的编译顺序(modules.order)文件,到根目录的modules.order ## 子目录的module.order是在编译过程中生成的,见 ./scripts/makefile.build 中 modorder-cmds $(Q)$(AWK) '!x[$$0]++' $(vmlinux-dirs:%=$(objtree)/%/modules.order) > $(objtree)/modules.order @$(kecho) ' Building modules, stage 2.'; ## stage 2的ko编译 $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost ## 检查是否有重名模块 $(Q)$(CONFIG_SHELL) $(srctree)/scripts/modules-check.sh
## 依赖于当前目录的每个直接子目录modules.buitin文件modules.builtin: $(vmlinux-dirs:%=%/modules.builtin) ## 这里的'!x[$$0]++'应该是去重 $(Q)$(AWK) '!x[$$0]++' $^ > $(objtree)/modules.builtin
## $*代表前面的%## 此命令实际上列举的是当前配置下,内核obj-$(CONFIG_XXX)中CONFIG_XXX为y的情况,代表被builtin到内核的模块## 这个CONFIG_XXX也有可能为m,所以叫做被builtin到内核的模块,其递归处理当前目录中每个参与编译的子目录(由当前目录中的## obj-$(CONFIG_xxx),和subdir-y/m指定的目录),最终在当前目录生成./modules.builtin文件,其中记录当前目录所有builtin## 到内核的模块,同时也记录了所有递归的子目录中modules.builtin的内容.## 最终的记录格式为 kernel/xxxx.ko(所有.o替换为.ko,代表着原本应该是一个ko,但被builtin到内核了)## 其中auto.conf是内核配置项,tristate.conf是将配置项中的y/m大写后生成配置项%/modules.builtin: include/config/auto.conf include/config/tristate.conf $(Q)$(MAKE) $(modbuiltin)=$*


6.8.2:为外部模块编译构建环境(modules_prepare)目标
  非外部模块编译的help命令中有单独指出modules_prepare这一项,其主要是给外部模块编译使用的.
  外部模块的编译前提是需要内核已经做了一些准备工作,换成代码就是内核的prepare目标必须已经编译了(这要求内核必须开启CONFIG_MODULES,否则prepare目标内容不对):
  * 通常的做法是在编译外部模块之前就先对内核做了一个完整的build
  * 还可以使用一种方法就是在内核执行 make modules_prepare, 这就可以让内核先构建出 prepare相关目标,之后即可编译外部模块了
  但有一点需要注意是,make modules_prepare的方法无法支持CONFIG_MODVERSIONS特性,因为其依赖于vmlinux,而外部模块编译不应该引起内核的编译,故make modules_prepare不能为外部模块提供vmlinux,也就无法支持.

PHONY += modules_preparemodules_prepare: prepare

6.8.3:模块安装与模块签名(modules_install,modules_sign)
  modules_install也是非外部模块编译时help的一个目标选项,其没有命令,只包括两个依赖项:
 

 => _modinst_: 只有命令,没有依赖项 * 将$(objtree)目录的modules.order, modules.builtin, modules.buintin.modinfo(包含builtin模块的modinfo) 输出到 $(MODLIB)目录 * 链接源码目录到 $(MODLIB)/source * 执行Makefile.modinst处理$(MODVERDIR)/*.mod记录的本地文件存在的所有ko,处理过程包括三步: - strip符号表(可指定选项) - 模块签名(必须开启CONFIG_MODULE_SIG_ALL,否则通过make无法对模块签名,必须手动调用sign-file签名) - 模块压缩(若需要) 其中: 若$(MODLIB)目录为空,则输出到当前系统的根目录,否则输出到对应的相对目录 => _modinst_post: 其调用/sbin/depmod 生成内核模块的依赖关系

  按照标准流程,实际上在内核模块编译完(如make modules),其所有模块是没有签名的,必须再手动执行一个make module_install才能对模块进行签名
  modules_sign实际上是将模块签名这个动作单独拎出来了,这样模块签名就不必要去install了,而是直接调用modules_sign即可,但还是受到CONFIG_MODULE_SIG_ALL的限制,因为后者不开启,最终实际签名的的mod_sign_cmd命令为空.

## 模块安装的,单独的目标,默认没人调用,如果想做模块签名,就要make modules_installPHONY += modules_installmodules_install: _modinst_ _modinst_post
PHONY += _modinst_## _modinst_的操作包括## * 将源码目录软链到输出目录,这个输出目录如果没指定,那么直接就install到根目录/了## * 将输出目录的modules.order,modules.builtin,modules.builtin.modinfo 复制到输出目录## * Makefile.modinst中会找到MODVERDIR中记录的且本地存在的所有.ko,依次对所有ko调用modules_install## 其操作包括: strip, sign-file, compress 到MODLIB目录## 也就是说 modules_install实际上就是复制操作,只不过对于ko会做strip,签名和压缩_modinst_: @rm -rf $(MODLIB)/kernel @rm -f $(MODLIB)/source @mkdir -p $(MODLIB)/kernel @ln -s $(abspath $(srctree)) $(MODLIB)/source @if [ ! $(objtree) -ef $(MODLIB)/build ]; then \ rm -f $(MODLIB)/build ; \ ln -s $(CURDIR) $(MODLIB)/build ; \ fi @cp -f $(objtree)/modules.order $(MODLIB)/ @cp -f $(objtree)/modules.builtin $(MODLIB)/ ## modules.builtin.modinfo 这个文件包含所有built-in模块的modinfo @cp -f $(objtree)/modules.builtin.modinfo $(MODLIB)/ $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
PHONY += _modinst_post## 这里执行 depmod.sh,这个是用来生成模块依赖关系的,如:## /lib/modules/2.5.53/kernel/a.ko: /lib/modules/2.5.53/kernel/c.ko /lib/modules/2.5.53/kernel/b.ko## /lib/modules/2.5.53/kernel/b.ko:## /lib/modules/2.5.53/kernel/c.ko: /lib/modules/2.5.53/kernel/b.ko_modinst_post: _modinst_ $(call cmd,depmod) ## 模块签名ifeq ($(CONFIG_MODULE_SIG), y)PHONY += modules_sign
## 这个是单独做代码签名的,实际上是会遍历MODVERDIR中记录的且本地存在的所有.ko,依次对所有ko调用mod_sign_cmd签名.modules_sign: $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modsignendif

  这里额外提一句模块签名的mod_sign_cmd命令,在Makefile.modsign中,如果开启了CONFIG_MODULE_SIG_ALL,那么make modules_install时会对每个模块依次执行mod_sign_cmd,命令如下,其中:
  * CONFIG_MODULE_SIG_HASH 记录的是模块签名使用的hash算法,内核通过如 CONFIG_MODULE_SIG_SHA1/256等可配置
     * CONFIG_MODULE_SIG_KEY 是一个pem文件(base64编码),里面包含签名使用的私钥(可以加密码)和公钥证书(见后)
     * certs/signing_key.x509 是从pem文件中导出的公钥证书(der)格式

## 构造模块签名的目标和对应签名命令ifdef CONFIG_MODULE_SIG_ALL$(eval $(call config_filename,MODULE_SIG_KEY))mod_sign_cmd = scripts/sign-file $(CONFIG_MODULE_SIG_HASH) $(MODULE_SIG_KEY_SRCPREFIX)$(CONFIG_MODULE_SIG_KEY) certs/signing_key.x509elsemod_sign_cmd = trueendif

  按照内核的要求,实际上CONFIG_MODULE_SIG_KEY 只需要包含私钥即可,而 certs/signing_key.x509要包含的是公钥证书,只不过默认情况下前者包含了公钥证书和私钥,后者是从前者导出的,以下命令可查看pem/der文件内容:

## openssl x509 -text -in ./signing_key.pem## openssl x509 -in certs/signing_key.x509 -inform DER -noout -text

6.9:未开启模块支持时的规则
  在非外部模块编译时,未开启模块签名(CONFIG_MODULES),则modules,modules_install目标什么也不做,直接提示错误(外部模块编译必须开启CONFIG_MODULES,否则prepare不过没法编译)

PHONY += modules modules_installmodules modules_install: @echo >&2 @echo >&2 "The present kernel configuration has modules disabled." @echo >&2 "Type 'make config' and enable loadable module support." @echo >&2 "Then build a kernel with module support enabled." @echo >&2 @exit 1endif # CONFIG_MODULES

6.10:非外部模块编译时的其他目标:
  * clean/mrproper/disclean
    clean的操作有三个级别:
    - make clean: 会删除大部分已生成的文件,只留下编译外部模块必须的文件
    - make mrproper: 会删除当前的配置文件,和所有生成的文件
    - make distclean: 删除编译器备份文件,patch上下的文件等.
   三者是层层递进的,其依赖关系中也可看出这一点:

clean: archclean vmlinuxcleanmrproper: clean $(mrproper-dirs)distclean: mrproper

  * %src-pkg/%pkg: 内核打包相关的
  * help: 输出对Kbuild target的简单描述
  * $(DOC_TARGETS):可用来生成各种格式的内核文档,如PDF,html,doc等
  * script_gdb:生成一些gdb相关的脚本


7.外部模块编译
7.1:模块编译(modules目标)
  编译外部模块的前提前面也提到了,首先是内核必须先编译一遍,或者执行过make modules_prepare(但不支持CRC),在此基础上,外部模块的编译规则如下:

 modules => $(module-dirs) 这里就是KBUILD_EXTMOD目录 => prepare:这里的prepare只创建并清空MODVERDIR目录(所有的准备工作在make modules_prepare中已经做过了) => 检查输出目录的Module.symvers是否存在,不存在报warning * 直接通过Makefile.build编译KBUILD_EXTMOD,对于模块编译会编译其中如obj-m制定的所有目标文件(源码=>.o) 这是模块编译的stage1 * 调用Makefile.modpost来将*.o编译为*.ko,这是模块编译的stage2
## 这个代表子目录的obj-m等是否编译KBUILD_MODULES := 1PHONY += $(objtree)/Module.symvers
## 这里面记录所有符号的CRC,若当前目录没有CRC则提示warning$(objtree)/Module.symvers: @test -e $(objtree)/Module.symvers || ( \ echo; \ echo " WARNING: Symbol version dump $(objtree)/Module.symvers"; \ echo " is missing; modules will have no dependencies and modversions."; \ echo )
## 为KBUILD_EXTMOD为一个字符串添加 _module_ 前缀,好做为目标module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD))
PHONY += $(module-dirs) modules## => prepare只需要构建并清空MODVERDIR目录## => Module.symvers只检查了此文件是否存在## * 通过Makefile.build构建KBUILD_EXTMOD指定的目录(引入KBUILD_EXTMOD中的Makefile)## KBUILD_EXTMOD目录中所有obj-m等指定的目标全部会被编译(因为KBUILD_MODULES=1)$(module-dirs): prepare $(objtree)/Module.symvers $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@)
## modules实际上是要编译KBUILD_EXTMOD中的内容## => $(module-dirs)中做stage1的编译(源码=>*.o)## * modules命令中做stage2的编译(*.o=>*.ko)modules: $(module-dirs) @$(kecho) ' Building modules, stage 2.'; $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
## 这里的prepare只是构建并清空MODVERDIR目录PHONY += prepareprepare: $(cmd_crmodverdir)


7.2:模块安装(modules_install目标)
  外部模块安装流程和内部模块安装流程基本相同,同样都是调用Makefile.modinst来安装(包括strip, sign, compress), 唯一区别就是输出到了*/extra目录.

## 若没指定安装目录,则默认安装到/extra目录install-dir := $(if $(INSTALL_MOD_DIR),$(INSTALL_MOD_DIR),extra)PHONY += _emodinst_## 外部模块安装到*/extra目录,和内部模块一样,安装步骤包括 strip, sign, compress_emodinst_: $(Q)mkdir -p $(MODLIB)/$(install-dir) $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst## post install 同样也是构建模块的依赖关系PHONY += _emodinst_post_emodinst_post: _emodinst_ $(call cmd,depmod)

7.3:其他目标
  * clean:
    这里定义了部分clean的目录
  * help:
    外部模块编译时的help命令,其中只包含 modules, modules_install, clean 三个目标.


8.其他公共目标
8.1:clean
  前面不管是内部模块还是外部模块,实际上都是定义了clean的依赖,而clean真正的命令定义在这里:

clean: $(clean-dirs) $(call cmd,rmdirs) $(call cmd,rmfiles) @find $(if $(KBUILD_EXTMOD), $(KBUILD_EXTMOD), .) $(RCS_FIND_IGNORE) \ \( -name '*.[aios]' -o -name '*.ko' -o -name '.*.cmd' \ -o -name '*.ko.*' \ -o -name '*.dtb' -o -name '*.dtb.S' -o -name '*.dt.yaml' \ -o -name '*.dwo' -o -name '*.lst' \ -o -name '*.su' \ -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \ -o -name '*.lex.c' -o -name '*.tab.[ch]' \ -o -name '*.asn1.[ch]' \ -o -name '*.symtypes' -o -name 'modules.order' \ -o -name modules.builtin -o -name '.tmp_*.o.*' \ -o -name '*.c.[012]*.*' \ -o -name '*.ll' \ -o -name '*.gcno' \) -type f -print | xargs rm -f

8.2:各种check与print输出目标
  * includecheck: 负责检查重复的头文件包含
  * versioncheck: 版本合理性检查
  * namespacecheck:已编译好的内核的Name space检查
  * export_report: 列出所有的导出符号的使用情况
  * kernelrelease: 输出内核版本字符串

@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ make kernelrelease4.19.109-g6c232927e-dirty

  * kernelversion:

@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ make kernelversion4.19.109

 * image_name:

@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ make image_namearch/arm64/boot/Image.gz

8.3:tools目录的编译
  tools目录没有列到内核或模块的依赖项中,也没有默认使用Kbuild系统来编译,若子目录需要使用kbuild系统则需主动include Makefile.build(可参考子目录的makefile文件)


tools/: FORCE $(Q)mkdir -p $(objtree)/tools $(Q)$(MAKE) LDFLAGS= MAKEFLAGS="$(tools_silent) $(filter --j% -j,$(MAKEFLAGS))" O=$(abspath $(objtree)) subdir=tools -C $(src)/tools/
tools/%: FORCE $(Q)mkdir -p $(objtree)/tools $(Q)$(MAKE) LDFLAGS= MAKEFLAGS="$(tools_silent) $(filter --j% -j,$(MAKEFLAGS))" O=$(abspath $(objtree)) subdir=tools -C $(src)/tools/ $*

8.4:make中单独指定某个.i/.ll/.lst/.o/.s/.symtypes/.ko文件的编译
  这里提供的是内部和外部模块的公用规则,如果输入只指定了某个目标,那么就直接调用Makefile.build单独编译此目标.对于ko则在此基础上通过Makefile.modpost执行stage2编译.


## 解析后就是当前文件的全路径(外部目录或内核目录)build-target = $(if $(KBUILD_EXTMOD), $(KBUILD_EXTMOD)/)$@## build-target 中的路径,去除最后的/build-dir = $(patsubst %/,%,$(dir $(build-target)))
## 所有单独指定的%.i/ll/lst/o/s/symtypes/ko编译,同样都是用$(build)去文件对应的目录下去build,然后目标就是当前文件%.i: prepare FORCE $(Q)$(MAKE) $(build)=$(build-dir) $(build-target)%.ll: prepare FORCE $(Q)$(MAKE) $(build)=$(build-dir) $(build-target)%.lst: prepare FORCE $(Q)$(MAKE) $(build)=$(build-dir) $(build-target)%.o: prepare FORCE $(Q)$(MAKE) $(build)=$(build-dir) $(build-target)%.s: prepare FORCE $(Q)$(MAKE) $(build)=$(build-dir) $(build-target)%.symtypes: prepare FORCE $(Q)$(MAKE) $(build)=$(build-dir) $(build-target)## 这里是stage2编译,在.o中是执行了stage1编译%.ko: %.o $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost

8.5: 目录目标的编译
  若单独指定某个目录作为目标,则有以下规则:

# ModulesPHONY += //: ./
# Make sure the latest headers are built for DocumentationDocumentation/ samples/: headers_install
## 当make的目标为某个子目录时候,则默认模块相关和buintin相关的目标都要编译## KBUILD_BUINTIN默认值为1,所以这里的submake只需要传入KBUILD_MODULES=1即可%/: prepare FORCE $(Q)$(MAKE) KBUILD_MODULES=1 $(build)=$(build-dir)
8.6: if_changed的支持
## 当前文件的targets 就一个vmlinux,因为规则中使用了if_changed命令的目标就vmlinux一个,这里需要支持if_changed宏## submake target的.cmd文件在submake中自己includeexisting-targets := $(wildcard $(sort $(targets))) ## include 文件不存在时不报错-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)