linux内核代码4.1.15顶层Makefile逐行分析

linux内核代码4.1.15顶层Makefile逐行分析

VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 15
EXTRAVERSION =
NAME = Series 4800

一开始就是对linux内核版本的说明

MAKEFLAGS += -rR --include-dir=$(CURDIR)

MAKEFLAGS变量是用来传递MAKE选项的,如果在命令行中make后面有选项,则在此处,会在原来make选项后加上 -rR --include-dir=$(CURDIR),对于递归调用同样适用,如果下级子目录makefile中也有MAKEFLAGS变量的设定,则会继承上级makefile中MAKEFLAGS的值,除非显示设定为不继承。
例如,命令行中执行的make --no-print-directory
则此处的MAKEFLAGS值为 --no-print-directory -rR --include-dir=当前目录。

# Avoid funny character set dependencies
unexport LC_ALL
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC

C语言的区域设置,估计都要设,去除本地化设置,具体作用也不深究了,看自带的注释,应该就是为了避免一些比较奇怪的字符集依赖。uxexport表示不向子makefile传递,export则需要向子makefile传递。

# Avoid interference with shell env settings
unexport GREP_OPTIONS

shell下有个命令就是grep,grep搜索的时候shell下应该有搜索属性或者选项,比如看到有人需要把搜索结果高亮,就会显式的加上export GREP_OPTIONS=’–color=XXX’ ; color有三个值供选择: never always auto ;这里unexport掉,就是为了避免和shell env下的设置冲突。

ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

这里用到了makefile里头的origin语法。origin函数不像其它函数,他并不直接操作变量的值,只是告诉你这个变量是从哪里来的,可以理解为追踪出生地。
其语法是:

$(origin   variable)

注意这里是变量的名字,不是引用,所以不要使用“$”字符。origin函数会以返回值告诉你这个变量的“出生情况”(这个变量从哪里来的?)。
此处就是追踪变量V,是不是从命令行来的,如果是,返回"command line",否则返回"undefined"。紧接着后面就判断,如果来源于命令行,则将KBUILD的值等于变量V的值。
举个例子,命令行下输入下列命令,返回值分别为:

Shell命令 返回值 KBUILD值
make V=1 command line 1
make undefined
make V undefined

如果此变量是来源于命令行,则将变量KBUILD_VERBOSE赋值为变量V的值。

ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

Q :控制编译的时候是否在终端输出完整的命令。
quiet:控制输出命令长短

  • quiet 为空 长命令,整个命令都会输出
  • quiet 为“ quiet_ ” 短命令,仅输出短文本
  • quiet 为" silent_ " 无输出,就是静默输出
# If the user is running make -s (silent mode), suppress echoing of
# commands
ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  quiet=silent_
endif
else					# make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
  quiet=silent_
endif
endif
  • 此处先用ifneq+filter语法,filter过滤变量MAKE_VERSION,结果中仅保留满足4.x格式的,然后把结果用ifneq去判断,是否不为空。总结下来,ifneq ($(filter 4.%,$(MAKE_VERSION)),),实际就是如果变量MAKE_VERSION满足通配符4.$,就走此分支。
  • firstword:make工具为4.x版本的分支,用firstword去取x$(MAKEFLAGS)这个字符串的第一个单词,单词的分隔符是空格,然后匹配正则表达式 [%s],就是这个单词里头必须有字母s,如果过滤后不为空,也就是能匹配到,就置quiet = silent_,即静默输出。
  • 对于make工具为非4.x的版本,如果过滤后是s% -s%这种模式的,就置quiet = silent_,也就是无输出
export quiet Q KBUILD_VERBOSE
  • quiet Q KBUILD_VERBOSE三个变量向子makefile传递
# KBUILD_SRC is set on invocation of make in OBJ directory
# KBUILD_SRC is not intended to be used by the regular user (for now)
ifeq ($(KBUILD_SRC),)

# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

KBUILD_SRC:在OBJ目录调用make时需要设定此变量的值。
KBUILD_OUTPUT:指定的输出文件存放位置
如果想把输出文件存放在指定目录,有两种办法

  1. make O=dir/to/store/output/files/
  2. export KBUILD_OUTPUT=dir/to/store/output/files/
    上述就是判断命令行中是否通过O=指定了输出路径,如果指定了,则将KBUILD_OUTPUT 置为 O的值。O=赋值优先于KBUILD_输出
    上述语句意思是,如果没有指定KBUILD_SRC,并且又在命令行中使用了make O=xxx,就需要把KBUILD_OUTPUT置为O的值。
PHONY := _all

这里开始都是默认目标了,就是没用make xxx指定目标的时候,默认就是执行这个目标

_all:
# Cancel implicit rules on top Makefile
$(CURDIR)/Makefile Makefile: ;

这条命令也不是很明白,从注释上看,是为了阻止makefile使用隐含规则来构建目标。这条语句肯定是条空语句。查到大家给的说法是空命令行可以防止make执行时试图为重建这个目标去查找隐含命令(包括了使用隐含规则中的命令和“.DEFAULT”指定的命令),不是太懂,保留意见。

ifneq ($(KBUILD_OUTPUT),)
# Invoke a second make in the output directory, passing relevant variables
# check that the output directory actually exists
saved-output := $(KBUILD_OUTPUT)KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
								&& /bin/pwd)$(if $(KBUILD_OUTPUT),, \
     $(error failed to create output directory "$(saved-output)"))PHONY += $(MAKECMDGOALS) sub-make  ④

$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
	@:   ⑤

sub-make: FORCE
	$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
	-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))  ⑥

# Leave processing to above invocation of make
skip-makefile := 1  ⑦
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

这段是一个整体,就一起看了。当KBUILD_OUTPUT不为空的时候,执行。
① 变量赋值
② shell下执行,先级联创建目录$KBUILD_OUTPUT,然后进入到这个目录,并且执行/bin/pwd,就是获取当前目录的完整路径,并赋值给变量KBUILD_OUTPUT
③ 判断KBUILD_OUTPUT是否为空,如果为空,就说明没有创建成功。
④ MAKECMDGOALS:记录了命令行参数指定的终极目标列表,没有通过参数指定终极目标时此变量为空。比如make all,那么MAKECMDGOALS的值就是all,make,MAKECMDGOALS的值就是空。此语句就是给终极目标列表在增加sub-make这个目标。
⑤ filter-out:
函数名称 :反过滤函数—filter-out。
语法: $(filter-out PATTERN…,TEXT)
函数功能 :和“filter”函数实现的功能相反。过滤掉字串“TEXT”中所有符合模式“PATTERN”的单词,保留所有不符合此模式的单词。可以有多个模式。存在多个模式时,模式表达式之间使用空格分割。
返回值 :空格分割的“TEXT”字串中所有不符合模式“PATTERN”的字串
在此处:
PATTERN: _all sub-make ( C U R D I R ) / M a k e f i l e T E X T : (CURDIR)/Makefile TEXT: (CURDIR)/MakefileTEXT(MAKECMDGOALS)
意思就是所有不满足_all sub-make $(CURDIR)/Makefile这种模式的字符串,都被留下来,作为返回值。返回的结果,和_all 都依赖于sub_make。
⑥ 这一段是sub-make的规则。

  • FORCE:不管目标在不在,依赖是否更新,都重新生成目标以及依赖中隐含的目标。
  • -f:指定makefile文件。
    ⑦ 变量不晓得啥意思,看注释描述是把处理留给上述的make调用。
ifeq ($(skip-makefile),)

后续的流程都是基于这个skip-makefile为空的,就一条条分开讲

MAKEFLAGS += --no-print-directory

–no-print-directory:不打印"Entering directory …"

ifeq ("$(origin C)", "command line")
  KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

检查命令行中有没有输入make C=XXX这种,如果有,把值付给KBUILD_CHECKSRC。

Shell命令 KBUILD_CHECKSRC Meaning
make 0 不做源代码检查
make C=1 1 只检查重新编译过的源文件
make C=2 2 检查所有的源文件,无论他们会否重新编译过
ifdef SUBDIRS
  KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

此处针对的是编译外部模块:

  • 如果定义了宏SUBDIRS,同时如果BUILD_EXTMOD没有定义,就把SUBDIRS赋值给KBUILD_EXTMOD。这个分支是兼容老的编译模式,make SUBDIRS=dir。
  • 如果命令行中,输入了make M=dir这种,就把M赋值给KBUILD_EXTMOD。
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

PHONY += all,这句比较诡异,既然所有的都要加all,为啥后面的_all又要单独分开,先持保留态度。
如果KBUILD_EXTMOD有值,就说明编译的是外部模块,_all 就依赖于modules,否则_all就依赖于all。

ifeq ($(KBUILD_SRC),)
        # building in the source tree
        srctree := .
else
        ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
                # building in a subdirectory of the source tree
                srctree := ..
        else
                srctree := $(KBUILD_SRC)
        endif
endif
  • 如果KBUILD_SRC为空,说明没有指定源文件路径,srctree就置为.,意思就是当前路径。
  • 否则,比较KBUILD_SRC 和取当前目录的路径是不是相等,如果相等,srctree就置为1,否则srctree置为KBUILD_SRC。
objtree		:= .
src		:= $(srctree)
obj		:= $(objtree)

这三个变量后面用得到,src代表源代码路径,obj代表生成的目标文件路径。后面确定下是不是这个意思

VPATH		:= $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))

VPATH做啥用的,后面补充

export srctree objtree VPATH

导出这三个变量给下级子目录用。

SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
				  -e s/sun4u/sparc64/ \
				  -e s/arm.*/arm/ -e s/sa110/arm/ \
				  -e s/s390x/s390/ -e s/parisc64/parisc/ \
				  -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
				  -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )

