zoukankan      html  css  js  c++  java
  • 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月

  • 相关阅读:
    Java面向对象基本/传参/引用/访问控制/构造器
    二叉树遍历&分治
    Java基础 & 基本数据类型 & String类
    Java面向对象继承/重写/多态
    Java集合基础
    Java面向对象抽象类/接口类/内部类
    你碰我变
    cookie&&localstorage
    父亲的谎话,只有长大后才能听懂……
    CSS的兼容性
  • 原文地址:https://www.cnblogs.com/rongpmcu/p/7662793.html
Copyright © 2011-2022 走看看