Kbuild系统源码分析(一)—./Makefile
linux内核的Kbuid系统实际上就是make & makefile的一种应用,其起始于内核根目录的./Makefile; 文档记录在内核./Documentation/Kbuild目录下,代码解析如下:
1. ./Makefile文件入口
1.1:内核版本定义
VERSION = 5
PATCHLEVEL = 2
SUBLEVEL = 5
EXTRAVERSION =
NAME = Bobtail Squid
1.2:默认目标定义
# _all是个伪目标,每次运行make _all都一定会重新编译
# 最后一句代码指定了.PHONY: $(PHONY),也就是说PHONY中指定的目标均为伪目标
PHONY := _all
1.3:检查源码目录、输出目录、当前目录三者是否相同以决定是否使用submake重新启动编译
1.3.1:设置日志输出级别
根据命令行指定了 make V=0/1/2, 和是否指定了 -s 来决定日志输出的级别
## 若变量V来自命令行,则设置 KBUILD_VERBOSE = $(V)
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
## 否则默认将其设置为0
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif
## 若KBUILD_VERBOSE = 0, 则默认quite
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
## filter-out 过滤MAKEFLAGS(记录部分命令行参数,如-k/-s) 中的 --开头的以空格分开的字符, 非--开头的选项则保留,
## 然后调用findstring继续处理 filter-out的结果,看是否发现了s字符串,若发现代表当前指定了 -s参数(如make -s),那么就设置quiet=silent_
ifneq ($(findstring s,$(filter-out --%,$(MAKEFLAGS))),)
quiet=silent_
endif
1.3.2:设置源码目录和输出目录
Kbuild中默认编译输出目录$(abs_objtree)为当前目录$(CURDIR),通过指定如make O=xxx的形式可修改$(KBUILD_OUTPUT)进而修改输出目录$(abs_objtree),最终:
* 若当前目录是要输出的目录(没有设置O=xxx),则设置不输出"Entering directory ..."消息
* 若当前目录非要输出的目录(设置了O=xxx),则设置need-sub-make := 1(此变量导致后续重启submake来重新编译,因为make必须重启才能切目录)
## 这里O必须是来自命令行的,其他的不处理,变量O是用来指定make的输出目录的
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
## 若有用户指定的输出目录
ifneq ($(KBUILD_OUTPUT),)
## 则获取此目录的全路径, 这条指令是创建并切换到KBUILD_OUTPUT,并返回其全路径
abs_objtree := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) && pwd)
## 若输出全路径为空则报错,可能是目录创建失败
$(if $(abs_objtree),, \
$(error failed to create output directory "$(KBUILD_OUTPUT)"))
## 解析符号链接和相对路径(这里应该是就解析符号链接了)
abs_objtree := $(realpath $(abs_objtree))
else
## 否则绝对路径就是当前路径CURDIR,这个是makefile内置变量
abs_objtree := $(CURDIR)
endif # ifneq ($(KBUILD_OUTPUT),)
## 若目标目录就是当前目录,那么不输出 make进入目录的消息
ifeq ($(abs_objtree),$(CURDIR))
# Suppress "Entering directory ..." unless we are changing the work directory.
MAKEFLAGS += --no-print-directory
else
## 若通过make o=xxx 指定的输出目录和源码目录不同,则设置 need-sub-make :=1, 这会导致后面使用
## 一个submake来重启整个make,因为make是不能运行时切换目录的只能在启动时设置目录
need-sub-make := 1
endif
源码目录$(abs_srctree)是makefile文件所在目录,源码目录路径中不能出现space和colon符号, 若源码目录和输出目录不同,则需要:
* MAKEFLAGS += --include-dir=$(abs_srctree) //确保切换到输出目录重新执行的submake能找到源码目录中的文件
* need-sub-make := 1 //代表需要使用submake重启make
若是make 3.x版本,则也要默认使用submake
## 源码目录为make指定的最后一个makefile所在的目录
abs_srctree := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
## $(subst FROM,TO,TEXT) 将TEXT中的FROM替换为TO
## 这里将源码目录中的:替换为空格,然后检查当前是否只有一个字符串,也就是说源码目录路径中不能出现空格和冒号
ifneq ($(words $(subst :, ,$(abs_srctree))), 1)
$(error source directory cannot contain spaces or colons)
endif
## 若源码目录和输出目录不同,则要在make参数中增加--include-dir,然后其子make使其有效重新编译
ifneq ($(abs_srctree),$(abs_objtree))
## MAKEFLAGS是make的内置变量,记录当前make的部分参数,如-f/-w, 在当前make中修改此变量再启动submake 则部分-/--开头的参数可以直接传给submake
## 这里加上--include-dir的目的是让submake包含源码目录,因为submake要后续要切换到输出目录工作了,若无此包含则会导致源码目录的文件找不到
MAKEFLAGS += --include-dir=$(abs_srctree)
need-sub-make := 1
endif
## 若当前make的版本是3.x,则默认就要走submake
ifneq ($(filter 3.%,$(MAKE_VERSION)),)
need-sub-make := 1
$(lastword $(MAKEFILE_LIST)): ;
endif
## 将当前源码目录、输出目录、是否需要submake都export
export abs_srctree abs_objtree
export sub_make_done := 1
注:
* 源码目录、输出目录、当前目录实际上都不影响Kbuild的编译,只不过是需要多启一个submake;
* 当源码目录,输出目录,当前目录只要有一个不同,就要进入submake,进入submake后会调整输出目录=当前目录,但源码目录还是可能跟二者不等,kbuld中通过增加--include-dir解决此问题
1.3.3:sub-make(切换目录)重启构建
当需要重启构建时,则先为当前make命令行指定的所有目标都定义一个规则(need-sub-make确保这些规则不会被重定义);在此规则中,命令行输入的所有目标都依赖于sub-make,commands都为空,而sub-make则通过:
* -c切换到输出目录
* -f指定源码目录下的makefile(同样也是当前这个makefile)
* 传入$(MAKECMDGOALS)以确保submake会编译当前make命令行指定的所有目标
* 当前make的命令行参数和环境变量都默认传给submake
此submake最终导致当前makefile重新执行,且此时源码目录、输出目录、当前目录三者相同,整个1.3的流程pass,直接进入1.4
## 若需要切换目录则启动submake编译,当前make中的所有目标都在这个if中动态定义,当前make除了启动submake再无其他工作
ifeq ($(need-sub-make),1)
## MAKECMDGOALS记录的是命令行中指定的本次make编译的所有目标(可以是一个或多个)
## 这里将这些目标全部加入到PHONY变量中,此文件最后一句会将这些目标全部定义为伪目标, sub-make也同样也被定义为伪目标
PHONY += $(MAKECMDGOALS) sub-make
## 过滤MAKECMDGOALS,去除其中的_all/sub-make/makefile文件名,然后将其余的所有目标的规则均定义到这里
## make解析makefile时,到这里只有ifeq ($(need-sub-make),1)成立才会解析,否走1.4后的case,故这里和后续目标的定义并不冲突(只有一个真正存在,取决于need-sub-make)
$(filter-out _all sub-make $(lastword $(MAKEFILE_LIST)), $(MAKECMDGOALS)) _all: sub-make
@## 这里的:是shell中的空命令,只是个占位符,不做实际操作
@:
## 上面为所有非sub-make目标都定义了规则(命令为空),这些目标都是伪目标,默认编译,这些目标都依赖sub-make,
## 所以不论当前要构建什么目标,最终实际上都要构建sub-make,在need-sub-make为1时也是此sub-make真正处理的make,这里:
## -C $(abs_objtree) 代表sub-make的工作目录为 $(abs_objtree)
## -f $(abs_srctree)/Makefile 代表当前的makefile文件
## $(MAKECMDGOALS)是当前make的目标,全部传递给sub-make(这里Q只是表示当前语句是否回显)
## 而make的命令行参数,前面export的环境变量都是默认传给sub-make的
# Invoke a second make in the output directory, passing relevant variables
sub-make:
$(Q)$(MAKE) -C $(abs_objtree) -f $(abs_srctree)/Makefile $(MAKECMDGOALS)
endif # need-sub-make
endif # sub_make_done
## 如果是sub_make_done,则后续代码基本无效,直接跳转到此文件尾
2:Kbuild编译(非sub-make代码开始)
2.1:make输出信息调整
## 这个ifeq基本上到结尾了,当前make的目录已经调整到了变量O=指定的目录, 变量O是命令行变量,还是存在的,但已经不需要再调整目录了
ifeq ($(need-sub-make),)
## 所有sub-make都不输出"Entering directory ..."信息
MAKEFLAGS += --no-print-directory
2.2:确定export变量KBUILD_CHECKSRC、KBUILD_EXTMOD、KBUILD_SRC、srctree,objtree, VPATH
这里确定Kbuild变量:
* KBUILD_CHECKSRC: 决定是否开启编译期静态代码检查(make C=0/1/2),默认不执行编译期间检查,除非指定make C=1/2
* KBUILD_EXTMOD:为一个要编译的外部目录,由于Kbuild的起始makefile必须是内核根目录的./Makefile,如果编译内核外部的代码则需要指定外部代码所在的目录(目录中
同时应存在对应的makefile),这个KBUILD_EXTMOD就是要编译的外部模块的目录,如为编译模块时指定模块的源码目录(make M=xxx)
* KBUILD_SRC:给selftests用的变量
* srctree: 源码目录,实际上是尽量转化为当前目录的相对目录(./,../),若不是二者,则使用源码目录的绝对路径
* objtree: 是输出目录,也就是当前目录(./)
* VPATH: = $(srctree)
这里同时初始化了变量src/obj
## 若C是命令行传入的参数,则设置 KBUILD_CHECKSRC = $(C)
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
## 默认不执行编译期间检查
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
## 设置要编译的外部模块目录
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
## 前面sub-make切换目录后,这个srctree和objtree目录还是不变的,二者是否相等还是取决于是否指定了 O=xxx
ifeq ($(abs_srctree),$(abs_objtree))
# building in the source tree
## 因为到这里肯定是切换到 abs_objtree了,所以srctree用当前目录即可
srctree := .
else
## 若输出目录是源码目录的子目录,则srctree是针对当前目录的 ../
ifeq ($(abs_srctree)/,$(dir $(abs_objtree)))
# building in a subdirectory of the source tree
srctree := ..
else
## 其他情况就直接指定srctree绝对路径了
srctree := $(abs_srctree)
endif
KBUILD_SRC := $(abs_srctree)
endif
export KBUILD_CHECKSRC KBUILD_EXTMOD KBUILD_SRC
objtree := .
src := $(srctree)
obj := $(objtree)
VPATH := $(srctree)
export srctree objtree VPATH
2.3:确定是否需重新生成.config和对应的*.conf/*.h文件,是否是混合编译(mixed-targets)
Kbuild生成.config, syncconfig依赖.config生成*.conf/*.h的流程图如下(参考[1])
1)首先是conf工具根据各种config(如defconfig) + kconfig 生成.config
2)其次是syncconfig工具根据.config生成make或内核代码中使用的各种.conf和.h文件
这部分代码完成的工作包括:
* 确定当前是否要重新编译.config(最终决定于config-targets变量)
- .config只有在make没有指定M=(非外部模块编译),且明确指明了[%]config时才会编译
* 确定当前的编译目标是否需要include .config(最终取决于dot-config变量)
- 若当前make的目标包含且仅包含$(no-dot-config-targets)中的目标时(见下),当前编译不需要include .config中的配置项,如help.
* 确定当前的编译目标是否需要重新编译 *.conf/*.h(最终取决于may-sync-config变量)
- 若当前make的目标包含且仅包含$(no-sync-config-targets)中的目标时(见下),当前编译不需要重新生成*.conf和*.h文件).
no-dot-config-targets是其自己,也就是说不用生成.config时一定也不会依赖*.conf/*.h,因为后者树依赖于前者横撑的
- 若当前指定了 make M=,则不论其他参数如何设置,默认都不重新生成*.conf/*.h,否则模块编译结果可能与当前内核不一致.
* 确定当前是否为混合编译(最终取决于mixed-targets变量)
mixed-targets代表当前make的目标中是否包含多种类型的target,若存在多种类型的target则开启混合编译模式,后续启动submake对所有目标一个一个
依次进行构建,其判断依据是,若make命令的目标中:
- 指定了[%]config 加其他任意目标 ,则要要设置mixed-target
- 同时指定了clean-targets中的目标和非clean-targets中的目标,则要要设置mixed-target
- 同时存在 install 和 modules_install 两个目标,则要设置mixed-target
具体代码如下:
## version.h是动态生成的(源码目录中无此文件),其内容为:
## @ubuntu:~/qemu_aarch64_device/android_kernel/common_android_4.14/common/out$ 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 := include/generated/uapi/linux/version.h
old_version_h := include/linux/version.h
clean-targets := %clean mrproper cleandocs
## 这里记录的一些目标,是不需要依赖于内核.config即可构建的
no-dot-config-targets := $(clean-targets) \
cscope gtags TAGS tags help% %docs check% coccicheck \
$(version_h) headers_% archheaders archscripts \
%asm-generic kernelversion %src-pkg
## 这里的目标是不需要依赖syncconfig生成的*.confg和*.h即可构建的
no-sync-config-targets := $(no-dot-config-targets) install %install \
kernelrelease
## 此变量记录当前是否要重新构建.config文件,若当前make指定了[%]config作为目标,就会设置config-targets
config-targets := 0
## 代表当前make中是否指定了多种类型的target,若存在就会设置mixed-targets,开启混合编译模式
mixed-targets := 0
## dot-config 代表当前的编译目标是否需要引入 .config中的配置项变量,有的目标不需要引入.config就可以编译,如help
dot-config := 1
## 代表当前的编译目标是否需要重建*.conf和*.h,如auto.conf/autoconf.h,这些文件都是依赖于.config的,故不需要引入.config的必然也不需要重新构建对应的*.confg/*.h
## 但除此之外还有一些目标是不需要重新构建*.confg/*.h的,如install,其他的默认都要重新构建*.confg/*.h
may-sync-config := 1
## 若当前make仅指定了no-dot-config-targets中的目标(一个或多个),而没有指定no-dot-config-targets之外的目标,则设置dot-config :=0,
## 代表后续不需要在makefile中包含.config文件中的CONFIG宏定义变量
ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
## 若匹配到了no-dot-config-targets中的目标则走这里
ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
## 若除了no-dot-config-targets中的目标外没有指定其他目标, 则走这里
## 这里设置dot-config := 0, 代表当前编译目标均不需要包含.config
dot-config := 0
endif
endif
## 若当前make仅指定了no-sync-config-targets中的目标(一个或多个),而没有指定no-sync-config-targets之外的目标,
## 则不需要通过syncconfig(基于.config) 生成各种*.conf 和*.h给make和内核使用
ifneq ($(filter $(no-sync-config-targets), $(MAKECMDGOALS)),)
## 若指定的是no-sync-config-targets中的目标
ifeq ($(filter-out $(no-sync-config-targets), $(MAKECMDGOALS)),)
## 且除了no-sync-config-targets中的目标没有指定其他目标,则不必重新使用syncconfig构建*.conf/*.h
may-sync-config := 0
endif
endif
## 若指定了 M= 参数(编译外部模块),则不管其他配置如何,都不要重新syncconfig(否则和当前内核有可能不一致了)
ifneq ($(KBUILD_EXTMOD),)
may-sync-config := 0
endif
## 若没指定M=, 且指定了 %config, 则代表要重建.config
ifeq ($(KBUILD_EXTMOD),)
ifneq ($(filter config %config,$(MAKECMDGOALS)),)
## 那么则需要重新构建config
config-targets := 1
## 在指定了config / %config的情况下若目标>1,则设置mixed-targets
ifneq ($(words $(MAKECMDGOALS)),1)
mixed-targets := 1
endif
endif
endif
ifneq ($(filter $(clean-targets),$(MAKECMDGOALS)),)
## 若存在clean-targets中的目标,则走这里
ifneq ($(filter-out $(clean-targets),$(MAKECMDGOALS)),)
## 若同时存在非clean-targets之外的目标,则设置为mixed-target
mixed-targets := 1
endif
endif
## 若同时存在 install 和modules_install,则也要设置mixed-target
ifneq ($(filter install,$(MAKECMDGOALS)),)
## 若存在install目标(注意这里是完全匹配)
ifneq ($(filter modules_install,$(MAKECMDGOALS)),)
## 且存在 modules_install的目标
mixed-targets := 1
endif
endif
3:混合目标编译模式的处理
若make的目标为混合的多个目标,最终启动混合编译模式,此时实际上会对make中的所有目标均单独启动一个submake单独编译,当前make中其他目标全部置空,submake循环编译结束后编译即完成,最后当前make退出.
## mixed-targets是混合目标的情况,具体判断标准见其定义
ifeq ($(mixed-targets),1)
## 将所有的目标和__build_one_by_one都设置为伪目标
PHONY += $(MAKECMDGOALS) __build_one_by_one
## 把所有的目标的都置空,都会依赖于__build_one_by_one, 然后只执行 __build_one_by_one 这一个伪目标
$(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one
@:
## set -e: 当命令以非0状态退出时,则退出shell
## 这里遍历 MAKECMDGOALS 中的目标,对每一个目标都单独做一个make单独目标,makefile就是源码目录默认的Makefile文件
## 也就是说make中若是混合目标了,那最后就是一个一个目标的启动submake编译
__build_one_by_one:
$(Q)set -e; \
for i in $(MAKECMDGOALS); do \
$(MAKE) -f $(srctree)/Makefile $$i; \
done
else ## 这个else基本上到文件末尾了,若不是mixed-targets,其他的代码基本都在else里面执行
4:非混合目标编译模式的处理
真正单目标的编译代码均在这里
4.1:定义Kbuild系统变量
这里包含大量的变量的导出与定义,如:
KERNELRELEASE:编译版本信息,如4.14.174-00251-g1de6fe1a466f-dirty
KERNELVERSION: 解析1.1中内核版本后给出的一个最终当前内核版本号,如4.14.174
SRCARCH:当前要编译的体系结构,默认来自命令行或环境变量中的$(ARCH),若没指定,则通过uname -m解析当前平台的体系结构
KCONFIG_CONFIG: 默认值是.config,是内核config-targets,syncconfig生成*.conf/*.h的默认文件,一般不有修改(是.config名字的源头)
CONFIG_SHELL:kbuild系统中部分命令使用其执行shell命令,默认为/bin/bash
HOSTCC/HOSTCXX:HOST端程序编译c程序和c[++]程序的编译器,默认为gcc/g++
KBUILD_HOSTCFLAGS/KBUILD_HOSTCXXFLAGS/KBUILD_HOSTLDFLAGS/KBUILD_HOSTLDLIBS: 这四个选项是给Kbuild系统源码使用的,用户不应该再输入中修改这些变量
KBUILD_HOSTCFLAGS:HOST端程序编译(HOSTCC)时需要包含的编译选项
KBUILD_HOSTCXXFLAGS:HOST端程序编译(HOSTCXX)时需要包含的编译选项
KBUILD_HOSTLDFLAGS:HOST端程序链接(HOSTCC/LD)时需要包含的链接选项
KBUILD_HOSTLDLIBS:HOST端程序链接时需要包含的库文件路径
HOSTCFLAGS/HOSTCXXFLAGS/HOSTLDFLAGS/HOSTLDLIBS:这4个参数最终分别会合入到上面的4个参数中,这4个在源码中定义为空,是专门为用户提供的命令行附加项
AS/LD/CC/CPP: 分别为目标平台使用的汇编/链接/编译器/预处理器,默认值为$(CROSS_COMPILE)as/ld/gcc/$(CC) -E
AR/NM/STRIP/OBJCOPY/OBJDUMP: 分别为目标平台使用的相关工具,其作用和binutils中对用名称的工具一样,默认值为 $(CROSS_COMPILE)ar/nm/strip/objcopy/objdump
CROSS_COMPILE: Kbuild中是没有定义CROSS_COMPILE的,其值来自于用户输入,CROSS_COMPILE正常仅仅是用来指定目标平台工具链的前缀,并无其他用途。
CHECK/CHECKFLAGS:支持内核代码静态扫描,主要是一些如__user宏的检查,CHECK是对应工具,默认是sparse,CHECKFLAGS是要检查的宏.此静态代码检查是否开启,取决于内置变量KBUILD_CHECKSRC,最终来源于命令行选项 make C=
NOSTDINC_FLAGS: 记录一些特殊参数,如-nostdinc代表不到标准系统目录搜索头文件, -system代表从左到右搜索.c_flag/a_flags/cpp_flags和符号表处理中均添加了此变量.
USERINCLUDE/LINUXINCLUDE:
USERINCLUDE 记录所有UAPI相关的include目录
LINUXINCLUDE 记录内核源码编译时所有依赖的include目录,其包含USERINCLUDEc_flags, a_flags, cpp_flags 和符号表处理中都直接包含了LINUXINCLUDE中的目录
KBUILD_AFLAGS/KBUILD_CFLAGS/KBUILD_CPPFLAGS/KBUILD_LDFLAGS:这4个选项是目标平台内核编译通用的flag,模块和内核编译都是用其中的flag,其中:
KBUILD_AFLAGS:是编译目标平台*.s文件的通用flag
KBUILD_CFLAGS/KBUILD_CPPFLAGS: 是编译目标平台*.c文件的通用flag(二者区别暂不清楚)
KBUILD_LDFLAGS:是通用的链接选项
KBUILD_AFLAGS_KERNEL/KBUILD_CFLAGS_KERNEL:只有在编译内核(*.s/*.c)时才加入的选项,外部模块编译不使用此选项
KBUILD_AFLAGS_MODULE/KBUILD_CFLAGS_MODULE/KBUILD_LDFLAGS_MODULE:只有在编译,链接外部模块(*.s/*.c/*.o)时才加入的选项,内核编译不使用此选项
CFLAGS_MODULE/AFLAGS_MODULE/LDFLAGS_MODULE:这3个选项对应上面的3个模块KBUILD_XXXX_MODULE选项,区别在于其是留给用户输入附加的,Kbuild中定义为空
CFLAGS_KERNEL/AFLAGS_KERNEL:这2个选项对应上面的2个内核KBUILD_XXXX_KERNEL选项,区别在于其是留给用户输入附加的,Kbuild中定义为空
LDFLAGS_vmlinux: 这个是最终链接vmlinux额外参数,只在link-vmlinux.sh中使用了.
GCC_PLUGINS_CFLAGS:记录GCC插件中指定的参数,若使用gcc插件,最终参数会被加入到 KBUILD_CFLAGS中去
MODVERDIR: 对于内核来说是输出目录下的./tmp_versions文件,对于模块编译则是模块源码目录下的./tmp_versions目录,编译过程中所有export了符号的模块都会记录在此目录中,最后通过modpost统一处理
RCS_FIND_IGNORE/RCS_TAR_IGNORE: 分别是后续一些find/tar命令忽略的文件扩展名,如.git
## 若是非混合目标(可能是只有一类目标,或因为混合目标导致__build_one_by_one后每次只有一个目标)则走这里
## 这里会先include script/Kbuild.include,此文件中没有具体的target,但定义了kbuild中使用的大量变量和函数 其中部分函数和目标记录在2.Kbuild.include分析中
include scripts/Kbuild.include
## 文件内容类似KERNELVERSION,如 4.14.174-00251-g1de6fe1a466f-dirty
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
## 这个值记录当前内核版本信息,如4.14.174,其值来自于此文件开头的 VERSION等变量的整合.
## help命令中可知, make kernelversion可直接查看当前内核版本.
KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)
export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
## 这个脚本值是通过shell执行uname -m,过滤后得到当前的平台信息记录到 $(SUBARCH)中,如在x86平台其值为 x86
include scripts/subarch.include
## 若没指定ARCH,则默认从SUBARCH中获取
ARCH ?= $(SUBARCH)
UTS_MACHINE := $(ARCH)
## SUBARCH的值默认来自命令行变量或环境变量$(ARCH),若没有则使用uname -m 获取当前平台的体系结构
SRCARCH := $(ARCH)
ifeq ($(ARCH),i386 ...|x86_64...|sparc32...|sparc64...|sh64..) ## 这里略过部分代码
.......
SRCARCH := x86|sparc|sh
endif
## KCONFIG_CONFIG默认是.config,实际上代表的是config-targets生成的文件名以及syncconfig处理的文件名,
## 这个一般不需要修改. 内核中的.config实际上就是源于这个KCONFIG_CONFIG
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
## Kbuild中部分命令会直接使用CONFIG_SHELL来执行
## if -x 代表如果文件存在且可执行,默认$BASH是存在的,为/bin/bash
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
## 与Linux from Scratch项目有关,先pass
HOST_LFS_CFLAGS := $(shell getconf LFS_CFLAGS 2>/dev/null)
HOST_LFS_LDFLAGS := $(shell getconf LFS_LDFLAGS 2>/dev/null)
HOST_LFS_LIBS := $(shell getconf LFS_LIBS 2>/dev/null)
## 默认编译HOST端程序用到的编译器
HOSTCC = gcc
HOSTCXX = g++
## KBUILD_HOSTXXX是编译HOST端程序一定会用到的CONFIG的集合,不同的HOST端程序的编译过程可能会通过
## 其他变量增加其他的选项(可参考 host_c_flags),但KBUILD_HOSTXXX总是这些编译的基本选项,是必须包含项.
## KBUILD_HOSTCFLAGS是$(HOSTCC)编译时必须包含的编译选项,$(HOSTCFLAGS) 没有定义,是用户可通过make命令行或环境变量添加的额外选项
KBUILD_HOSTCFLAGS := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
-fomit-frame-pointer -std=gnu89 $(HOST_LFS_CFLAGS) \
$(HOSTCFLAGS)
## KBUILD_HOSTCXXFLAGS是$(HOSTCXX)编译时必须包含的编译选项,$(HOSTCXXFLAGS)没有定义,是用户可通过make命令行或环境变量添加的额外选项
KBUILD_HOSTCXXFLAGS := -O2 $(HOST_LFS_CFLAGS) $(HOSTCXXFLAGS)
## KBUILD_HOSTLDFLAGS是HOST链接过程的通用flags,不论是用$(HOSTCC)还是$(LD)链接,都要使用此变量
## $(HOSTLDFLAGS)没有定义,是用户可通过make命令行或环境变量添加的额外选项
KBUILD_HOSTLDFLAGS := $(HOST_LFS_LDFLAGS) $(HOSTLDFLAGS)
## KBUILD_HOSTLDLIBS记录是HOST链接过程中的库文件,不论是用$(HOSTCC)还是$(LD)链接,都要加入此库文件路径
## $(HOSTLDLIBS)没有定义,是用户可通过make命令行或环境变量添加额外的库路径
KBUILD_HOSTLDLIBS := $(HOST_LFS_LIBS) $(HOSTLDLIBS)
## CROSS_COMPILE没有定义,其来自于用户输入的命令行或环境变量,其本身通常只用做binutils中工具的前缀,
## 用来确定binutils中工具真正的文件名,但通常只有交叉编译时才会使用到此前缀
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
## -E 是只执行预处理,命名为CPP,实际上是用了 CC -E
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
PAHOLE = pahole
LEX = flex
YACC = bison
AWK = awk
INSTALLKERNEL := installkernel
DEPMOD = /sbin/depmod
PERL = perl
PYTHON = python
PYTHON2 = python2
PYTHON3 = python3
## linux内核的静态代码检查工具,会确认部分代码格式是否正确,但对内核开发者没啥大用
CHECK = sparse
## 指定sparse中要检查的宏定义
CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \
-Wbitwise -Wno-return-void -Wno-unknown-attribute $(CF)
## 一些特殊参数放在这里,如-nostdinc代表不到标准系统目录搜索头文件, -system代表从左到右搜索
## NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include)
NOSTDINC_FLAGS :=
## 这里的5个变量类似HOSTCFLAGS 是留给用户命令行或环境变量输入的全局参数,其功能类似对应的KBUILD_XXX
## CFLAGS_MODULE是用户命令行/环境变量附加的,编译内核模块时(*.c)的参数
CFLAGS_MODULE =
## AFLAGS_MODULE是用户命令行/环境变量附加的,编译内核模块时(*.s)的参数
AFLAGS_MODULE =
## LDFLAGS_MODULE是用户命令行/环境变量附加的,链接内核模块时的参数
LDFLAGS_MODULE =
## CFLAGS_KERNEL是用户命令行/环境变量附加的,编译内核时(*.c)的参数
CFLAGS_KERNEL =
## AFLAGS_KERNEL是用户命令行/环境变量附加的,编译内核时(*.s)的参数
AFLAGS_KERNEL =
## 这个是最终链接vmlinux额外参数,值在link-vmlinux.sh中使用了,Kbuild并没有为用户预留额外的内核LD参数(如 LDFFLAGS_KERNEL),
## 因为内核只链接了vmlinux,其链接参数都记录在这里了.
LDFLAGS_vmlinux =
## 这个是所有UAPI相关的include目录
USERINCLUDE := \
-I$(srctree)/arch/$(SRCARCH)/include/uapi \
-I$(objtree)/arch/$(SRCARCH)/include/generated/uapi \
-I$(srctree)/include/uapi \
-I$(objtree)/include/generated/uapi \
-include $(srctree)/include/linux/kconfig.h
## LINUXINCLUDE 记录内核源码编译时所有依赖的include目录,在c_flags, a_flags, cpp_flags 和符号表处理中都直接包含了LINUXINCLUDE中的目录
LINUXINCLUDE := \
-I$(srctree)/arch/$(SRCARCH)/include \
-I$(objtree)/arch/$(SRCARCH)/include/generated \
$(if $(filter .,$(srctree)),,-I$(srctree)/include) \
-I$(objtree)/include \
$(USERINCLUDE)
## 这3个是内核编译通用的flag 模块和内核编译都是用其中的flag
## KBUILD_CFLAGS是通用的*.c文件编译选项
## KBUILD_AFLAGS是通用的*.s文件编译选项
KBUILD_AFLAGS := -D__ASSEMBLY__ -fno-PIE
KBUILD_CFLAGS := -Wall -Wundef -Werror=strict-prototypes -Wno-trigraphs \
-fno-strict-aliasing -fno-common -fshort-wchar -fno-PIE \
-Werror=implicit-function-declaration -Werror=implicit-int \
-Wno-format-security \
-std=gnu89
## 暂不知道这个和KBUILD_CFLAGS啥区别,二者都用在正常的*c文件编译选项中了
KBUILD_CPPFLAGS := -D__KERNEL__
## 这个是只有在编译内核的时候加入的配置项
KBUILD_AFLAGS_KERNEL :=
KBUILD_CFLAGS_KERNEL :=
## 这个是只有在编译模块的时候才加入的编译项
KBUILD_AFLAGS_MODULE := -DMODULE
KBUILD_CFLAGS_MODULE := -DMODULE
KBUILD_LDFLAGS_MODULE := -T $(srctree)/scripts/module-common.lds
## KBUILD_LDFLAGS是通用的链接问价能编译选项
KBUILD_LDFLAGS :=
## 这里记录GCC插件中指定的参数,若使用gcc插件,最终参数会被加入到 KBUILD_CFLAGS中去
GCC_PLUGINS_CFLAGS :=
export ARCH SRCARCH CONFIG_SHELL HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM STRIP OBJCOPY OBJDUMP PAHOLE KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS
export MAKE LEX YACC AWK INSTALLKERNEL PERL PYTHON PYTHON2 PYTHON3 UTS_MACHINE
export HOSTCXX KBUILD_HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS KBUILD_LDFLAGS
export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE
export CFLAGS_KASAN CFLAGS_KASAN_NOSANITIZE CFLAGS_UBSAN
export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
export KBUILD_ARFLAGS
## 若当前是编译外部模块(KBUILD_EXTMOD非空),则./tmp_versions目录输出到外部模块源码目录,否则输出到当前目录(内核输出目录,adb_objtree)
## MODVERDIR为.tmp_versions目录的全路径,此目录中存放一堆文本文件*.mod,记录编译阶段所有有export符号的模块名,后续会通过modpost批量处理
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions
## 这是后续find命令处理一些文件时需要忽略的文件
export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o \
-name CVS -o -name .pc -o -name .hg -o -name .git \) \
-prune -o
## tar命令忽略的文件
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
--exclude CVS --exclude .pc --exclude .hg --exclude .git
4.2: 目标scripts_basic
目标script_basic的实际操作实际上是通过./scripts/Makefile.build构建子目录 scripts/basic.(子目录具体操作内容[TODO])
## 将scripts_basic加入伪目标
PHONY += scripts_basic
## 这里的build是前面include Kbuild.include时引入的,其定义在 kbuild.include中:
## build := -f $(srctree)/scripts/Makefile.build obj
## 若编译scripts_basic,则使用此规则,这里展开为 make -f $(srctree)/scripts/Makefile.build obj=scripts/basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
4.3: 目标outputmakefile
此目标在当前源码目录和输出目录不同时:
* 在输出目录构建源码目录的软链接(./source,若已有符号链接不跟随且覆盖原有定义)
* 在输出目录自动生成一个根Makefile
* 在输出目录生成.gitignore文件
outputmakefile:
ifneq ($(srctree),.)
## 链接源码目录到输出目录下的source目录 -fsn是针对目录的,若已有符号链接,不跟随且覆盖原有定义
$(Q)ln -fsn $(srctree) source
## mkmakefile在输出目录生成了一个Makefile,以使得输出目录可以编译
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile $(srctree)
## 若输出目录存在.gitignore则结束,否则创建.gitignore,并写入 *,即忽略输出目录的所有文件
$(Q)test -e .gitignore || \
{ echo "# this is build directory, ignore it"; echo "*"; } > .gitignore
endif
4.4:CLANG编译项和编译器版本信息
编译内核使用什么编译器,是用户输入决定的,如用户在内核make命令中指定CC=clang,那么在make过程中,所有target平台的目标文件均会使用clang作为编译器(默认使用CC编译).
而这里的代码就是在发现若当前平台是使用clang编译时,设置clang对应的编译参数.
此外,不论是什么编译器,最终均执行--version获取编译器版本信息到 CC_VERSION_TEXT
## 若当前编译器是clang
ifneq ($(shell $(CC) --version 2>&1 | head -n 1 | grep clang),)
## 若命令行指定了CROSS_COMPILE,则设置clang的--target 参数,clang在交叉编译时需要指定目标平台,如 --target=aarch64-linux-android
ifneq ($(CROSS_COMPILE),)
CLANG_FLAGS := --target=$(notdir $(CROSS_COMPILE:%-=%))
## 通过clang的elfedit工具找到gcc交叉编译链工具的目录
GCC_TOOLCHAIN_DIR := $(dir $(shell which $(CROSS_COMPILE)elfedit))
## 指定前缀给GCC工具链的目录,这个应该是工具链的目录
CLANG_FLAGS += --prefix=$(GCC_TOOLCHAIN_DIR)
GCC_TOOLCHAIN := $(realpath $(GCC_TOOLCHAIN_DIR)/..)
endif
ifneq ($(GCC_TOOLCHAIN),)
## 指定gcc工具链目录
CLANG_FLAGS += --gcc-toolchain=$(GCC_TOOLCHAIN)
endif
CLANG_FLAGS += -no-integrated-as
CLANG_FLAGS += -Werror=unknown-warning-option
## CLANG相关flag加到 KBUILD_C/AFLAG中,内核目标平台所有的目标文件编译都会用到这些flags
KBUILD_CFLAGS += $(CLANG_FLAGS)
KBUILD_AFLAGS += $(CLANG_FLAGS)
export CLANG_FLAGS
endif
CC_VERSION_TEXT = $(shell $(CC) --version 2>/dev/null | head -n 1)
4.5:对于xxx_config规则的编译
注意当前的状态是处于非混合目标模式编译,若make中输入的是混合目标(如既要编译config,又要编译内核或模块),则在3中会将其拆分为多个非混合目标依次执行.
故若当前存在要编译xxx_config,则设置对应目标后,此makefile的代码即结束.
## 若当前要编译xxxconfig
ifeq ($(config-targets),1)
## 则包含平台相关的makefile
include arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG CC_VERSION_TEXT
## 这里是config和 %config的构建命令,其依赖于 scripts_basic outputmakefile
config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
else
... ## 非xxx_config的情况
endif # ifeq ($(config-targets),1)
endif # ifeq ($(mixed-targets),1)
endif # need-sub-make
PHONY += FORCE
FORCE:
4.6:对于非xxx_config规则的编译
./Makefile中剩余代码均是对非xxx_config情况编译的支持, 细节参考<Kbuild系统源码分析(二)—./Makefile>
参考资料:
[1]. https://zhuanlan.zhihu.com/p/78254770
[2]. https://lwn.net/Articles/69148/
[3]. https://blog.csdn.net/cloudblaze/article/details/5167552