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 := 1endifexport 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_TRACERCC_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-variableendifinclude 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_COMPRESSifdef CONFIG_MODULE_COMPRESS_GZIPmod_compress_cmd = gzip -n -fendif # CONFIG_MODULE_COMPRESS_GZIPifdef CONFIG_MODULE_COMPRESS_XZmod_compress_cmd = xz -fendif # 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_KSYMSKBUILD_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-genericPHONY += prepare-objtoolprepare-objtool: $(objtool_target)uts_len := 64define filechk_utsrelease.hif [ `echo -n "$(KERNELRELEASE)" | wc -c ` -gt $(uts_len) ]; then \echo '"$(KERNELRELEASE)" exceeds $(uts_len) characters' >&2; \exit 1; \fi; \echo \#define UTS_RELEASE \"$(KERNELRELEASE)\"endefdefine filechk_version.hecho \#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 archscriptsPHONY += __headers__headers: $(version_h) scripts_basic uapi-asm-generic archheaders archscripts$(Q)$(MAKE) $(build)=scripts build_unifdefPHONY += headers_install_allheaders_install_all:$(Q)$(CONFIG_SHELL) $(srctree)/scripts/headers.sh installPHONY += 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 checkPHONY += 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=1ifdef 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_testsPHONY += kselftest-cleankselftest-clean:$(Q)$(MAKE) -C $(srctree)/tools/testing/selftests cleanPHONY += 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_checkdtbs_install:$(Q)$(MAKE) $(dtbinst)=$(dtstree)ifdef CONFIG_OF_EARLY_FLATTREEall: dtbsendifendifPHONY += 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: modulesPHONY += 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_postPHONY += _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.modinstPHONY += _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)
