linux kernel make构建分析

前言

之前对uboot的构建进行了分析,现在再对linux kernel的构建进行分析。几年前的确也分析过,但是只是停留在笔记层面,没有转为文章,这次下定决定来完善它。

环境

同样,采用的还是zynq平台的linux,从Makefile可以看到版本:

VERSION = 3
PATCHLEVEL = 15
SUBLEVEL = 0
EXTRAVERSION =
NAME = Shuffling Zombie Juror

linux Makefile支持的选项(最常用到的)

选项V,用于开启或者关闭执行make时编译信息的打印

    @echo  '  make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build'
@echo ' make V=2 [targets] 2 => give reason for rebuild of target' ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif

选项C,用于开启或者关闭静态代码检查

    @echo  '  make C=1   [targets] Check all c source with $$CHECK (sparse by default)'
@echo ' make C=2 [targets] Force check of all c source with $$CHECK' ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif

选项M/SUBDIRS,源码外模块编译时会用到

# Use make M=dir to specify directory of external module to build
# Old syntax make ... SUBDIRS=$PWD is still supported
# Setting the environment variable KBUILD_EXTMOD take precedence
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif

选项O/KBUILD_OUTPUT,指定out-of-build时的输出目录

    @echo  '  make O=dir [targets] Locate all output files in "dir", including .config'

ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif

选项W,也是代码检查用的,很少用到

    @echo  '  make W=n   [targets] Enable extra gcc checks, n=1,2,3 where'
@echo ' 1: warnings which may be relevant and do not occur too often'
@echo ' 2: warnings which occur quite often but may still be relevant'
@echo ' 3: more obscure warnings, can most likely be ignored'
@echo ' Multiple levels can be combined with W=12 or W=123' ifeq ("$(origin W)", "command line")
export KBUILD_ENABLE_EXTRA_GCC_CHECKS := $(W)
endif

构建分析

内核的构建一般包含两步(如果算上distclean的话,就是三步,如果再算上dtbs构建的话,就是四步,这里忽略那两个步骤的分析),下面会对每一个步骤进行跟踪分析

第一步,配置,对应的命令make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- zynq_zturn_defconfig

对应的规则是(顶层Makefile里):

%config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig $@

这里先解释两个地方

第一个,关于Q,它只是在KBUILD_VERBOSE为1的时候为空,不为1的时候,为@。在make中,一条命令前加@表示执行该命令的时候,不打印出执行的命令。而KBUILD_VERBOSE的设置,请参考关于"支持的选项"那节里的V选项的描述。

# A simple variant is to prefix commands with $(Q) - that's useful
# for commands that shall be hidden in non-verbose mode.
#
# $(Q)ln $@ :<
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif

也顺便说说quiet

# Normally, we echo the whole command before executing it. By making
# that echo $($(quiet)$(cmd)), we now have the possibility to set
# $(quiet) to choose other forms of output instead, e.g.
#
# quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@
# cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
#
# If $(quiet) is empty, the whole command will be printed.
# If it is set to "quiet_", only the short version will be printed.
# If it is set to "silent_", nothing will be printed at all, since
# the variable $(silent_cmd_cc_o_c) doesn't exist.
# 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

第二个,关于build(在scripts/Kbuild.include定义,它是由顶层Makefile所包含进来的):

# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj

现在重点关注这条规则的依赖,scripts_basic和outputmakefile

依赖1 scripts_basic(顶层Makefile中)

PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount

也就是说,会先执行

$(Q)$(MAKE) $(build)=scripts/basic

以及

$(Q)rm -f .tmp_quiet_recordmcount(这个就不用说了,删除临时文件)

经过上面对Q和build的说明,我们可以知道

$(Q)$(MAKE) $(build)=scripts/basic

等价于

$(Q)$(MAKE) -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj=scripts/basic

这里假设KBUILD_SRC为空(一般都是为空),那么上面的命令最终变成了

$(Q)$(MAKE) -f scripts/Makefile.build obj=scripts/basic

也就是说会执行scripts/Makefile.build这个Makefile,且设置输入变量obj为 scripts/basic

经过分析scripts/Makefile.build,最终执行的规则为:

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:

其中KBUILD_BUILTIN := 1在顶层Makefile有定义,如果执行”make modules”,会在214行开始对其进行一些处理

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

所以我们这里 KBUILD_BUILTIN :=1

如果执行”make all”、”make _all”、”make modules”、”make”中任一个命令,则会对这个变量进行处理

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

因此,我们这里KBUILD_MODULES :=

分析了这两个变量后,上面的规则可重新写为

__build: $(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)
@:

规则的命令是一个冒号命令”:”,冒号(:)命令是bash的内建命令,通常把它看作true命令。

下面来一个个分析在执行make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- zynq_zturn_defconfig的情况下,$(builtin-target) $(lib-target) $(extra-y)) $(subdir-ym) $(always)这些变量的值。

ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(subdir-m) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif ifneq ($(strip $(lib-y) $(lib-m) $(lib-n) $(lib-)),)
lib-target := $(obj)/lib.a
endif

这里的变量除了always,其他都为空,always来自于scripts/Makefile.build中,有

kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

由于我们输入的obj为scripts/basic,而该Makefile开头就有src := $(obj),于是我们可以知道,它最终包含了头文件scripts/basic/Makefile,该Makefile内容如下:

hostprogs-y := fixdep
always := $(hostprogs-y) # fixdep is needed to compile other host programs
$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep

由此可知,always的内容为fixdep。也就是说scripts_basic在执行make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- zynq_zturn_defconfig的结果就是构建fixdep

依赖2,outputmakefile

outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

