zoukankan      html  css  js  c++  java
  • uboot makefile构建分析

    前言

    几年前分析过uboot的构建及启动过程,做了笔记,但最终没有转为文章。这次又有机会开发嵌入式产品了(之前一年多都是在搞x86 linux),看了下uboot的构建过程,觉得有必要写下整个分析过程,为了自己也为了分享,因此就有了这篇文章。

    目标

    通过分析uboot的整个构建过程,了解我们要开发的板子是通过哪些文件来配置的,这些配置是怎么对构建起作用的。我想只有清楚了这些,我们才能很轻松的对uboot进行修改、配置、裁剪等等来满足项目需求。而且,如果熟悉了uboot的构建过程,以后开发uboot支持的其他架构,其他板子就顺手多了。

    构建过程分析

    uboot的构建包含几个步骤,比如可选的清理、生成配置、编译。其实它是完全参考了linux内核的构建过程(最新的uboot就更像了,几乎是一个模子套出来的^_^),后面我会再写一篇linux内核的构建分析,就更有体会了。分析的uboot版本采用xilinx zynq_zturn的uboot,最新的uboot可以通过git clone git://git.denx.de/u-boot.git拿到,我要分析的uboot版本信息在makefile显示为:

    VERSION = 2013                                                                  
    PATCHLEVEL = 10                                                                 
    SUBLEVEL =                                                                      
    EXTRAVERSION =
    

    最新的显示为

    VERSION = 2016                                                                  
    PATCHLEVEL = 05                                                                 
    SUBLEVEL =                                                                      
    EXTRAVERSION =    
    

    可以看到年代差距还是非常大的,之所以不拿最新的uboot作为分析对象,其一,我需要验证我分析的每个过程,最新的uboot我没法马上验证,其二,原理是一样的,看懂了这篇文章,我相信最新的uboot也是可以轻而易举的看懂的_

    下面开始对每个步骤进行详细的分析。

    make ARCH=arm CROSS_COMPILE=arm-linux- distclean过程

    这一步是完全清理掉执行后面两个步骤生成的中间文件,一般在只在第一次构建的时候为了保险起见,执行下这个步骤。每当我们第一次编译uboot的时候,必须先弄清楚我们用什么编译器(由CROSS_COMPILE指定),编译什么架构的(由ARCH指定),对应板子的默认配置(第二步生成配置的时候由xxx_config指定)。这个太简单,不清楚也不会影响到我写这篇文章的目标,因此就不多说了。

    make ARCH=arm CROSS_COMPILE=arm-linux- zynq_zturn_config过程

    这一步就是生成配置的过程,主要生成了include/config.h和include/config.mk以及建立对应的链接文件指向对应的链接目录。

    unconfig:                                                                       
        @rm -f $(obj)include/config.h $(obj)include/config.mk                      
            $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp                    
            $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep                
            $(obj)include/spl-autoconf.mk                                          
            $(obj)include/tpl-autoconf.mk                                           
                                                                                    
    %_config::  unconfig                                                                                                                                          
        @$(MKCONFIG) -A $(@:_config=)    
    

    先处理unconfig依赖,清理掉之前生成的临时文件,然后通过@$(MKCONFIG) -A $(@:_config=)完成配置。MKCONFIG在前面已经配置成MKCONFIG := $(SRCTREE)/mkconfig,其实就是当前目录下的mkconfig shell脚本文件,而$(@:_config=)是make的内建语法,其实就是去掉zynq_zturn_config里的_config,因为$(@)就是zynq_zturn_config,所以最终就是执行mkconfig -A zynq_zturn。这个脚本的功能也就是前面说的,生成include/config.h和include/config.mk以及建立对应的链接文件指向对应的链接目录。下面通过直接注释的方式说明该脚本的执行过程。

    #!/bin/sh -e                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                    
    # Script to create header files and links to configure                          
    # U-Boot for a specific board.                                                  
    #                                                                               
    # Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC]                  
    #                                                                               
    # (C) 2002-2013 DENX Software Engineering, Wolfgang Denk <wd@denx.de>           
    #                   include/include/                                                            
    # SPDX-License-Identifier:  GPL-2.0+                                            
    #                                                                               
                                                                                    
    APPEND=no   # Default: Create new config file                                   
    BOARD_NAME=""   # Name to print in make output                                  
    TARGETS=""                                                                      
                                                                                    
    arch=""                                                                         
    cpu=""                                                                          
    board=""                                                                        
    vendor=""                                                                       
    soc=""                                                                          
    options=""                                                                      
    
    #如果输入参数是2个且第一个参数是-A(我当前就是这个情形),就直接从boards.cfg里面通过awk提取,放入到变量line中                                                                                
    if [ ( $# -eq 2 ) -a ( "$1" = "-A" ) ] ; then                               
        # Automatic mode                      
    #大致的意思是取某一行,这一行不是#开头,且它的第七个字段与第二个输入参数(也就是zynq_zturn)匹配,那么输出这行的每个字段                                      
        line=`awk '($0 !~ /^#/ && $7 ~ /^'"$2"'$/) { print $1, $2, $3, $4, $5, $6, $7, $8 }' boards.cfg`
        if [ -z "$line" ] ; then                                                    
            echo "make: *** No rule to make target \`$2_config'.  Stop." >&2        
            exit 1                                                                  
        fi                                                                          
      #如果提取成功,将这些变量设置为输入参数                                                                                
        set ${line}                                                                 
        # add default board name if needed                                          
        [ $# = 3 ] && set ${line} ${1}                                              
    fi                                                                              
    
    #这里就是遍历刚才设置的每一个参数,我这里的第一个参数就不符合,也就是直接跳过这段                                                                                
    while [ $# -gt 0 ] ; do                                                         
        case "$1" in                                                                
        --) shift ; break ;;                                                        
        -a) shift ; APPEND=yes ;;                                                   
        -n) shift ; BOARD_NAME="${7%_config}" ; shift ;;                            
        -t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;       
        *)  break ;;                                                                
        esac                                                                        
    done                                                                            
                                                                                    
    [ $# -lt 7 ] && exit 1                                                          
    [ $# -gt 8 ] && exit 1                                                          
                                                                                    
    # Strip all options and/or _config suffixes                                     
    # 第七个输入参数根据boards.cfg描述,应该是target
    CONFIG_NAME="${7%_config}"                                                      
    # 如果BOARD_NAME为空,也将其设置为target                                                                               
    [ "${BOARD_NAME}" ] || BOARD_NAME="${7%_config}"                                
    
    #根据boards.cfg里面的描述,每个字段意义如下:
    #Status, Arch, CPU:SPLCPU, SoC, Vendor, Board name, Target, Options, Maintainers
    #下面就是根据配置来设置变量arch cpu spl_cpu board vendor soc                                                                              
    arch="$2"                                                                       
    cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $1}'`                             
    spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $2}'`                         
    if [ "$6" = "-" ] ; then                                                        
        board=${BOARD_NAME}                                                         
    else                                                                            
        board="$6"                                                                  
    fi                                                                              
    [ "$5" != "-" ] && vendor="$5"                                                  
    [ "$4" != "-" ] && soc="$4"                                                     
    [ $# -gt 7 ] && [ "$8" != "-" ] && {                                            
        # check if we have a board config name in the options field                 
        # the options field mave have a board config name and a list                
        # of options, both separated by a colon (':'); the options are              
        # separated by commas (',').                                                
        #                                                                           
        # Check for board name                                                      
        tmp="${8%:*}"                                                               
        if [ "$tmp" ] ; then                                                        
            CONFIG_NAME="$tmp"                                                      
        fi                                                                          
        # Check if we only have a colon...                                          
        if [ "${tmp}" != "$8" ] ; then                                              
            options=${8#*:}                                                         
            TARGETS="`echo ${options} | sed 's:,: :g'` ${TARGETS}"                  
        fi                                                                          
    }                                                                               
                                                                                    
    if [ "${ARCH}" -a "${ARCH}" != "${arch}" ]; then                                
        echo "Failed: $ARCH=${ARCH}, should be '${arch}' for ${BOARD_NAME}" 1>&2   
        exit 1                                                                      
    fi                                                                              
                                                                                    
    if [ "$options" ] ; then                                                        
        echo "Configuring for ${BOARD_NAME} - Board: ${CONFIG_NAME}, Options: ${options}"
    else                                                                            
        echo "Configuring for ${BOARD_NAME} board..."                               
    fi                                                                              
                                                                                    
    #                                                                               
    # Create link to architecture specific headers                                  
    #      
    # 这里会根据是否是out-of-build,配置的arch来设置目录链接,这些目录链接在编译时需要用到                                                                         
    if [ "$SRCTREE" != "$OBJTREE" ] ; then                                          
        mkdir -p ${OBJTREE}/include                                                 
        mkdir -p ${OBJTREE}/include2                                                
        cd ${OBJTREE}/include2                                                      
        rm -f asm                                                                   
        ln -s ${SRCTREE}/arch/${arch}/include/asm asm                               
        LNPREFIX=${SRCTREE}/arch/${arch}/include/asm/                               
        cd ../include                                                               
        mkdir -p asm                                                                
    else                                                                            
        cd ./include                                                                
        rm -f asm                                                                   
        ln -s ../arch/${arch}/include/asm asm                                       
    fi                                                                              
                                                                                    
    rm -f asm/arch                                                                  
                                                                                    
    if [ -z "${soc}" ] ; then                                                       
        ln -s ${LNPREFIX}arch-${cpu} asm/arch                                       
    else                                                                            
        ln -s ${LNPREFIX}arch-${soc} asm/arch                                       
    fi                                                                              
                                                                                    
    if [ "${arch}" = "arm" ] ; then                                                 
        rm -f asm/proc                                                              
        ln -s ${LNPREFIX}proc-armv asm/proc                                         
    fi                                                                              
                                                                                    
    #                                                                               
    # Create include file for Make                                                  
    #          
    #这里就是创建include/config.mk文件了,这个文件是给编译的时候include的,用来告诉它以下信息:
    #ARCH   = arm                                                                                                                                                                      
    #CPU    = armv7                                                                  
    #BOARD  = zynq                                                                   
    #VENDOR = xilinx                                                                 
    #SOC    = zynq                                                                    
    ( echo "ARCH   = ${arch}"                                                       
        if [ ! -z "$spl_cpu" ] ; then                                               
        echo 'ifeq ($(CONFIG_SPL_BUILD),y)'                                         
        echo "CPU    = ${spl_cpu}"                                                  
        echo "else"                                                                 
        echo "CPU    = ${cpu}"                                                      
        echo "endif"                                                                
        else                                                                        
        echo "CPU    = ${cpu}"                                                      
        fi                                                                          
        echo "BOARD  = ${board}"                                                    
                                                                                    
        [ "${vendor}" ] && echo "VENDOR = ${vendor}"                                
        [ "${soc}"    ] && echo "SOC    = ${soc}"                                   
        exit 0 ) > config.mk                                                        
                                                                                    
    # Assign board directory to BOARDIR variable                                    
    if [ -z "${vendor}" ] ; then                                                    
        BOARDDIR=${board}                                                           
    else                                                                            
        BOARDDIR=${vendor}/${board}                                                 
    fi                                                                              
                                                                                    
    #                                                                               
    # Create board specific header file                                             
    #               
    #这里就是创建include/config.h文件了                                                                
    if [ "$APPEND" = "yes" ]    # Append to existing config file                    
    then                                                                            
        echo >> config.h                                                            
    else                                                                            
        > config.h      # Create new config file                                    
    fi                                                                              
    echo "/* Automatically generated - do not edit */" >>config.h                   
                                                                                    
    for i in ${TARGETS} ; do                                                        
        i="`echo ${i} | sed '/=/ {s/=/  /;q; } ; { s/$/ 1/; }'`"                    
        echo "#define CONFIG_${i}" >>config.h ;                                     
    done                                                                            
                                                                                    
    echo "#define CONFIG_SYS_ARCH  "${arch}""  >> config.h                        
    echo "#define CONFIG_SYS_CPU   "${cpu}""   >> config.h                        
    echo "#define CONFIG_SYS_BOARD "${board}"" >> config.h                        
                                                                                    
    [ "${vendor}" ] && echo "#define CONFIG_SYS_VENDOR "${vendor}"" >> config.h   
                                                                                    
    [ "${soc}"    ] && echo "#define CONFIG_SYS_SOC    "${soc}""    >> config.h   
                                                                                    
    cat << EOF >> config.h                                                          
    #define CONFIG_BOARDDIR board/$BOARDDIR                                         
    #include <config_cmd_defaults.h>                                                
    #include <config_defaults.h>                                                    
    #include <configs/${CONFIG_NAME}.h>                                             
    #include <asm/config.h>                                                         
    #include <config_fallbacks.h>                                                   
    #include <config_uncmd_spl.h>                                                   
    EOF                                                                             
                                                                                    
    exit 0    
    

    总的来说,就是根据zynq_zturn_config从boards.cfg里面提取硬件信息,如架构名称、cpu名称、板子名称、厂商名称、soc名称,然后生成include/config.h和include/config.mk,其中include/config.mk给makefile用的,而include/config.h则是生成配置信息头文件所依赖的,也是很多其他头文件依赖的。

    make ARCH=arm CROSS_COMPILE=arm-linux- 过程

    这部分选几个重点进行说明。

    第一点,支持的配置变量

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

    通过O=xxx 来指定out-of-build,当然也就是通过export BUILD_DIR=xxx来指定,不过命令行的O=xxx优先级高

    C
    ifdef C                                                                         
    ifeq ("$(origin C)", "command line")                                            
    CHECKSRC := $(C)                                                                
    endif                                                                           
    endif
    

    通过C=1来开启静态代码检查,这个是sparse实现的,应该是linus本人写的一个工具

    第二点,伪目标all
    ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))              
                                                                                    
    # Include autoconf.mk before config.mk so that the config options are available 
    # to all top level build files.  We need the dummy all: target to prevent the   
    # dependency target in autoconf.mk.dep from being the default.                  
    all:                                                                            
    sinclude $(obj)include/autoconf.mk.dep                                          
    sinclude $(obj)include/autoconf.mk                                              
                                                                                    
    ifndef CONFIG_SANDBOX                                                           
    SUBDIRS += $(SUBDIR_EXAMPLES)                                                   
    endif        
    ...
    ...
    ...
    all:        $(ALL-y) $(SUBDIR_EXAMPLES) 
    ...
    ...
    ...
    else    # !config.mk                                                            
    all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin                        
    $(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot                                 
    $(filter-out tools,$(SUBDIRS))                                                 
    updater depend dep tags ctags etags cscope $(obj)System.map:                    
        @echo "System not configured - see README" >&2                              
        @ exit 1                                                                    
                                                                                    
    tools: $(VERSION_FILE) $(TIMESTAMP_FILE)                                        
        $(MAKE) -C $@ all                                                           
    endif   # config.mk         
    

    我们会发现有三个all目标,最后的那个all只在你还没执行第二步配置的时候会执行且提示你系统还没配置呢!第一个和第二个all在执行了第二步配置后生效,之所以要弄两个all,用它原话说:

    # Include autoconf.mk before config.mk so that the config options are available 
    # to all top level build files.  We need the dummy all: target to prevent the   
    # dependency target in autoconf.mk.dep from being the default. 
    

    其实就是防止你在第三步执行的时候,没有指定目标,那这时会采用第一个遇到的合适的作为目标,那么在第二个all前的include里所载入的makefile里面的目标就很有可能被当做目标,所以为了防止这一点,使用了两个目标,前面的目标就是告诉makefile这次构建的目标是all,当它又遇到后面的all时,make会将两次的依赖整合起来,这个语法是make所支持的。

    第三点,include/autoconf.mk及include/autoconf.mk.dep生成过程

    在前面有看到,第一个all后紧跟着就是sinclude 它俩,问题是在执行完第二步后,这两是还没有生成的。根据make的原则,文件不存在,那么跳过它,继续处理后面的内容,等到所有的都处理完后,再回来看能否通过规则找到这俩。下面给出make的大致流程:

    1. 读入所有的Makefile。
    2. 读入被include的其它Makefile。
    3. 初始化文件中的变量。
    4. 推导隐晦规则,并分析所有规则。
    5. 为所有的目标文件创建依赖关系链。
    6. 根据依赖关系,决定哪些目标要重新生成。
    7. 执行生成命令。

    往后找,我们能够看到它俩的规则:

    $(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h          
        @$(XECHO) Generating $@ ;                                                  
        set -e ;                                                                   
        : Generate the dependancies ;                                              
        $(CC) -x c -DDO_DEPS_ONLY -M $(CFLAGS) $(CPPFLAGS)                         
            -MQ $(obj)include/autoconf.mk include/common.h > $@                     
                                                                                    
    $(obj)include/autoconf.mk: $(obj)include/config.h                               
        @$(XECHO) Generating $@ ;                                                  
        set -e ;                                                                   
        : Extract the config macros ;                                              
        $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h |                     
            sed -n -f tools/scripts/define2mk.sed > $@.tmp &&                      
        mv $@.tmp $@  
    

    这里会从include/common.h里面提取并生成配置信息

    CONFIG_SPI_FLASH_WINBOND=y                                                                                                                                                        
    CONFIG_CMD_FAT=y                                                                
    CONFIG_ARMV7=y                                                                  
    CONFIG_CMD_ITEST=y                                                              
    CONFIG_CMD_EDITENV=y                                                            
    CONFIG_SYS_ENET=y                                                               
    CONFIG_ZYNQ_BOOT_FREEBSD=y                                                      
    CONFIG_ZYNQ=y       
    ...
    ...
    ...
    

    从这里我们会发现,如果我们要配置uboot,这就是我们的入口点。这次就分析到这里,后面的部分其实就容易了,当然如果有必要,我会继续再写一篇完善这篇,包括uboot bin的构建过程、链接脚本的作用等等。

    参考

    • [U-BOOT顶层MAKEFILE详解](http://www. csdn123.com/html/blogs/20130710/34823.htm)

    完!
    2016年5月

  • 相关阅读:
    JVM参数默认值列表
    垃圾回收G1日志解析
    《深入理解JAVA虚拟机》垃圾回收时为什么会停顿
    《深入理解JAVA虚拟机》JDK的垃圾收集算法
    什么才是技术?
    Lodash使用示例(比较全)
    MSCL超级工具类(C#),开发人员必备,开发利器
    刷新SqlServer数据库中所有的视图
    Sql Server 2014/2012/2008/2005 数据库还原出现 3154错误的解决办法
    C#中执行批处理文件(.bat),执行数据库相关操作
  • 原文地址:https://www.cnblogs.com/rongpmcu/p/7662791.html
Copyright © 2011-2022 走看看