这条命令有点长,用shell执行的,如下拆开一一来分析下:

  • shell:用shell来执行
  • uname -m:显示电脑类型。如装的是虚拟机,用的iso镜像ubuntu1804,x86,64位,这条指令的执行结果就是x86_64,代表是X86架构,64位系统。这个64位挺有意思的,经常看得到,台式机装的操作系统是win10,X86_64,开发板用的是arm64。这个64从硬件角度而言代表CPU的能力,一次可以处理64bit也就是8字节的数据。软件上说64,则可理解的就多了。寄存器可以是64bit的,内存的空间可以是2^64次方,int位32,但是long型已经是64bit了,等等。扯远了。。。。
  • sed -e s/i.86/x86/ -e s/x86_64/x86/ …后面的那些就不贴了。
    sed是一个非交互性的文本流编辑器,它编辑文件或标准输入导出的文本拷贝。此处用到的话,是把上一语句的输出通过|作为他的输入,参数说明:
    -e :script 指定sed编辑命令
    s:替换文本,替换命令用替换模式替换指定模式,格式为:
    [ a d d r e s s [,address]] s/pattern-to-find/replacement-pattern/[g p w n]
  • 总体理解下来,就是将uname -m查出来的结果,用sed进行处理进行处理,将i.86替换为x86,x86_64替换为x86,依次类推。其实就是将体系架构下的各小类统一规划成大类,结果就只剩下x86,arm,powperpc,mips这样大的体系架构了。
ARCH		?= arm
CROSS_COMPILE	?= arm-linux-gnueabihf-

关键的东西来了,定义编译的体系架构和交叉编译工具链前缀。此处的?=,表示如果前面已经有赋值了,就用前面的赋值,如果没有,就用语句中的值来替代。比如make ARCH=X86 CROSS_COMPILE=xxx,那么此处的赋值语句就不会执行,还是用命令行中的值,只有make中没有指定,或者前面没有定义这两个变量,此处才有意义。

UTS_MACHINE 	:= $(ARCH)
SRCARCH 	:= $(ARCH)

这两变量还不知道哪里会用到,留个悬念,后面补充。

# Additional ARCH settings for x86
ifeq ($(ARCH),i386)
        SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
        SRCARCH := x86
endif

# Additional ARCH settings for sparc
ifeq ($(ARCH),sparc32)
       SRCARCH := sparc
endif
ifeq ($(ARCH),sparc64)
       SRCARCH := sparc
endif

# Additional ARCH settings for sh
ifeq ($(ARCH),sh64)
       SRCARCH := sh
endif

# Additional ARCH settings for tile
ifeq ($(ARCH),tilepro)
       SRCARCH := tile
endif
ifeq ($(ARCH),tilegx)
       SRCARCH := tile
endif

根据ARCH来确定SRCARCH。后面用到,看到了再补充。

# Where to locate arch specific headers
hdr-arch  := $(SRCARCH)

hdr-arch赋值为SRCARCH。这里赋值符是:=,不同于上面的?=。:= 指的是不会使用后面的变量。也就是SRCARCH这里是啥,hdr-arch就是啥,不会因为你后面改变SRCARCH,而改变。

KCONFIG_CONFIG	?= .config
export KCONFIG_CONFIG

KCONFIG_CONFIG指的config的配置文件名称,此处指定为.config。并把KCONFIG_CONFIG导出给下级makefile使用。

CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
	  else if [ -x /bin/bash ]; then echo /bin/bash; \
	  else echo sh; fi ; fi)

这段语句,其实就是为了确定系统运行的bash是啥,一般都是/bin/bash。发现自己特别变态,喜欢追根究底这种奇怪的脚本。。。
-shell:指明用shell命令解析这句命令,这些都是bash的语法。

  • if [ -x "$$BASH" ]; then echo $$BASH; 这句话有点懵的,貌似$$BASH代表目前正在执行的bash的进程ID,if -x在bash中的意思是判断文件是否存在并且可执行。echo:bash的echo命令,就是输出的意思。就是判断现在是不是bash在执行咯,如果是,就输出它的ID给CONFIG_SHELL?试了下,没有走这分支。
  • else if [ -x /bin/bash ]; then echo /bin/bash; 判断/bin/bash是否存在且可执行,如果为真,就输出/bin/bash给CONFIG_SHELL。我虚拟机下编译走的此分支。CONFIG_SHELL是等于/bin/bash。
  • else echo sh;否则,就输出sh。
HOSTCC       = gcc
HOSTCXX      = g++
HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2

保留,看后面怎么用这堆变量。

ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1)
HOSTCFLAGS  += -Wno-unused-value -Wno-unused-parameter \
		-Wno-missing-field-initializers -fno-delete-null-pointer-checks
endif

通过shell去判断HOSTCC的版本,然后添加HOSTCFLAGS 选项。

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

确定是进行模块化编译还是内置编译。KBUILD_MODULES 模块化编译(modular),KBUILD_BUILTIN 内置编译(built-in),默认是内置编译。built-in就是将所有编译的东西都内置到内核中。模块化编译就是make modules。

ifeq ($(MAKECMDGOALS),modules)
  KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

如果我们使用命令make modules,就不会进行编译内置对象。如果使用modversions构建模块时,因为要考虑内置模块是不是最新的,所以这个变量就还是要置。(看注释写的,没用到过。)

ifneq ($(filter all _all modules,$(MAKECMDGOALS)),)
  KBUILD_MODULES := 1
endif

用filter模式过滤,只要是make all/ make _all / make xxx modules之中之一,就要进行模块化编译。

ifeq ($(MAKECMDGOALS),)
  KBUILD_MODULES := 1
endif

make为空这种,就是没有指定目标的,也要进行模块化编译,默认就是all嘛。

export KBUILD_MODULES KBUILD_BUILTIN
export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD

导出这一对给子makefile用。

ifneq ($(CC),)
ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1)
COMPILER := clang
else
COMPILER := gcc
endif
export COMPILER
endif

$CC如果不为空,通过版本判断编译器使用什么,这里一般都是gcc。Clang是一个C语言、C++、Objective-C语言的轻量级编译器,用的比较少,一般大家用GCC的多。把COMPILER导出给子makefile用。

scripts/Kbuild.include: ;
include scripts/Kbuild.include

包含scripts/Kbuild.include这个文件;

AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm
STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump
AWK		= awk
GENKSYMS	= scripts/genksyms/genksyms
INSTALLKERNEL  := installkernel
DEPMOD		= /sbin/depmod
PERL		= perl
PYTHON		= python
CHECK		= sparse

CHECKFLAGS     := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \
		  -Wbitwise -Wno-return-void $(CF)
CFLAGS_MODULE   =
AFLAGS_MODULE   =
LDFLAGS_MODULE  =
CFLAGS_KERNEL	=
AFLAGS_KERNEL	=
CFLAGS_GCOV	= -fprofile-arcs -ftest-coverage


# Use USERINCLUDE when you must reference the UAPI directories only.
USERINCLUDE    := \
		-I$(srctree)/arch/$(hdr-arch)/include/uapi \
		-Iarch/$(hdr-arch)/include/generated/uapi \
		-I$(srctree)/include/uapi \
		-Iinclude/generated/uapi \
                -include $(srctree)/include/linux/kconfig.h

# Use LINUXINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
LINUXINCLUDE    := \
		-I$(srctree)/arch/$(hdr-arch)/include \
		-Iarch/$(hdr-arch)/include/generated/uapi \
		-Iarch/$(hdr-arch)/include/generated \
		$(if $(KBUILD_SRC), -I$(srctree)/include) \
		-Iinclude \
		$(USERINCLUDE)

KBUILD_CPPFLAGS := -D__KERNEL__

KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
		   -fno-strict-aliasing -fno-common \
		   -Werror-implicit-function-declaration \
		   -Wno-format-security \
		   -std=gnu89

KBUILD_AFLAGS_KERNEL :=
KBUILD_CFLAGS_KERNEL :=
KBUILD_AFLAGS   := -D__ASSEMBLY__
KBUILD_AFLAGS_MODULE  := -DMODULE
KBUILD_CFLAGS_MODULE  := -DMODULE
KBUILD_LDFLAGS_MODULE := -T $(srctree)/scripts/module-common.lds

# Read KERNELRELEASE from include/config/kernel.release (if it exists)
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM STRIP OBJCOPY OBJDUMP
export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS

export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN
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

# When compiling out-of-tree modules, put MODVERDIR in the module
# tree rather than in the kernel tree. The kernel tree might
# even be read-only.
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions

# Files to ignore in find ... statements

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
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
			 --exclude CVS --exclude .pc --exclude .hg --exclude .git
  • 定义各类工具
  • 定义各类头文件搜索路径
  • 定义编译选项
  • 导出变量
PHONY += scripts_basic
scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic   ①
	$(Q)rm -f .tmp_quiet_recordmcount    ②
  • 目标加上scripts_basic
  • 定义scripts_basic规则。
    ① @make -f ./scripts/Makefile.build obj=scripts/basic
    $(Q):@,‘@’添加到命令行前,这个命令将不被make回显出来。
    $(MAKE):make
    $(build):-f ./scripts/Makefile.build obj
    所以最后就是:@make -f ./scripts/Makefile.build obj=scripts/basic
    ② @rm -f .tmp_quiet_recordmcount
scripts/basic/%: scripts_basic ;

显示定义这条目标的规则,这个目录下所有的文件,都依赖scripts_basic

PHONY += outputmakefile   ①
outputmakefile:ifneq ($(KBUILD_SRC),)$(Q)ln -fsn $(srctree) source   ④
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
	    $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)  ⑤
endif

这个目标的作用是如果指定了单独的输出路径,就在输出路径下产生一个makefile,有了makefile,在输出路径下如果执行make操作就很方便了,make最怕的就是没makefile。
① 目标加上outputmakefile
② outputmakefile规则如下:
③ 如果KBUILD_SRC不为空,则执行④ ⑤
④ @ln -fsn . source,ln是做软连接,语法为ln [参数][源文件或目录][目标文件或目录]
-f 强制执行
-s 软链接(符号链接)
-n 把符号链接视为一般目录
这里就是为当前目录建立一个source的符号链接
⑤ @/bin/bash ./scripts/mkmakefile . . 4 1 执行这个脚本,不深究了

PHONY += asm-generic  ①
asm-generic:$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.asm-generic \
	            src=asm obj=arch/$(SRCARCH)/include/generated/asm  ③
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.asm-generic \
	            src=uapi/asm obj=arch/$(SRCARCH)/include/generated/uapi/asm  ④

汇编相关,支持在asm-generic使用通用的头。
① 目标加上asm-generic
② asm-generic规则:
③ @make -f ./scripts/Makefile.asm-generic src=asm obj=arch/arm/include/generated/asm
这条命令,最主要的目的是为了根据kbuild文件生成指定的头文件。make的时候,指定makefile为./scripts/Makefile.asm-generic,传入参数src和obj,所以有必要分析下./scripts/Makefile.asm-generic的内容。

