linux kernel make构建分析

对着背影说爱祢 提交于 2020-02-23 05:17:10

前言

之前对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月

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!