前言
之前对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月
来源:https://www.cnblogs.com/rongpmcu/p/7662793.html