#scripts/Makefile.asm-generic的内容如下:
kbuild-file := $(srctree)/arch/$(SRCARCH)/include/$(src)/Kbuild #line1
-include $(kbuild-file)                                         #line2

include scripts/Kbuild.include                                  #line3

# Create output directory if not already present
_dummy := $(shell [ -d $(obj) ] || mkdir -p $(obj))             #line4

quiet_cmd_wrap = WRAP    $@                                    #line5
cmd_wrap = echo "\#include " >$@             #line6

all: $(patsubst %, $(obj)/%, $(generic-y))                     #line7
	@:                                                         #line8

$(obj)/%.h:                                                    #line9
	$(call cmd,wrap)                                           #line10
  • line1:指明kbuild-file,这个文件里头指明要生成的.h文件的名字。类似generic-y += bitsperlong.h
  • line2:这里用到了-include语法,-include和include唯一的区别就是当所要包含的文件不存在时不会有错误提示、make也不会退出;只有真正由于不能正确完成终极目标的重建时(某些必需的目标无法在当前已读取的makefile文件内容中找到正确的重建规则),才会提示致命错误并退出。
  • line3:包含scripts/Kbuild.include 文件。这个文件定义了很多的通用定义,有点多,用到了再直接摘出来说。
  • line4:创建输出的目录,obj在②中传入的是arch/arm/include/generated/asm,所以输出目录其实就是此目录。bash语法 -d 就是判断一个目录是否存在,mkdir -p 则是级联创建此目录。
  • line5:这句没搞懂
  • line6:cmd_wrap赋值,这个就是后面要line10调用的命令,意思就是输出"#include ,其实就是各自根据参数生成各自的头,举个例子,如果要生成的.h文件是bitsperlong.h,那么最后在arch/arm/include/generated/asm下就会出现一个bitsperlong.h,内容是#include 。最后结尾有个[>$@],这是bash的变量,>是重定向符,$@是所有参数的意思,所以>$@就是重定向到参数的意思。。。这里没有理解为啥要重定向这个。
  • line 7:定义all规则,:后面是他的依赖。这里用到了patsubst,模式字符串替换函数。
    格式:$(patsubst ,, )
    功能:查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式,如果匹配的话,则以替换。这里,可以包括通配符“%”,表示任意长度的字串。如果中也包含“%”,那么,中的这个“%”将是中的那个“%”所代表的字串。
    在此处,就是任意长度字符串,就是变量generic-y的值,而generic-y来源于line1,由要生成的所有的.h文件名组成,就是$(obj)/%,最后总的意思就是把由.h文件名组成的字符串,每个.h前面都加个arch/arm/include/generated/asm/路径。
  • line 8:将其执行的命令行在执行前输出到屏幕上。
  • line9:定义$(obj)/%.h:规则。
  • line10:调用cmd_wrap,就是line6那个说明了。
    ④ @make -f ./scripts/Makefile.asm-generic src=uapi/asm obj=arch/arm/include/generated/uapi/asm
    和③一样的功能,就不描述了。
version_h := include/generated/uapi/linux/version.h  ①
old_version_h := include/linux/version.h             ②
no-dot-config-targets := clean mrproper distclean \
			 cscope gtags TAGS tags help% %docs check% coccicheck \
			 $(version_h) headers_% archheaders archscripts \
			 kernelversion %src-pkg                       ③

config-targets := 0    ④
mixed-targets  := 0    ⑤
dot-config     := 1ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)  ⑧
		dot-config := 0     ⑨
	endif
endif

ifeq ($(KBUILD_EXTMOD),)ifneq ($(filter config %config,$(MAKECMDGOALS)),)  ⑪
                config-targets := 1ifneq ($(words $(MAKECMDGOALS)),1)   ⑬
                        mixed-targets := 1   ⑭
                endif
        endif
endif

解决老版本的兼容问题。如果没有.config文件,而是config文件,比如make oldconfig all,这个时候就需要走此分支。
①、②:版本码。新版本中,include/linux/version.h可能不存在。
③:除去config之外的所有的target。打印出来后,clean mrproper distclean cscope gtags TAGS tags help% %docs check% coccicheck include/generated/uapi/linux/version.h headers_% archheaders archscripts kernelversion %src-pkg。
④:是否命令行中包含了config关键词,oldconfig,.config都算。默认为0,不包含config关键字。
⑤:混合方式,就是又有老的
config,又有.config。默认为0,不混合。
⑥:只有.config,默认置为1,认为是只包含.config。
⑦:如果命令行中,包含了no-dot-config-targets③中的目标,只要有一个都算,走此分支,比如make proper,就会走此分支。
⑧:filterout去反过滤,就是命令行中,过滤掉no-dot-config-targets③中的目标后,为空,则走此分支,可以理解为,除了no-dot-config-targets,没有其他的target了,这个时候肯定就表示没有包含config关键字的target了。
⑨:dot-config置为0,表示没有config目标。
⑩:如果不编译外置模块,则走此分支。这个变量是表示外置模块编译的意思,之前有解释。
⑪:命令行中,包含了config或者%config的目标,走此分支。make config,make .config,make oldconfig,就都在此分支了。
⑫:config-targets置为1。
⑬:这里用到了words函数。判断是否只有1个单词,如果不只1个,就设定mixed-targets=1。
单词个数统计函数: $(words )
功能: 统计字符串 中单词的个数
返回: 单词个数。
⑭:混合mixed-targets置为1。

ifeq ($(mixed-targets),1)

如果是混合模式,走此分支。

PHONY += $(MAKECMDGOALS) __build_one_by_one           ①

$(filter-out __build_one_by_one, $(MAKECMDGOALS)): __build_one_by_one  ②
	@:  ③

__build_one_by_one:$(Q)set -e; \         ⑤
	for i in $(MAKECMDGOALS); do \      ⑥
		$(MAKE) -f $(srctree)/Makefile $$i; \           ⑦
	done

①、目标加上命令行中给的,和__build_one_by_one。
②、过滤出命令行中给的目标,除了__build_one_by_one以外的,让他们都依赖__build_one_by_one。
③、将其执行的命令行在执行前输出到屏幕上。
④、__build_one_by_one规则。
⑤、执行这段脚本文件有命令有异常则退出。
⑥、依次取MAKECMDGOALS中的目标。
⑦、每个目标,都执行@make -f ./Makefile $$i,$$i代表访问shell命令中定义的i变量。

else

下面就是非混合模式了。

ifeq ($(KBUILD_EXTMOD),)                   #line1
PHONY += scripts                           #line2
scripts: scripts_basic include/config/auto.conf include/config/tristate.conf \
	 asm-generic                                 #line3
	$(Q)$(MAKE) $(build)=$(@)                    #line4

# Objects we will link into vmlinux / subdirs we need to visit
init-y		:= init/							#line5
drivers-y	:= drivers/ sound/ firmware/		#line6
net-y		:= net/								#line7
libs-y		:= lib/								#line8
core-y		:= usr/                             #line9
endif # KBUILD_EXTMOD
  • line1:编译非外部模块,就是编译内置目标了。
  • line2:目标上scripts。
  • line3:scripts依赖的各种。
  • line4:@make -f ./scripts/Makefile.build obj=scripts。$(@)代表规则中的目标集合,此处就是scripts。这里的$(build)值哪里来的呢?源头就在前面include scripts/Kbuild.include,此文件中有句话,build := -f $(srctree)/scripts/Makefile.build obj,就在那里赋值了。执行这句话的目的是,为了初始化很多kbuild文件需要的变量,还定义了很多的依赖规则,目标库名等等,反正做了挺多的。
  • line5 ~ line9:编译相关库对应的目录
ifeq ($(dot-config),1)										#line 1    
-include include/config/auto.conf							#line 2
ifeq ($(KBUILD_EXTMOD),)									#line 3
-include include/config/auto.conf.cmd						#line 4
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;			#line 5
include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd #line 6
	$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig		#line 7
else														#line 8
PHONY += include/config/auto.conf							#line 9

include/config/auto.conf:									#line 10
	$(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)												#line 10

endif # KBUILD_EXTMOD
else														#line 11
include/config/auto.conf: ;									#line 12
endif # $(dot-config)
  • line 1:如果命令行中显式的包含了config关键字或者是默认编译,就是也没有包含not-dot-config-target里头的目标,此时默认采用.config。
  • line 2:makefile中嵌入include/config/auto.conf,如果此文件不存在也不报错。
  • line 3:如果是不是外置模块编译,就是vmlinux等内置目标编译,走此分支。
  • line 4:包含include/config/auto.conf.cmd,如果此文件不存在也不报错。
  • line 5:$(KCONFIG_CONFIG) = .config,也就是.config include/config/auto.conf.cmd:此目标没有依赖
  • line 6:定义一个依赖关系。
  • line 7:@make -f ./Makefile silentoldconfig,执行Makefile的silentoldconfig配置。
  • line 8:如果是外置模块编译,走此分支。
  • line 9:目标加上include/config/auto.conf
  • line 10:$(Q)test -e include/generated/autoconf.h -a -e $@使用test -e这条shell命令,测试include/config/auto.conf文件是否存在,后面还接了-a -e 是把测试条件连接起来,判断include/config/auto.conf 和 include/config/auto.conf是否存在,-a -e都是判断文件是否存在不存在的。有一定优先级,具体啥优先级,没太清楚。总体意思是,只要有一个存在,结果就为真,继续往下。echo >&2;是打印空行。/bin/false啥都不干,设置退出码为1。
  • line 11:没有显示包含config关键字。
  • line 12:include/config/auto.conf:没有显示依赖关系。
all: vmlinux

all就是默认的target,如果make后什么都不带,就会执行all这个target了,他依赖于vmlinux,vmlinux很熟悉吧,就是我们最后生成的目标文件。

include arch/$(SRCARCH)/Makefile

包含arch/$(SRCARCH)/Makefile,如果是arm,就include arch/arm/Makefile

KBUILD_CFLAGS	+= $(call cc-option,-fno-delete-null-pointer-checks,)

KBUILD_CFLAGS存放的是传递给编译器的编译选项,如果没有特殊要求,编译的文件被编译时用的就用的它。

ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS	+= -Os $(call cc-disable-warning,maybe-uninitialized,)
else
KBUILD_CFLAGS	+= -O2
endif

