Kbuild系统源码分析(二)—./Makefile
上接 <Kbuild系统源码分析(一)—./Makefile>
5.对非xxx_config目标的编译
5.1:设置./Makefile的默认目标
./Makefile的开头可知,其默认的编译目标是_all,之前的处理流程中处理了对混合目标,xxx_config等编译的情况,而大部分情况都是走到这里,这里要先设置make的默认目标,在整个./Makefile中,_all的编译仅在这里唯一设置了一次,根据当前是否是编译外部模块,_all中的默认目标有所区别:
* 如果编译的是外部模块,那么_all只依赖于伪目标modules
* 如果是编译非外部模块,那么_all只依赖于伪目标all
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
## 当编译in-tree的代码时,默认的目标是伪目标all,all是只有在编译非外部模块才会用到的目标(语义上)
_all: all
else
## 编译out-tree的代码时,默认的目标是伪目标modules,modules是在编译外部或非外部模块均有可能用到的目标
_all: modules
endif
其中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,则默认也需要编译内核vmlinux
ifeq ($(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 := 1
endif
## 如果目标为空,那么默认也需要编译模块(就是all或modules的情况,取决于KBUILD_EXTMOD
ifeq ($(MAKECMDGOALS),)
KBUILD_MODULES := 1
endif
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.conf
endif
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 := -pg
endif
## retpoline是防specture攻击的相关特性
RETPOLINE_CFLAGS_GCC := -mindirect-branch=thunk-extern -mindirect-branch-register
RETPOLINE_VDSO_CFLAGS_GCC := -mindirect-branch=thunk-inline -mindirect-branch-register
RETPOLINE_CFLAGS_CLANG := -mretpoline-external-thunk
RETPOLINE_VDSO_CFLAGS_CLANG := -mretpoline
RETPOLINE_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_CFLAGS
export 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_XXXFLAGS
ARCH_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 syncconfig
else
## 这里是不需要 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-config
endif # $(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_SIZE
KBUILD_CFLAGS += -Os
else
KBUILD_CFLAGS += -O2
endif
## 包含kcov的配置,此文件中没有新增规则
include scripts/Makefile.kcov
## 包含插件配置,此文件中没有新增规则
include scripts/Makefile.gcc-plugins
## 栈溢出保护相关开关
stackp-flags-$(CONFIG_CC_HAS_STACKPROTECTOR_NONE) := -fno-stack-protector
stackp-flags-$(CONFIG_STACKPROTECTOR) := -fstack-protector
stackp-flags-$(CONFIG_STACKPROTECTOR_STRONG) := -fstack-protector-strong
KBUILD_CFLAGS += $(stackp-flags-y)
## clang编译器相关开关
ifdef CONFIG_CC_IS_CLANG
KBUILD_CPPFLAGS += -Qunused-arguments
......
KBUILD_CFLAGS += -mno-global-merge
else
## 非clang则加这个
## 未使用但初始化的变量不报错
KBUILD_CFLAGS += -Wno-unused-but-set-variable
endif
include scripts/Makefile.kasan
include scripts/Makefile.extrawarn
include 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 = true
ifdef 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_XZ
endif # CONFIG_MODULE_COMPRESS
export 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.x509
else
mod_sign_cmd = true
endif
export 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.a
init-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.a
vmlinux-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_recursive
autoksyms_recursive: $(vmlinux-deps)
## 内核和模块可能会通过EXPORT_SYMBOL及其变种为其他其他模块导出符号,所有这些符号并不是每次编译都用到的, 这取决于当前内核配置了编译哪些模块.
## CONFIG_TRIM_UNUSED_KSYMS的作用就是将当前编译中所有未使用的EXPORT的符号drop掉. 这会为编译器提供更多的优化可能(尤其是LTO),以减少代码和二进制大小,
## 同时也有一定的安全优势, 但如果需要编译out-of-tree 模块的话,则不能开启此config
ifdef CONFIG_TRIM_UNUSED_KSYMS
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \
"$(MAKE) -f $(srctree)/Makefile vmlinux"
endif
ifdef CONFIG_TRIM_UNUSED_KSYMS
KBUILD_vmlinuxMODULES := 1
endif
autoksyms_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 执行postlink
cmd_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-dirty
include/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.release
ifneq ($(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/objtool
prepare: prepare0 prepare-objtool
## 这个是变量,后面是目标,二者不一样
asm-generic := -f $(srctree)/scripts/Makefile.asm-generic obj
PHONY += 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-objtool
prepare-objtool: $(objtool_target)
uts_len := 64
define 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操作
## 这个应该是检查头文件中的指令循环的??先pass
headerdep:
$(Q)find $(srctree)/include/ -name '*.h' | xargs --max-args 1 \
$(srctree)/scripts/headerdep.pl -I$(srctree)/include
# ---------------------------------------------------------------------------
# Kernel headers
#Default location for installed headers
export 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_all
headers_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_all
headers_check_all: headers_install_all
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/headers.sh check
PHONY += headers_check
headers_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_CHECK
all: headers_check
endif
6.6:kselftest相关规则
在make help中,kselftest共有三个目标,分别为kselftest,kselftest-clean,kselftest-merge
PHONY += kselftest
kselftest:
$(Q)$(MAKE) -C $(srctree)/tools/testing/selftests run_tests
PHONY += kselftest-clean
kselftest-clean:
$(Q)$(MAKE) -C $(srctree)/tools/testing/selftests clean
PHONY += kselftest-merge
kselftest-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/dts
endif
## 若dstree非空
ifneq ($(dtstree),)
%.dtb: prepare3 scripts_dtc
$(Q)$(MAKE) $(build)=$(dtstree) $(dtstree)/$@
PHONY += dtbs dtbs_install dt_binding_check
dtbs dtbs_check: prepare3 scripts_dtc
$(Q)$(MAKE) $(build)=$(dtstree)
dtbs_check: export CHECK_DTBS=1
dtbs_check: dt_binding_check
dtbs_install:
$(Q)$(MAKE) $(dtbinst)=$(dtstree)
ifdef CONFIG_OF_EARLY_FLATTREE
all: dtbs
endif
endif
PHONY += scripts_dtc
scripts_dtc: scripts_basic
$(Q)$(MAKE) $(build)=scripts/dtc
dt_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_prepare
modules_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_install
PHONY += modules_install
modules_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.modsign
endif
这里额外提一句模块签名的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.x509
else
mod_sign_cmd = true
endif
按照内核的要求,实际上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_install
modules 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 1
endif # CONFIG_MODULES
6.10:非外部模块编译时的其他目标:
* clean/mrproper/disclean
clean的操作有三个级别:
- make clean: 会删除大部分已生成的文件,只留下编译外部模块必须的文件
- make mrproper: 会删除当前的配置文件,和所有生成的文件
- make distclean: 删除编译器备份文件,patch上下的文件等.
三者是层层递进的,其依赖关系中也可看出这一点:
clean: archclean vmlinuxclean
mrproper: 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 := 1
PHONY += $(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 += prepare
prepare:
$(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 kernelrelease
4.19.109-g6c232927e-dirty
* kernelversion:
@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ make kernelversion
4.19.109
* image_name:
@ubuntu:~/qemu_aarch64_device/linux-4.19.109$ make image_name
arch/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: 目录目标的编译
若单独指定某个目录作为目标,则有以下规则:
# Modules
PHONY += /
/: ./
# Make sure the latest headers are built for Documentation
Documentation/ 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中自己include
existing-targets := $(wildcard $(sort $(targets)))
## include 文件不存在时不报错
-include $(foreach f,$(existing-targets),$(dir $(f)).$(notdir $(f)).cmd)