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的构建过程、链接脚本的作用等等。

参考

完!
2016年5月

posted @ 2017-10-14 10:20 rongpmcu 阅读(...) 评论(...) 编辑 收藏