编译优化选项,针对C的,CXX还有汇编应该都有另外的。如果没有定义宏CONFIG_CC_OPTIMIZE_FOR_SIZE,就用的-O2,目前用的多的就是这个。当然,加了优化,有些时候,编译器会给你自动优化,偶尔会出莫名的问题,这种就具体情况具体分析了,推荐还是二级优化。

# Tell gcc to never replace conditional load with a non-conditional one
KBUILD_CFLAGS	+= $(call cc-option,--param=allow-store-data-races=0)

继续加编译选项。这些也搞不清楚是写啥,直接看注释,告诉gcc不要用无条件加载替换条件加载。了解就好。

ifdef CONFIG_READABLE_ASM
# Disable optimizations that make assembler listings hard to read.
# reorder blocks reorders the control in the function
# ipa clone creates specialized cloned functions
# partial inlining inlines only parts of functions
KBUILD_CFLAGS += $(call cc-option,-fno-reorder-blocks,) \
                 $(call cc-option,-fno-ipa-cp-clone,) \
                 $(call cc-option,-fno-partial-inlining)
endif
ifneq ($(CONFIG_FRAME_WARN),0)
KBUILD_CFLAGS += $(call cc-option,-Wframe-larger-than=${CONFIG_FRAME_WARN})
endif

还要加编译选项。不去纠结了,直接翻译注释。针对汇编可读性和帧警告的。

ifdef CONFIG_CC_STACKPROTECTOR_REGULAR
  stackp-flag := -fstack-protector
  ifeq ($(call cc-option, $(stackp-flag)),)
    $(warning Cannot use CONFIG_CC_STACKPROTECTOR_REGULAR: \
             -fstack-protector not supported by compiler)
  endif
else
ifdef CONFIG_CC_STACKPROTECTOR_STRONG
  stackp-flag := -fstack-protector-strong
  ifeq ($(call cc-option, $(stackp-flag)),)
    $(warning Cannot use CONFIG_CC_STACKPROTECTOR_STRONG: \
	      -fstack-protector-strong not supported by compiler)
  endif
else
  # Force off for distro compilers that enable stack protector by default.
  stackp-flag := $(call cc-option, -fno-stack-protector)
endif
endif
KBUILD_CFLAGS += $(stackp-flag)

处理帧保护器模式的编译选项。

ifeq ($(COMPILER),clang)
KBUILD_CPPFLAGS += $(call cc-option,-Qunused-arguments,)
KBUILD_CPPFLAGS += $(call cc-option,-Wno-unknown-warning-option,)
KBUILD_CFLAGS += $(call cc-disable-warning, unused-variable)
KBUILD_CFLAGS += $(call cc-disable-warning, format-invalid-specifier)
KBUILD_CFLAGS += $(call cc-disable-warning, gnu)
# Quiet clang warning: comparison of unsigned expression < 0 is always false
KBUILD_CFLAGS += $(call cc-disable-warning, tautological-compare)
# CLANG uses a _MergedGlobals as optimization, but this breaks modpost, as the
# source of a reference will be _MergedGlobals and not on of the whitelisted names.
# See modpost pattern 2
KBUILD_CFLAGS += $(call cc-option, -mno-global-merge,)
KBUILD_CFLAGS += $(call cc-option, -fcatch-undefined-behavior)
else
# This warning generated too much noise in a regular build.
# Use make W=1 to enable this warning (see scripts/Makefile.build)
KBUILD_CFLAGS += $(call cc-disable-warning, unused-but-set-variable)
endif

根据编译器,选择编译器选项,一般gcc。

ifdef CONFIG_FRAME_POINTER
KBUILD_CFLAGS	+= -fno-omit-frame-pointer -fno-optimize-sibling-calls
else
# Some targets (ARM with Thumb2, for example), can't be built with frame
# pointers.  For those, we don't have FUNCTION_TRACER automatically
# select FRAME_POINTER.  However, FUNCTION_TRACER adds -pg, and this is
# incompatible with -fomit-frame-pointer with current GCC, so we don't use
# -fomit-frame-pointer with FUNCTION_TRACER.
ifndef CONFIG_FUNCTION_TRACER
KBUILD_CFLAGS	+= -fomit-frame-pointer
endif
endif

帧指针相关。如果定义了宏CONFIG_FRAME_POINTER,走此分支。

  • -fomit-frame-pointer:尽可能不生成栈帧。
  • -fno-omit-frame-pointer:生成栈帧
  • -fno-optimize-sibling-calls:不优化同级递归和尾递归
KBUILD_CFLAGS   += $(call cc-option, -fno-var-tracking-assignments)

-fvar-tracking-assignments:评注赋值以进行变量跟踪。加了no的就是不进行评注赋值了,不进行变量跟踪。

ifdef CONFIG_DEBUG_INFO
ifdef CONFIG_DEBUG_INFO_SPLIT
KBUILD_CFLAGS   += $(call cc-option, -gsplit-dwarf, -g)
else
KBUILD_CFLAGS	+= -g
endif
KBUILD_AFLAGS	+= -Wa,-gdwarf-2
endif

如果定义了宏CONFIG_DEBUG_INFO。

  • -gsplit-dwarf:产生调试信息在分开的.dwo文件中。
  • -g:生成默认格式的调试信息。
  • -Wa:将逗号分隔的 <选项> 传递给汇编器。
  • -gdwarf-2:生成DWARF version2 格式的调试信息。
ifdef CONFIG_DEBUG_INFO_DWARF4
KBUILD_CFLAGS	+= $(call cc-option, -gdwarf-4,)
endif

如果定义了宏CONFIG_DEBUG_INFO_DWARF4,走此分支。

  • -gdwarf-4:生成DWARF version4 格式的调试信息。
ifdef CONFIG_DEBUG_INFO_REDUCED
KBUILD_CFLAGS 	+= $(call cc-option, -femit-struct-debug-baseonly) \
		   $(call cc-option,-fno-var-tracking)
endif

如果定义了宏CONFIG_DEBUG_INFO_REDUCED,走此分支。

  • -femit-struct-debug-baseonly:积极地缩减结构体的调试信息。
  • -fno-var-tracking:不进行变量跟踪。
ifdef CONFIG_FUNCTION_TRACER
ifndef CC_FLAGS_FTRACE
CC_FLAGS_FTRACE := -pg
endif
export CC_FLAGS_FTRACE
ifdef CONFIG_HAVE_FENTRY
CC_USING_FENTRY	:= $(call cc-option, -mfentry -DCC_USING_FENTRY)
endif
KBUILD_CFLAGS	+= $(CC_FLAGS_FTRACE) $(CC_USING_FENTRY)
KBUILD_AFLAGS	+= $(CC_USING_FENTRY)
ifdef CONFIG_DYNAMIC_FTRACE
	ifdef CONFIG_HAVE_C_RECORDMCOUNT
		BUILD_C_RECORDMCOUNT := y
		export BUILD_C_RECORDMCOUNT
	endif
endif
endif

如果定义了宏CONFIG_FUNCTION_TRACER,走此分支。

ifdef CONFIG_DEBUG_SECTION_MISMATCH
KBUILD_CFLAGS += $(call cc-option, -fno-inline-functions-called-once)
endif

如果定义了宏CONFIG_DEBUG_SECTION_MISMATCH走此分支。

  • -fno-inline-functions-called-once:不是只集成单个调用者所需的函数。
# arch Makefile may override CC so keep this after arch Makefile is included
NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include)
CHECKFLAGS     += $(NOSTDINC_FLAGS)

CHECKFLAGS还不确定啥时候用。

  • -nostdinc:不搜索标准系统头文件目录(但仍将使用由 -isystem 指定的目录)
  • -print-file-name=<库>:显示<库>的完整路径。
# warn about C99 declaration after statement
KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)

# disable pointer signed / unsigned warnings in gcc 4.0
KBUILD_CFLAGS += $(call cc-disable-warning, pointer-sign)

# disable invalid "can't wrap" optimizations for signed / pointers
KBUILD_CFLAGS	+= $(call cc-option,-fno-strict-overflow)

# conserve stack if available
KBUILD_CFLAGS   += $(call cc-option,-fconserve-stack)

# disallow errors like 'EXPORT_GPL(foo);' with missing header
KBUILD_CFLAGS   += $(call cc-option,-Werror=implicit-int)

# require functions to have arguments in prototypes, not empty 'int foo()'
KBUILD_CFLAGS   += $(call cc-option,-Werror=strict-prototypes)

# Prohibit date/time macros, which would make the build non-deterministic
KBUILD_CFLAGS   += $(call cc-option,-Werror=date-time)

# use the deterministic mode of AR if available
KBUILD_ARFLAGS := $(call ar-option,D)
  • -Wdeclaration-after-statement:当声明出现在语句后时给出警告。
  • -fno-strict-overflow:不将有符号数溢出的行为视为未定义的 。
  • -fconserve-stack:没搜到
  • -Werror=implicit-int:未规定类型的声明,将该行为设置为警告
  • -Werror=strict-prototypes:严格的类型检查。比如定义某个函数,入参没有,如果不显式写void,可能都会报错。
  • -Werror=date-time:没查到,后面补充。
  • KBUILD_ARFLAGS:指的汇编的编译选项。
# check for 'asm goto'
ifeq ($(shell $(CONFIG_SHELL) $(srctree)/scripts/gcc-goto.sh $(CC)), y)
	KBUILD_CFLAGS += -DCC_HAVE_ASM_GOTO
	KBUILD_AFLAGS += -DCC_HAVE_ASM_GOTO
endif

这段是执行/bin/bash ./scripts/gcc-goto.sh arm-linux-gnueabihf-gcc。而gcc-got.sh是测试对’asm goto’ 是否支持。里头内容就自己去看了。

include scripts/Makefile.kasan
include scripts/Makefile.extrawarn

包含这两头文件,没去细看,后面补充。

# Add any arch overrides and user supplied CPPFLAGS, AFLAGS and CFLAGS as the
# last assignments
KBUILD_CPPFLAGS += $(ARCH_CPPFLAGS) $(KCPPFLAGS)
KBUILD_AFLAGS   += $(ARCH_AFLAGS)   $(KAFLAGS)
KBUILD_CFLAGS   += $(ARCH_CFLAGS)   $(KCFLAGS)

添加CXX,汇编,C的其他各种编译选项。

# Use --build-id when available.
LDFLAGS_BUILD_ID = $(patsubst -Wl$(comma)%,%,\
			      $(call cc-ldoption, -Wl$(comma)--build-id,))
