vlambda博客
学习文章列表

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

  linux内核的Kbuid系统实际上就是make & makefile的一种应用,其起始于内核根目录的./Makefile; 文档记录在内核./Documentation/Kbuild目录下,代码解析如下:


1. ./Makefile文件入口
1.1:内核版本定义

VERSION = 5PATCHLEVEL = 2SUBLEVEL = 5EXTRAVERSION =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## 否则默认将其设置为0ifndef KBUILD_VERBOSE KBUILD_VERBOSE = 0endif## 若KBUILD_VERBOSE = 0, 则默认quiteifeq ($(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-directoryelse## 若通过make o=xxx 指定的输出目录和源码目录不同,则设置 need-sub-make :=1, 这会导致后面使用## 一个submake来重启整个make,因为make是不能运行时切换目录的只能在启动时设置目录need-sub-make := 1endif

   源码目录$(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 := 1endif
## 若当前make的版本是3.x,则默认就要走submakeifneq ($(filter 3.%,$(MAKE_VERSION)),)need-sub-make := 1$(lastword $(MAKEFILE_LIST)): ;endif
## 将当前源码目录、输出目录、是否需要submake都exportexport abs_srctree abs_objtreeexport 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-makeendif # 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 = 0endif
## 设置要编译的外部模块目录ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M)endif
## 前面sub-make切换目录后,这个srctree和objtree目录还是不变的,二者是否相等还是取决于是否指定了 O=xxxifeq ($(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.hold_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-targetsconfig-targets := 0
## 代表当前make中是否指定了多种类型的target,若存在就会设置mixed-targets,开启混合编译模式mixed-targets := 0
## dot-config 代表当前的编译目标是否需要引入 .config中的配置项变量,有的目标不需要引入.config就可以编译,如helpdot-config := 1
## 代表当前的编译目标是否需要重建*.conf和*.h,如auto.conf/autoconf.h,这些文件都是依赖于.config的,故不需要引入.config的必然也不需要重新构建对应的*.confg/*.h## 但除此之外还有一些目标是不需要重新构建*.confg/*.h的,如install,其他的默认都要重新构建*.confg/*.hmay-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 endifendif
## 若当前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 endifendif
## 若指定了 M= 参数(编译外部模块),则不管其他配置如何,都不要重新syncconfig(否则和当前内核有可能不一致了)ifneq ($(KBUILD_EXTMOD),) may-sync-config := 0endif
## 若没指定M=, 且指定了 %config, 则代表要重建.configifeq ($(KBUILD_EXTMOD),) ifneq ($(filter config %config,$(MAKECMDGOALS)),) ## 那么则需要重新构建config config-targets := 1 ## 在指定了config / %config的情况下若目标>1,则设置mixed-targets ifneq ($(words $(MAKECMDGOALS)),1) mixed-targets := 1 endif endifendif
ifneq ($(filter $(clean-targets),$(MAKECMDGOALS)),) ## 若存在clean-targets中的目标,则走这里 ifneq ($(filter-out $(clean-targets),$(MAKECMDGOALS)),) ## 若同时存在非clean-targets之外的目标,则设置为mixed-target mixed-targets := 1 endifendif
## 若同时存在 install 和modules_install,则也要设置mixed-targetifneq ($(filter install,$(MAKECMDGOALS)),) ## 若存在install目标(注意这里是完全匹配) ifneq ($(filter modules_install,$(MAKECMDGOALS)),) ## 且存在 modules_install的目标 mixed-targets := 1 endifendif


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; \ doneelse ## 这个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-dirtyKERNELRELEASE = $(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平台其值为 x86include 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|shendif
## KCONFIG_CONFIG默认是.config,实际上代表的是config-targets生成的文件名以及syncconfig处理的文件名,## 这个一般不需要修改. 内核中的.config实际上就是源于这个KCONFIG_CONFIGKCONFIG_CONFIG ?= .configexport KCONFIG_CONFIG
## Kbuild中部分命令会直接使用CONFIG_SHELL来执行## if -x 代表如果文件存在且可执行,默认$BASH是存在的,为/bin/bashCONFIG_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项目有关,先passHOST_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 = gccHOSTCXX = 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)asLD = $(CROSS_COMPILE)ldCC = $(CROSS_COMPILE)gcc## -E 是只执行预处理,命名为CPP,实际上是用了 CC -ECPP = $(CC) -EAR = $(CROSS_COMPILE)arNM = $(CROSS_COMPILE)nmSTRIP = $(CROSS_COMPILE)stripOBJCOPY = $(CROSS_COMPILE)objcopyOBJDUMP = $(CROSS_COMPILE)objdumpPAHOLE = paholeLEX = flexYACC = bisonAWK = awkINSTALLKERNEL := installkernelDEPMOD = /sbin/depmodPERL = perlPYTHON = pythonPYTHON2 = python2PYTHON3 = 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 := -DMODULEKBUILD_CFLAGS_MODULE := -DMODULEKBUILD_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 CCexport CPP AR NM STRIP OBJCOPY OBJDUMP PAHOLE KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBSexport MAKE LEX YACC AWK INSTALLKERNEL PERL PYTHON PYTHON2 PYTHON3 UTS_MACHINEexport HOSTCXX KBUILD_HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGSexport KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS KBUILD_LDFLAGSexport KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULEexport CFLAGS_KASAN CFLAGS_KASAN_NOSANITIZE CFLAGS_UBSANexport KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULEexport KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULEexport KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNELexport 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/basicscripts_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 "*"; } > .gitignoreendif

4.4:CLANG编译项和编译器版本信息
  编译内核使用什么编译器,是用户输入决定的,如用户在内核make命令中指定CC=clang,那么在make过程中,所有target平台的目标文件均会使用clang作为编译器(默认使用CC编译).
  而这里的代码就是在发现若当前平台是使用clang编译时,设置clang对应的编译参数.
  此外,不论是什么编译器,最终均执行--version获取编译器版本信息到 CC_VERSION_TEXT

## 若当前编译器是clangifneq ($(shell $(CC) --version 2>&1 | head -n 1 | grep clang),)
## 若命令行指定了CROSS_COMPILE,则设置clang的--target 参数,clang在交叉编译时需要指定目标平台,如 --target=aarch64-linux-androidifneq ($(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)endifCLANG_FLAGS += -no-integrated-asCLANG_FLAGS += -Werror=unknown-warning-option
## CLANG相关flag加到 KBUILD_C/AFLAG中,内核目标平台所有的目标文件编译都会用到这些flagsKBUILD_CFLAGS += $(CLANG_FLAGS)KBUILD_AFLAGS += $(CLANG_FLAGS)export CLANG_FLAGSendif
CC_VERSION_TEXT = $(shell $(CC) --version 2>/dev/null | head -n 1)

4.5:对于xxx_config规则的编译
  注意当前的状态是处于非混合目标模式编译,若make中输入的是混合目标(如既要编译config,又要编译内核或模块),则在3中会将其拆分为多个非混合目标依次执行.
  故若当前存在要编译xxx_config,则设置对应目标后,此makefile的代码即结束.

## 若当前要编译xxxconfigifeq ($(config-targets),1)## 则包含平台相关的makefileinclude arch/$(SRCARCH)/Makefileexport KBUILD_DEFCONFIG KBUILD_KCONFIG CC_VERSION_TEXT
## 这里是config和 %config的构建命令,其依赖于 scripts_basic outputmakefileconfig: 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 += FORCEFORCE:.PHONY: $(PHONY) ## 整个makefile结束

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