这个规则的命令运行一个shell脚本scripts/makefile,并传递四个参数。这个脚本主要是在$(objtree)参数指定的目录中生成一个Makefile文件。由于这里KBUILD_SRC为空,所以这个脚本并不会被执行

分析完两个依赖后,再来看

%config: scripts_basic outputmakefile FORCE
$(Q)mkdir -p include/linux include/config
$(Q)$(MAKE) $(build)=scripts/kconfig $@

在他的依赖被处理完后,开始执行规则的命令。第一个命令创建了两个目录,第二个命令扩展后为

$(Q)$(MAKE) -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build  obj=scripts/kconfig zynq_zturn_defconfig

这个命令依然是执行scripts/Makefile.build这个makefile文件。并执行它里面zynq_zturn_defconfig的规则。根据上面的分析,在Makefile.build会包含scripts/kconfig/Makefile文件。然后执行以zynq_zturn_defconfig为目标的规则,在scripts/kconfig/Makefile中有规则:

%_defconfig: $(obj)/conf
$(Q)$< --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

其中Kconfig来自:

# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
# KBUILD_DEFCONFIG may point out an alternative default configuration
# used for 'make defconfig'
include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG ifdef KBUILD_KCONFIG
Kconfig := $(KBUILD_KCONFIG)
else
Kconfig := Kconfig
endif

arch/arm/Makefile中没有定义KBUILD_KCONFIG,因此Kconfig := Kconfig

等价于

scripts/kconfig/conf --defconfig=arch/$(SRCARCH)/configs/zynq_zturn_defconfig Kconfig

继续分析%_defconfig: $(obj)/conf,它会先构建conf(请参考scripts/Makefile.host),然后就是调用conf处理--defconfig=arch/$(SRCARCH)/configs/zynq_zturn_defconfig Kconfig。这就是我们实际配置的过程。

这里可以总结下,执行make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- zynq_zturn_defconfig,make第一步会先编译fixdep这个程序,它在内核的编译过程会用到,然后会构建conf,它用来解析Kconfig,最后会根据命令行情况生成.config

我们可以通过执行命令来验证下上面的分析(记得先distclean下):

$ make V=1 ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- zynq_zturn_defconfig
make -f scripts/Makefile.build obj=scripts/basic
gcc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -o scripts/basic/fixdep scripts/basic/fixdep.c
rm -f .tmp_quiet_recordmcount
mkdir -p include/linux include/config
make -f scripts/Makefile.build obj=scripts/kconfig zynq_zturn_defconfig
gcc -Wp,-MD,scripts/kconfig/.conf.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -DCURSES_LOC="<ncurses.h>" -DLOCALE -c -o scripts/kconfig/conf.o scripts/kconfig/conf.c
cat scripts/kconfig/zconf.tab.c_shipped > scripts/kconfig/zconf.tab.c
cat scripts/kconfig/zconf.lex.c_shipped > scripts/kconfig/zconf.lex.c
cat scripts/kconfig/zconf.hash.c_shipped > scripts/kconfig/zconf.hash.c
gcc -Wp,-MD,scripts/kconfig/.zconf.tab.o.d -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -DCURSES_LOC="<ncurses.h>" -DLOCALE -Iscripts/kconfig -c -o scripts/kconfig/zconf.tab.o scripts/kconfig/zconf.tab.c
In file included from scripts/kconfig/zconf.tab.c:2537:0:
scripts/kconfig/menu.c: In function ‘get_symbol_str’:
scripts/kconfig/menu.c:590:18: warning: ‘jump’ may be used uninitialized in this function [-Wmaybe-uninitialized]
jump->offset = strlen(r->s);
^
scripts/kconfig/menu.c:551:19: note: ‘jump’ was declared here
struct jump_key *jump;
^
gcc -o scripts/kconfig/conf scripts/kconfig/conf.o scripts/kconfig/zconf.tab.o
scripts/kconfig/conf --defconfig=arch/arm/configs/zynq_zturn_defconfig Kconfig
#
# configuration written to .config
#

第二步,构建,对应的命令make ARCH=arm CROSS_COMPILE=arm-xilinx-linux-gnueabi- zImage

BOOT_TARGETS    = zImage Image xipImage bootpImage uImage

$(BOOT_TARGETS): vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

由此可以看出依赖vmlinux,下面先看vmlinux

# Include targets which we want to
# execute if the rest of the kernel build went well.
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_SAMPLES
$(Q)$(MAKE) $(build)=samples
endif
ifdef CONFIG_BUILD_DOCSRC
$(Q)$(MAKE) $(build)=Documentation
endif
+$(call if_changed,link-vmlinux)

关于命令前加号说明

makefile中以+开头的命令的执行不受到 make的-n,-t,-q三个参数的影响。比方说你的 makefile 是这样写的

all:

echo hello

+echo world

正常你 make,两个echo命令都会执行

hello

world

然后你 make -n,就只有第二个echo命令会被执行了

接着看依赖$(vmlinux-deps):

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 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

由此可以看出主要是依赖$(head-y) $(init-y) $(core-y) $(libs-y) $(drivers-y) $(net-y)

其中

head-y      := arch/arm/kernel/head$(MMUEXT).o  (arch/arm/Makefile)

其他的都是在顶层的Makefile里定义

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)

写到这的时候,我发现网上有一个blog已经详细分析了整个构建过程,而且写的非常好(之前没看到它!!!),因此我就打算停止继续写这篇了,不重复造*,请大家直接参考 这里

完!

2016年5月

上一篇:redis 双写一致性 看一篇成高手系列1


下一篇:Ubuntu 14.10 下安装Ambari