KBUILD_LDFLAGS_MODULE += $(LDFLAGS_BUILD_ID)
LDFLAGS_vmlinux += $(LDFLAGS_BUILD_ID)

继续添加。不深究了。

ifeq ($(CONFIG_STRIP_ASM_SYMS),y)
LDFLAGS_vmlinux	+= $(call ld-option, -X,)
endif

如果定义了宏CONFIG_STRIP_ASM_SYMS,且值为y,LDFLAGS_vmlinux中添加-X,指定其后输入文件的语言。

export KBUILD_IMAGE ?= vmlinux

设置内核镜像的名字,并导出给下级makefile使用。

export	INSTALL_PATH ?= /boot

镜像和系统映射文件的存放路径,默认为boot下。

export INSTALL_DTBS_PATH ?= $(INSTALL_PATH)/dtbs/$(KERNELRELEASE)

导出设备树的存放路径,并导出给下级makefile使用。这里输出后为/boot/dtbs/4.1.15+

MODLIB	= $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
export MODLIB

导出MODLIB的存放路径,并导出给下级makefile使用。这里输出后为/lib/modules/4.1.15+

ifdef INSTALL_MOD_STRIP
ifeq ($(INSTALL_MOD_STRIP),1)
mod_strip_cmd = $(STRIP) --strip-debug
else
mod_strip_cmd = $(STRIP) $(INSTALL_MOD_STRIP)
endif # INSTALL_MOD_STRIP=1
else
mod_strip_cmd = true
endif # INSTALL_MOD_STRIP
export mod_strip_cmd

INSTALL_MOD_STRIP确定模块生成后是否需要被剥离。

mod_compress_cmd = true
ifdef CONFIG_MODULE_COMPRESS
  ifdef CONFIG_MODULE_COMPRESS_GZIP
    mod_compress_cmd = gzip -n
  endif # CONFIG_MODULE_COMPRESS_GZIP
  ifdef CONFIG_MODULE_COMPRESS_XZ
    mod_compress_cmd = xz
  endif # CONFIG_MODULE_COMPRESS_XZ
endif # CONFIG_MODULE_COMPRESS
export mod_compress_cmd

如果定义了CONFIG_MODULE_COMPRESS,则走此分支,确定模块被生成后是否需要被压缩。

# Select initial ramdisk compression format, default is gzip(1).
# This shall be used by the dracut(8) tool while creating an initramfs image.
#
INITRD_COMPRESS-y                  := gzip
INITRD_COMPRESS-$(CONFIG_RD_BZIP2) := bzip2
INITRD_COMPRESS-$(CONFIG_RD_LZMA)  := lzma
INITRD_COMPRESS-$(CONFIG_RD_XZ)    := xz
INITRD_COMPRESS-$(CONFIG_RD_LZO)   := lzo
INITRD_COMPRESS-$(CONFIG_RD_LZ4)   := lz4

确定初始ramdisk的压缩格式,默认是gzip。

ifdef CONFIG_MODULE_SIG_ALL
MODSECKEY = ./signing_key.priv
MODPUBKEY = ./signing_key.x509
export MODPUBKEY
mod_sign_cmd = perl $(srctree)/scripts/sign-file $(CONFIG_MODULE_SIG_HASH) $(MODSECKEY) $(MODPUBKEY)
else
mod_sign_cmd = true
endif
export mod_sign_cmd

CONFIG_MODULE_SIG_ALL不晓得做什么用。

ifeq ($(KBUILD_EXTMOD),)

下面是编译内置镜像时候,要走的分支。

core-y		+= kernel/ mm/ fs/ ipc/ security/ crypto/ block/

vmlinux-dirs	:= $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
		     $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
		     $(net-y) $(net-m) $(libs-y) $(libs-m)))

vmlinux-alldirs	:= $(sort $(vmlinux-dirs) $(patsubst %/,%,$(filter %/, \
		     $(init-) $(core-) $(drivers-) $(net-) $(libs-))))

init-y		:= $(patsubst %/, %/built-in.o, $(init-y))
core-y		:= $(patsubst %/, %/built-in.o, $(core-y))
drivers-y	:= $(patsubst %/, %/built-in.o, $(drivers-y))
net-y		:= $(patsubst %/, %/built-in.o, $(net-y))
libs-y1		:= $(patsubst %/, %/lib.a, $(libs-y))
libs-y2		:= $(patsubst %/, %/built-in.o, $(libs-y))
libs-y		:= $(libs-y1) $(libs-y2)
  • core-y:vmlinux需要编译的部分,和目录也是一一对应的。
  • vmlinux-dirs:先用filter过滤,保留满足%/这种格式的。比如:$(init-y) = init/,就满足此格式。然后用模式字符串替换函数patsubst,将过滤后的结果,用% 去替换%/,那其实就是去掉字符串最后面的/嘛。在我的环境下,vmlinux-dirs最后的结果是init usr arch/arm/vfp arch/arm/vdso arch/arm/kernel arch/arm/mm arch/arm/common arch/arm/probes arch/arm/net arch/arm/crypto arch/arm/firmware arch/arm/mach-imx kernel mm fs ipc security crypto block drivers sound firmware net arch/arm/lib lib
  • vmlinux-alldirs:先用filter去过滤,将满足%/的字符串留下来。patsubst去掉字符串最后的/,最后调用sort函数,对留下的结果以及vmlinux-dirs合并后的字符串按首字母进行排序,排序的结果赋值给vmlinux-alldirs。打印最后的结果为:arch/arm/common arch/arm/crypto arch/arm/firmware arch/arm/kernel arch/arm/kvm arch/arm/lib arch/arm/mach-imx arch/arm/mm arch/arm/net arch/arm/nwfpe arch/arm/oprofile arch/arm/probes arch/arm/vdso arch/arm/vfp arch/arm/xen block crypto drivers firmware fs init ipc kernel lib mm net security sound usr
    函数名称:排序函数—sort。
    语法:$(sort LIST)
    函数功能:给字串“LIST”中的单词以首字母为准进行排序(升序),并取掉重复
    的单词。
    返回值:空格分割的没有重复单词的字串。
    函数说明:两个功能,排序和去字串中的重复单词。可以单独使用其中一个功能。
  • 后面的init-y到net-y都是进行字符串替换。比如init-y,替换前为:init/,最后结果为:init/built-in.o
  • libs-y:把libs-y1和libs-y2中字符串拼在一起。
# Externally visible symbols (used by link-vmlinux.sh)
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds
export LDFLAGS_vmlinux

导出下列变量给子makefile使用,有些直接把自己环境上的结果贴出来。

  • KBUILD_VMLINUX_INIT:arch/arm/kernel/head.o init/built-in.o
  • KBUILD_VMLINUX_MAIN :usr/built-in.o arch/arm/vfp/built-in.o arch/arm/vdso/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/probes/built-in.o arch/arm/net/built-in.o arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o arch/arm/mach-imx/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o net/built-in.o
  • KBUILD_LDS :指定链接器脚本。arch/arm/kernel/vmlinux.lds
  • LDFLAGS_vmlinux :-p --no-undefined -X --pic-veneer --build-id
# used by scripts/pacmage/Makefile
export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools virt)

看注释意思,给scripts/pacmage/Makefile使用。先用filter-out,对变量vmlinux-alldirs的值,过滤出不满足模式arch/%,最后结果连同后面的arch Documentation …按首字母排序。最后结果为:Documentation arch block crypto drivers firmware fs include init ipc kernel lib mm net samples scripts security sound tools usr virt

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

不晓得这个变量做啥用的,直接把结果打印出来:arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o init/built-in.o usr/built-in.o arch/arm/vfp/built-in.o arch/arm/vdso/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/probes/built-in.o arch/arm/net/built-in.o arch/arm/crypto/built-in.o arch/arm/firmware/built-in.o arch/arm/mach-imx/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o net/built-in.o

# Final link of vmlinux
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)

CONFIG_SHELL:/bin/bash,前面已经确定了这个变量的值。
$<:makefile中依赖文件集合的第一个,这里没有,所以是空。
LD:arm-linux-gnueabihf-ld ,前面确定了这个变量的值。
LDFLAGS:前面确定了这个变量的值
/bin/bash arm-linux-gnueabihf-ld -EL -p --no-undefined -X --pic-veneer --build-id

quiet_cmd_link-vmlinux = LINK    $@

$@:规则中目标集合。这里如果没有,就直接是LINK。这个变量还不确定怎么用,先留悬念。

# Include targets which we want to
# execute if the rest of the kernel build went well.
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE

重头戏来了,vmlinux,终极大boss。依赖scripts/link-vmlinux.sh脚本,$(vmlinux-deps)。加FORCE参数,不管目标在不在,依赖是否更新,都重新生成目标以及依赖中隐含的目标。

ifdef CONFIG_HEADERS_CHECK
	$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif

如果定义了宏CONFIG_HEADERS_CHECK,就执行下面的命令进行头部检查:@make -f ./Makefile headers_check

ifdef CONFIG_SAMPLES
	$(Q)$(MAKE) $(build)=samples
endif

如果定义了宏CONFIG_SAMPLES,就执行下面的命令:@make -f ./scripts/Makefile.build obj=samples,这里不是很清楚在做什么。

ifdef CONFIG_BUILD_DOCSRC
	$(Q)$(MAKE) $(build)=Documentation
endif

如果定义了宏CONFIG_BUILD_DOCSRC,就执行下面的命令:@make -f ./scripts/Makefile.build obj=Documentation,这里不是很清楚在做什么。

ifdef CONFIG_GDB_SCRIPTS
	$(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py
endif

@ln -fsn /home/chen/tbox/linux-imx_4.1.15_2.1.0/scripts/gdb/vmlinux-gdb.py,这个创建软链接没有目标文件,疑问。这里的目的也没理清楚。

+$(call if_changed,link-vmlinux)

执行cmd_link-vmlinux,这个变量上面有定义过,做链接操作。

$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

排序后,指定依赖关系。

PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
	$(Q)$(MAKE) $(build)=$@

依次处理各级子目录,结果如下:

@make -f ./scripts/Makefile.build obj=usr
@make -f ./scripts/Makefile.build obj=arch/arm/vfp
@make -f ./scripts/Makefile.build obj=arch/arm/vdso
@make -f ./scripts/Makefile.build obj=arch/arm/kernel
@make -f ./scripts/Makefile.build obj=arch/arm/mm
@make -f ./scripts/Makefile.build obj=arch/arm/common
@make -f ./scripts/Makefile.build obj=arch/arm/probes
@make -f ./scripts/Makefile.build obj=arch/arm/net
@make -f ./scripts/Makefile.build obj=arch/arm/crypto
@make -f ./scripts/Makefile.build obj=arch/arm/firmware
@make -f ./scripts/Makefile.build obj=arch/arm/mach-imx
@make -f ./scripts/Makefile.build obj=kernel
@make -f ./scripts/Makefile.build obj=mm
@make -f ./scripts/Makefile.build obj=fs
@make -f ./scripts/Makefile.build obj=ipc
@make -f ./scripts/Makefile.build obj=security
@make -f ./scripts/Makefile.build obj=crypto
@make -f ./scripts/Makefile.build obj=block
@make -f ./scripts/Makefile.build obj=drivers
@make -f ./scripts/Makefile.build obj=sound
@make -f ./scripts/Makefile.build obj=firmware
@make -f ./scripts/Makefile.build obj=net
@make -f ./scripts/Makefile.build obj=arch/arm/lib
@make -f ./scripts/Makefile.build obj=lib
define filechk_kernel.release
	echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
endef

定义了这个函数,主要是输出kernel version,下面就会用到。

# Store (new) KERNELRELEASE string in include/config/kernel.release
include/config/kernel.release: include/config/auto.conf FORCE
	$(call filechk,kernel.release)

调用filechk_kernel.release。

PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3

目标加上这部分,prepareN在prepareN-1之前执行。

# prepare3 is used to check if we are building in a separate output directory,
# and if so do:
# 1) Check that make has not been executed in the kernel src $(srctree)
prepare3: include/config/kernel.release                         #line 1
ifneq ($(KBUILD_SRC),)											#line 2
	@$(kecho) '  Using $(srctree) as source for kernel'			#line 3
	$(Q)if [ -f $(srctree)/.config -o -d $(srctree)/include/config ]; then \
		echo >&2 "  $(srctree) is not clean, please run 'make mrproper'"; \
		echo >&2 "  in the '$(srctree)' directory.";\
		/bin/false; \
	fi;															#line 4
endif
  • line 1:定义prepare3的依赖关系。
  • line 2:如果KBUILD_SRC不为空。
  • line 3:$(kecho)能够将其后的内容输出到标准输出流(一般就是显示器),前提是没有使用“make -s”。
  • line 4:先判断 ( s r c t r e e ) / . c o n f i g 和 (srctree)/.config和 (srctree)/.config(srctree)/include/config至少有其一存在,如果存在,说明已经有编译过的内容,需要先清理。
# prepare2 creates a makefile if using a separate output directory
prepare2: prepare3 outputmakefile asm-generic

定义prepare2的依赖规则。

prepare1: prepare2 $(version_h) include/generated/utsrelease.h \
                   include/config/auto.conf         
	$(cmd_crmodverdir)

定义prepare1的依赖规则,执行cmd_crmodverdir。这个变量在后面定义。

archprepare: archheaders archscripts prepare1 scripts_basic

定义archprepare的依赖规则。

prepare0: archprepare FORCE
	$(Q)$(MAKE) $(build)=.

定义prepare0的依赖规则,执行@make -f ./scripts/Makefile.build obj=.。

prepare: prepare0

定义prepare0的依赖规则。

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

函数功能:判断内核版本号字符数目是否合法。

  • KERNELRELEASE:此变量是从include/config/kernel.release读出的,kernel的release版本号,我的环境是4.1.15+。
  • echo -n:不换行输出,我的环境输出:4.1.15+。
  • wc -c:统计文件的字节数。就是统计上一语句输出的字节数,也就是4.1.15+的字节数
  • -gt:看看有没有比uts_len大,如果大,就输出下面的错误提示,否则输出#define UTS_RELEASE 4.1.15+。
 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

函数功能:定义了版本码,并提供KERNEL_VERSION宏接口。

  • expr:expr命令是一个手工命令行计数器,用于在UNIX/LINUX下求表达式变量的值,一般用于整数值,也可用于字符串。
    ①:定义了LINUX_VERSION_CODE,它的值是shell脚本去计算的,公式为 V E R S I O N ∗ 65536 + VERSION*65536+ VERSION65536+(PATCHLEVEL) * 256+$(SUBLEVEL),makefile文件一开始就定义了这几个变量,代入进去:465536+1256+15=0x10000+0x100+0xf=0x1010f。
    ②:此表达式和①就是一样的。
$(version_h): $(srctree)/Makefile FORCE$(call filechk,version.h)$(Q)rm -f $(old_version_h)

①:定义version_h的依赖规则。version_h变量前面有定义,值是:include/generated/uapi/linux/version.h
②:调用filechk_version.h,就是刚才前面定义的函数。
③:old_version_h也是前面定义的,这句命令就是@rm -f include/linux/version.h

include/generated/utsrelease.h: include/config/kernel.release FORCE$(call filechk,utsrelease.h)

①:定义依赖关系
②:调用filechk_utsrelease.h

PHONY += headerdep														①
headerdep:
	$(Q)find $(srctree)/include/ -name '*.h' | xargs --max-args 1 \			
	$(srctree)/scripts/headerdep.pl -I$(srctree)/include				②

此规则的意思,就是调用scripts/headerdep.pl去处理./include/*.h文件。
①:目标加上headerdep。
②:定义规则。查找./include/目录下,文件名后缀是.h结尾的;xargs:xargs 是一个强有力的命令,它能够捕获一个命令的输出,然后传递给另外一个命令。这里就是将查询到的结果,传递给下一个命令,也就是传给perl脚本。--max-args 1:指命令参数最多为1,限定的是后面的perl脚本,看起来像是用脚本一个一个处理。这个perl脚本做啥用的,真没看懂,不是很懂perl,后续返回来补充。

# Firmware install
INSTALL_FW_PATH=$(INSTALL_MOD_PATH)/lib/firmware    					①
export INSTALL_FW_PATHPHONY += firmware_install												③
firmware_install: FORCE													④
	@mkdir -p $(objtree)/firmware										⑤
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_install	⑥

①:定义INSTALL_FW_PATH,在我环境上,INSTALL_FW_PATH=/lib/firmware。
②:导出INSTALL_FW_PATH给下级子目录用。
③:目标加上firmware_install。
④:定义firmware_install依赖规则,不依赖任何,但是加了FORCE关键字,无论目标是否存在或者有更新,都重新生成。
⑤:级联创建$(objtree)/firmware目录。
⑥:调用make命令,指定makefile为$(srctree)/scripts/Makefile.fwinst,obj=firmware __fw_install

#Default location for installed headers
export INSTALL_HDR_PATH = $(objtree)/usr

导出INSTALL_HDR_PATH 给下级makefile用,INSTALL_HDR_PATH 是默认安装header的路径。

# If we do an all arch process set dst to asm-$(hdr-arch)
hdr-dst = $(if $(KBUILD_HEADERS), dst=include/asm-$(hdr-arch), dst=include/asm)
  • if 函数的语法是:
    $(if , )或者$(if ,, )
    参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,会被计算,否则会被计算。
    if函数的返回值是:
    如果为真(非空字符串),那个会是整个函数的返回值。
    如果为假(空字符串),那么会是整个函数的返回值,此时如果没有被定义,那么,整个函数返回空字串。
  • 判断KBUILD_HEADERS是否有值,给dst变量赋值。hdr-arch前面有被赋值,等于SRCARCH,而SRCARCH= $(ARCH),在我的环境下就是arm。所以,总结下来就是KBUILD_HEADERS非空,dst=include/asm-$(hdr-arch),如果为空,dst=include/asm)
  • 看注释,如果想编译所有的arch,那么久需要给KBUILD_HEADERS赋值。
PHONY += archheaders
archheaders:
PHONY += archscripts
archscripts:

定义两个空规则,俺也不晓得为啥。。。。

PHONY += __headers										①
__headers: $(version_h) scripts_basic asm-generic archheaders archscripts FORCE$(Q)$(MAKE) $(build)=scripts build_unifdef			③

①:目标加上__headers
②:定义__headers的依赖。
③:@make -f ./scripts/Makefile.build obj=scripts build_unifdef

PHONY += headers_install_all									①
headers_install_all:$(Q)$(CONFIG_SHELL) $(srctree)/scripts/headers.sh install	③

①:加目标。
②:定义依赖关系。
③:@/bin/bash ./scripts/headers.sh install。作用后面补。

PHONY += headers_install													①
headers_install: __headers													②
	$(if $(wildcard $(srctree)/arch/$(hdr-arch)/include/uapi/asm/Kbuild),, \
	  $(error Headers not exportable for the $(SRCARCH) architecture))$(Q)$(MAKE) $(hdr-inst)=include/uapi									④
	$(Q)$(MAKE) $(hdr-inst)=arch/$(hdr-arch)/include/uapi/asm $(hdr-dst)

①:加目标
②:定依赖关系
③:wildcard是扩展通配符函数。语法:$(wildcard PATTERN...) 。此处先把$(srctree)/arch/$(hdr-arch)/include/uapi/asm/Kbuild)展开,如果不为空,啥都不干,如果为空,输出错误。纳闷,怎么就可以为空,没想通这种场景。
④:@make -f ./scripts/Makefile.headersinst obj=include/uapi。作用后面补充。
⑤:@make -f ./scripts/Makefile.headersinst obj=arch/arm/include/uapi/asm dst=include/asm。里头的两变量,前面都定义了。

PHONY += headers_check_all
headers_check_all: headers_install_all
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/headers.sh check				①

①:@/bin/bash ./scripts/headers.sh check。作用后面补。

PHONY += headers_check													
headers_check: headers_install
	$(Q)$(MAKE) $(hdr-inst)=include/uapi HDRCHECK=1$(Q)$(MAKE) $(hdr-inst)=arch/$(hdr-arch)/include/uapi/asm $(hdr-dst) HDRCHECK=1

①、@make -f ./scripts/Makefile.headersinst obj=include/uapi HDRCHECK=1
②、@make -f ./scripts/Makefile.headersinst obj=arch/arm/include/uapi/asm dst=include/asm HDRCHECK=1

PHONY += kselftest
kselftest:
	$(Q)$(MAKE) -C tools/testing/selftests run_tests

@make -C tools/testing/selftests run_tests,内核自检。

ifdef CONFIG_MODULES

编译modules。这个分支,默认是会进的,但是我还没有找到哪里定义的CONFIG_MODULES,后面返回来补充。fix me

all: modules

定义依赖规则。

PHONY += modules
modules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux) modules.builtin				①
	$(Q)$(AWK) '!x[$$0]++' $(vmlinux-dirs:%=$(objtree)/%/modules.order) > $(objtree)/modules.order 															②
	@$(kecho) '  Building modules, stage 2.';$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost								④
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_modbuild	⑤

①:定义依赖关系,不过其中有一个依赖要根据宏KBUILD_BUILTIN是否定义决定,如果定义了,就依赖vmlinux。
②:这个看着有点脑壳皮痛。一点点来。

  • $(AWK)在前面定义了,值为awk。awk是一个强大的文本分析工具,简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。
  • $(vmlinux-dirs:%=$(objtree)/%/modules.order):这句话是把vmlinux-dirs中的字符串,模式替换为$(objtree)/%/modules.order,比如usr,就变成./usr/modules.order,依此内推,把里头的字符串全部按此规则替换掉。
  • ‘!x[$$0]++’:这个有点没 明白,推测可能是判断字符串是否已经存在的意思,因为awk可以用循环,估计是依次推断读出来的内容,是否已经存在,如果不存在,才继续做下面操作,这样可以去除重复的内容。
  • 最后总体的意思,就是先将变量vmlinux-dirs的值依次进行替换,然后替换后的值其实就是一个文件的名字,比如./usr/modules.order就是一个文件名,然后依次读取此文件中的内容,每行用空格分开,判断这些按行读取的内容是否已经出现过,如果没有出现,就将值输出到$(objtree)/modules.order中去。
    ③:kecho:这个变量在kbuild.include里头有定义,kecho := $($(quiet)kecho),变量quiet=quiet_,所以kecho=quiet_kecho,而quiet_kecho在kbuild.include里头定义为了echo,quiet_kecho := echo,所以就是输出字符串Building modules, stage 2.
    ④:@make -f $(srctree)/scripts/Makefile.modpost。
    ⑤:@make -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_modbuild
modules.builtin: $(vmlinux-dirs:%=%/modules.builtin)$(Q)$(AWK) '!x[$$0]++' $^ > $(objtree)/modules.builtin			②

①:定义依赖规则。依赖的目标,由$(vmlinux-dirs)变成了将$(vmlinux-dirs)中的字符串模式替换为%/modules.builtin后的字符串。
②:awk又粉墨登场了。$^表示所有依赖的集合,就是①中模式替换后的字符串,替换后的字符串其实都是文件名,逐行读取那些文件的内容,去除重复的,输出到$(objtree)/modules.builtin里。

%/modules.builtin: include/config/auto.conf							①
	$(Q)$(MAKE) $(modbuiltin)=$*

①:定义%/modules.builtin的依赖关系。
②:@*:表示目标模式中%及其之前的部分。modbuiltin = -f ./scripts/Makefile.modbuiltin obj 最终的结果如下:
@make -f ./scripts/Makefile.modbuiltin obj=$*。结合上下文,$*其实就是$(vmlinux-dirs)。先模式替换给他加上的这个后缀嘛。

# Target to prepare building external modules
PHONY += modules_prepare
modules_prepare: prepare scripts

# Target to install modules
PHONY += modules_install
modules_install: _modinst_ _modinst_post

加目标,定义依赖关系。

PHONY += _modinst_
_modinst_:
	@rm -rf $(MODLIB)/kernel
	@rm -f $(MODLIB)/source
	@mkdir -p $(MODLIB)/kernel
	@ln -s `cd $(srctree) && /bin/pwd` $(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)/
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst

这里都是删建目录,做软链接和拷贝工作,最后执行了一个make。

PHONY += _modinst_post
_modinst_post: _modinst_
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_modinst
	$(call cmd,depmod)
  • 定义依赖关系。
  • @make -f ./scripts/Makefile.fwinst obj=firmware __fw_modinst
  • 调用cmd_depmod函数
ifeq ($(CONFIG_MODULE_SIG), y)
PHONY += modules_sign
modules_sign:
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modsign
endif
  • 如果CONFIG_MODULE_SIG = y
  • 加目标
  • @make -f ./scripts/Makefile.modsign
else # CONFIG_MODULES
# Modules not configured
# ---------------------------------------------------------------------------
modules modules_install: FORCE
	@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

只管打印,啥都没干。

###
# Cleaning is done on three levels.
# make clean     Delete most generated files
#                Leave enough to build external modules
# make mrproper  Delete the current configuration, and all generated files
# make distclean Remove editor backup files, patch leftover files and the like

# Directories & files removed with 'make clean'
CLEAN_DIRS  += $(MODVERDIR)
# Directories & files removed with 'make mrproper'
MRPROPER_DIRS  += include/config usr/include include/generated          \
		  arch/*/include/generated .tmp_objdiff
MRPROPER_FILES += .config .config.old .version .old_version \
		  Module.symvers tags TAGS cscope* GPATH GTAGS GRTAGS GSYMS \
		  signing_key.priv signing_key.x509 x509.genkey		\
		  extra_certificates signing_key.x509.keyid		\
		  signing_key.x509.signer vmlinux-gdb.py

一堆变量赋值,定义各种目录和文件夹或者文件名。

  • make clean:删除大部分生成的文件,留下足够的空间来构建外部模块。
  • make mrproper:删除当前配置和所有生成的文件
clean: rm-dirs  := $(CLEAN_DIRS)				①
clean: rm-files := $(CLEAN_FILES)				②
clean-dirs      := $(addprefix _clean_, . $(vmlinux-alldirs) Documentation samples)

①、②:定义clean的依赖规则,并给变量赋值。用:=会比较诡异,因为后面如果$开头的变量,后面值发生变化,是以最后一次变量值为准。
③:addprefix用于添加前缀,添加的目标是$(vmlinux-alldirs) Documentation samples)格式为:$(addprefix fixstring,string1 string2 …)。clean-dirs最后的结果就是:
clean. _clean_arch/arm/common _clean_arch/arm/crypto _clean_arch/arm/firmware _clean_arch/arm/kernel _clean_arch/arm/kvm _clean_arch/arm/lib _clean_arch/arm/mach-imx _clean_arch/arm/mm _clean_arch/arm/net _clean_arch/arm/nwfpe _clean_arch/arm/oprofile _clean_arch/arm/probes _clean_arch/arm/vdso _clean_arch/arm/vfp _clean_arch/arm/xen _clean_block _clean_crypto _clean_drivers _clean_firmware _clean_fs _clean_init _clean_ipc _clean_kernel _clean_lib _clean_mm _clean_net _clean_security _clean_sound _clean_usr _clean_Documentation _clean_samples

PHONY += $(clean-dirs) clean archclean vmlinuxclean
$(clean-dirs):
	$(Q)$(MAKE) $(clean)=$(patsubst _clean_%,%,$@)

① 定义依赖规则:

  • $(clean):定义在Kbuild.include中,值:-f ./scripts/Makefile.clean。
  • $(patsubst _clean_%,%,$@):patsubst 是字符串替换函数,前面说过了。$@目标的集合。意思就是把目标集合$(clean-dirs)中字符串前面的_clean_去除。最后执行的就是:@make -f ./scripts/Makefile.clean obj=$(vmlinux-alldirs) Documentation samples)
vmlinuxclean:
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/link-vmlinux.sh clean

@/bin/bash ./scripts/link-vmlinux.sh clean:调用这个shell脚本去清理。详情自己去看。

clean: archclean vmlinuxclean

依赖关系来了。

# mrproper - Delete all generated files, including .config
mrproper: rm-dirs  := $(wildcard $(MRPROPER_DIRS))
mrproper: rm-files := $(wildcard $(MRPROPER_FILES))
mrproper-dirs      := $(addprefix _mrproper_,Documentation/DocBook scripts)

定义依赖关系和变量,两个变量就是展开之前定义的两个变量,最后一个是加前缀。

PHONY += $(mrproper-dirs) mrproper archmrproper
$(mrproper-dirs):
	$(Q)$(MAKE) $(clean)=$(patsubst _mrproper_%,%,$@)

和上面的clean-dirs类似,不累赘了。

mrproper: clean archmrproper $(mrproper-dirs)
	$(call cmd,rmdirs)
	$(call cmd,rmfiles)
  • 定义依赖关系
  • 调用cmd_rmdirs和cmd_rmfiles。
PHONY += distclean
distclean: mrproper												①
	@find $(srctree) $(RCS_FIND_IGNORE) \
		\( -name '*.orig' -o -name '*.rej' -o -name '*~' \
		-o -name '*.bak' -o -name '#*#' -o -name '.*.orig' \
		-o -name '.*.rej' -o -name '*%'  -o -name 'core' \) \
		-type f -print | xargs rm -f							②

distclean来了。
①:定义依赖关系。
②:用find命令去$(srctree)下查找。

  • RCS_FIND_IGNORE:本文件前面定义并导出了这个变量的值。
    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
  • -o:或者的意思。顺便说下-a:而且。-not:相反
  • -type<文件类型>:  只寻找符合指定的文件类型的文件。f就是文件。
  • -prune  不寻找字符串作为寻找文件或目录的范本样式。如果想查找当前目录(/home/student)下的tmp.txt文件,但是想要避开sep目录:
    find /home/student -path /home/student/sep -prune -o -name "tmp.txt" -print
    照理来说,-prune必须搭配-path,-o一起使用,这里没有-path,有点没明白,到底要不要排除,排除哪里。
  • -print:假设find指令的回传值为True,就将文件或目录名称列出到标准输出。格式为每列一个名称,每个名称之前皆有"./"字符串。
    总结下来:到$(srctree)去查找符合条件的文件,条件是一堆名字或在一起,最后如果找到了就打印出来,最后删除。
# rpm target kept for backward compatibility
package-dir	:= scripts/package

%src-pkg: FORCE
	$(Q)$(MAKE) $(build)=$(package-dir) $@
%pkg: include/config/kernel.release FORCE
	$(Q)$(MAKE) $(build)=$(package-dir) $@
rpm: include/config/kernel.release FORCE
	$(Q)$(MAKE) $(build)=$(package-dir) $@

注释写了,给rpm目标留着的。

boards := $(wildcard $(srctree)/arch/$(SRCARCH)/configs/*_defconfig)
boards := $(sort $(notdir $(boards)))
board-dirs := $(dir $(wildcard $(srctree)/arch/$(SRCARCH)/configs/*/*_defconfig))
board-dirs := $(sort $(notdir $(board-dirs:/=)))

help:
...
$(help-board-dirs): help-%:
	@echo  'Architecture specific targets ($(SRCARCH) $*):'
	@$(if $(boards-per-dir), \
		$(foreach b, $(boards-per-dir), \
		printf "  %-24s - Build for %s\\n" $*/$(b) $(subst _defconfig,,$(b));) \
		echo '')
# Documentation targets
# ---------------------------------------------------------------------------
%docs: scripts_basic FORCE
	$(Q)$(MAKE) $(build)=scripts build_docproc
	$(Q)$(MAKE) $(build)=Documentation/DocBook $@

一堆help和doc的,就不写了。

else # KBUILD_EXTMOD
# make M=dir clean     Delete all automatically generated files
# make M=dir modules   Make all modules in specified dir
# make M=dir	       Same as 'make M=dir modules'
# make M=dir modules_install
#                      Install the modules built in the module directory
#                      Assumes install directory is already created
# We are always building modules
KBUILD_MODULES := 1
PHONY += crmodverdir
crmodverdir:
	$(cmd_crmodverdir)

编译外部模块。只有以上几种合法的语法。
①:在下面定义了:cmd_crmodverdir = $(Q)mkdir -p $(MODVERDIR)
$(if $(KBUILD_MODULES),; rm -f $(MODVERDIR)/*)

PHONY += $(objtree)/Module.symvers
$(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 )
  • test -e:条件测试,判断文件是否存在。
  • || :如果前面命令执行失败就执行这个。
    所以上面就是测试文件是否存在,不存在,打印提示信息。
module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD))
PHONY += $(module-dirs) modules
$(module-dirs): crmodverdir $(objtree)/Module.symvers
	$(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@)
  • 加前缀。
  • 依赖关系定义
  • 去掉前缀,然后做make操作。
  • 加那个前缀,其实就是为了做规则有个区分,没个别的啥,不然目标和最后规则中的元素,名字一样,不好区分。
modules: $(module-dirs)
	@$(kecho) '  Building modules, stage 2.';
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost

PHONY += modules_install
modules_install: _emodinst_ _emodinst_post

install-dir := $(if $(INSTALL_MOD_DIR),$(INSTALL_MOD_DIR),extra)
PHONY += _emodinst_
_emodinst_:
	$(Q)mkdir -p $(MODLIB)/$(install-dir)
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst

PHONY += _emodinst_post
_emodinst_post: _emodinst_
	$(call cmd,depmod)

clean-dirs := $(addprefix _clean_,$(KBUILD_EXTMOD))

PHONY += $(clean-dirs) clean
$(clean-dirs):
	$(Q)$(MAKE) $(clean)=$(patsubst _clean_%,%,$@)

clean:	rm-dirs := $(MODVERDIR)
clean: rm-files := $(KBUILD_EXTMOD)/Module.symvers

没有啥特殊的,就不拎出来写了。

help:
	@echo  '  Building external modules.'
	@echo  '  Syntax: make -C path/to/kernel/src M=$$PWD target'
	@echo  ''
	@echo  '  modules         - default target, build the module(s)'
	@echo  '  modules_install - install the module'
	@echo  '  clean           - remove generated files in module directory only'
	@echo  ''

# Dummies...
PHONY += prepare scripts
prepare: ;
scripts: ;
endif # KBUILD_EXTMOD

clean: $(clean-dirs)
	$(call cmd,rmdirs)
	$(call cmd,rmfiles)
	@find $(if $(KBUILD_EXTMOD), $(KBUILD_EXTMOD), .) $(RCS_FIND_IGNORE) \
		\( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \
		-o -name '*.ko.*' \
		-o -name '*.dwo'  \
		-o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \
		-o -name '*.symtypes' -o -name 'modules.order' \
		-o -name modules.builtin -o -name '.tmp_*.o.*' \
		-o -name '*.gcno' \) -type f -print | xargs rm -f
  • help加掉函数清理文件和目录。
quiet_cmd_tags = GEN     $@
      cmd_tags = $(CONFIG_SHELL) $(srctree)/scripts/tags.sh $@

tags TAGS cscope gtags: FORCE
	$(call cmd,tags)

调脚本生成tag。

PHONY += includecheck versioncheck coccicheck namespacecheck export_report

includecheck:
	find $(srctree)/* $(RCS_FIND_IGNORE) \
		-name '*.[hcS]' -type f -print | sort \
		| xargs $(PERL) -w $(srctree)/scripts/checkincludes.pl

versioncheck:
	find $(srctree)/* $(RCS_FIND_IGNORE) \
		-name '*.[hcS]' -type f -print | sort \
		| xargs $(PERL) -w $(srctree)/scripts/checkversion.pl

coccicheck:
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/$@

namespacecheck:
	$(PERL) $(srctree)/scripts/namespace.pl

export_report:
	$(PERL) $(srctree)/scripts/export_report.pl

endif #ifeq ($(config-targets),1)
endif #ifeq ($(mixed-targets),1)

一大堆一致性检查。

PHONY += checkstack kernelrelease kernelversion image_name

# UML needs a little special treatment here.  It wants to use the host
# toolchain, so needs $(SUBARCH) passed to checkstack.pl.  Everyone
# else wants $(ARCH), including people doing cross-builds, which means
# that $(SUBARCH) doesn't work here.
ifeq ($(ARCH), um)
CHECKSTACK_ARCH := $(SUBARCH)
else
CHECKSTACK_ARCH := $(ARCH)
endif
checkstack:
	$(OBJDUMP) -d vmlinux $$(find . -name '*.ko') | \
	$(PERL) $(src)/scripts/checkstack.pl $(CHECKSTACK_ARCH)

kernelrelease:
	@echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"

kernelversion:
	@echo $(KERNELVERSION)

image_name:
	@echo $(KBUILD_IMAGE)

发布和打印。

# Clear a bunch of variables before executing the submake
tools/: FORCE
	$(Q)mkdir -p $(objtree)/tools
	$(Q)$(MAKE) LDFLAGS= MAKEFLAGS="$(filter --j% -j,$(MAKEFLAGS))" O=$(objtree) subdir=tools -C $(src)/tools/

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

???

# Single targets are compatible with:
# - build with mixed source and output
# - build with separate output dir 'make O=...'
# - external modules
#
#  target-dir => where to store outputfile
#  build-dir  => directory in kernel source tree to use

ifeq ($(KBUILD_EXTMOD),)
        build-dir  = $(patsubst %/,%,$(dir $@))
        target-dir = $(dir $@)
else
        zap-slash=$(filter-out .,$(patsubst %/,%,$(dir $@)))
        build-dir  = $(KBUILD_EXTMOD)$(if $(zap-slash),/$(zap-slash))
        target-dir = $(if $(KBUILD_EXTMOD),$(dir $<),$(dir $@))
endif

针对单独目标的操作,比如make O=xxx

%.s: %.c prepare scripts FORCE
	$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
%.i: %.c prepare scripts FORCE
	$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
%.o: %.c prepare scripts FORCE
	$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
%.lst: %.c prepare scripts FORCE
	$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
%.s: %.S prepare scripts FORCE
	$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
%.o: %.S prepare scripts FORCE
	$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
%.symtypes: %.c prepare scripts FORCE
	$(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
# Modules
/: prepare scripts FORCE
	$(cmd_crmodverdir)
	$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \
	$(build)=$(build-dir)
# Make sure the latest headers are built for Documentation
Documentation/: headers_install
%/: prepare scripts FORCE
	$(cmd_crmodverdir)
	$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \
	$(build)=$(build-dir)
%.ko: prepare scripts FORCE
	$(cmd_crmodverdir)
	$(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1)   \
	$(build)=$(build-dir) $(@:.ko=.o)
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost

规则来咯。

quiet_cmd_rmdirs = $(if $(wildcard $(rm-dirs)),CLEAN   $(wildcard $(rm-dirs)))
      cmd_rmdirs = rm -rf $(rm-dirs)

quiet_cmd_rmfiles = $(if $(wildcard $(rm-files)),CLEAN   $(wildcard $(rm-files)))
      cmd_rmfiles = rm -f $(rm-files)

# Run depmod only if we have System.map and depmod is executable
quiet_cmd_depmod = DEPMOD  $(KERNELRELEASE)
      cmd_depmod = $(CONFIG_SHELL) $(srctree)/scripts/depmod.sh $(DEPMOD) \
                   $(KERNELRELEASE) "$(patsubst y,_,$(CONFIG_HAVE_UNDERSCORE_SYMBOL_PREFIX))"
# Create temporary dir for module support files
# clean it up only when building all modules
cmd_crmodverdir = $(Q)mkdir -p $(MODVERDIR) \
                  $(if $(KBUILD_MODULES),; rm -f $(MODVERDIR)/*)

这些前面用到了。

# read all saved command lines
targets := $(wildcard $(sort $(targets)))								①
cmd_files := $(wildcard .*.cmd $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd))

①、从命令行读取有的目标,排序,展开
②对于targets中每个字符串,处理成【目录.文件名.cmd】,联合【*.cmd】,全部展开。

  • foreach函数
    $(foreach ,,)
    这个函数的意思是,把参数;中的单词逐一取出放到参数;所指定的变量中,然后再执行< text>;所包含的表达式。每一次;会返回一个字符串,循环过程中,;的所返回的每个字符串会以空格分隔,最后当整个循环结束时,;所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
  • dir函数:取目录函数:
    语法:$(dir )
    功能: 从文件名序列 中取出目录部分
    返回: 文件名序列 中的目录部分
  • notdir函数: 取文件函数:
    语法:$(notdir )
    功能: 从文件名序列 中取出非目录部分
    返回: 文件名序列 中的非目录部分
ifneq ($(cmd_files),)
  $(cmd_files): ;	# Do not try to update included dependency files
  include $(cmd_files)
endif

如果不为空,加头文件。

endif	# skip-makefile

结束了。

PHONY += FORCE
FORCE:

# Declare the contents of the .PHONY variable as phony.  We keep that
# information in a variable so we can use it in if_changed and friends.
.PHONY: $(PHONY)

最终的.PHONY 依赖PHONY

你可能感兴趣的:(内核,makefile)