zoukankan      html  css  js  c++  java
  • UBoot 目录结构和编译过程


       本文是基于u-boot-04.04.00.01进行分析,硬件平台基于DM8168 (Cortex-A8 + DSP  + M3(视频处理子系统))  

    一. U-Boot 目录结构

         
     
      1.board
      本目录存放与已有开发板相关的文件。每种开发板有一个子目录,子目录仅存放与开发板相关的c文件和配置文件,不包含开发板CPU架构通用的实现文件。
      每个目录下有如下文件(以/board/ti/ti8168_dvr/为例):
        Makefile
          config.mk 
          dvr.c
      wire3_rom.c       
        u-boot.lds      全局链接文件

      2.common
      实现u-boot命令行下支持的命令,每一条命令对应一个文件。例如bootm命令对应的是cmd_bootm.c。

      3.arch
         与处理器体系架构相关目录,每一款体系架构的处理器均在一个子目录下,每个子目录下面包含系列此体系架构处理器,  下子目录基于某种系列处理器的不同的解决方案。
         每个目录下有如下文件(以arch/arm/cpu/arm_cortexa8/为例):
         Makefile
         config.mk
         cpu.c           和处理器相关的代码
         mx51/
         oamp3/
         s5pc1xx/
         start.S
         ti81xx/
         u-boot.lds  

         4.disk    

      对磁盘的支持。

         5.doc
         文档目录。

         6.drivers
         设备驱动程序目录。比如串口、USB、mmc等。

         7.fs
         支持的文件系统。u-boot支持cramfs、ext2、fat、fdos、jffs2、reiserfs、ubifs、yaffs2文件系统。

         8.include
         使用的头文件均在改目录下,还有对各种硬件平台支持的汇编文件、系统配置文件和文件系统支持的文件。
         该目录下configs目录有与开发板相关的配置文件。例如smdkv210single.h。
         该目录下asm目录有与cpu体系结构相关的头文件,例如asm-arm目录下有arch-s5pc11x目录。

         9.lib
         所有处理器通用的库文件。

         10.net
         与网络协议栈相关的代码,bootp协议、tftp协议、rarp协议和nfs文件系统等实现。

         11.tools
         生成u-boot工具,例如mkimage。

         12.api
         处理器或者开发板为外部应用程序提供独立的API。

         13.example
         一些独立的应用程序示范代码。 

         14.其他 etc。

    二. U-boot 编译与链接 

    参考原文:http://www.cnblogs.com/cslunatic/admin/EditPosts.aspx?postid=2982302

       1.  对于本开发板,编译U-Boot需要执行如下的命令:

    $  export PATH=/opt/armgcc/bin:$PATH
    $  make distclean
    $  make ti8168_dvr_config
    $  make u-boot.ti
           使用上面的命令编译U-Boot,编译生成的所有文件都保存在源代码目录中。为了保持源代码目录的干净,可以使用如下命令将编译生成的文件输出到一个外部目录,而不是在源代码目录中,下面的2种方法都将编译生成的文件输出到 /tmp/build目录:
    $  export  BUILD_DIR=/tmp/build
    $  make  ti8168_dvr_config
    $  make  u-boot.ti

    $  make  O=/tmp/build ti8168_dvr_config (注意是字母O,而不是数字0)
    $  make  u-boot.ti

         2. make <board_name>_config 命令执行过程 

       (1)定义主机系统架构
    HOSTARCH := $(shell uname -m | \
           sed -e s/i.86/i386/ \
           ......
           “sed –e”表示后面跟的是一串命令脚本而表达式“s/abc/def/”表示要从标准输入中,查找到内容为“abc”的,然后替换成“def”。其中“abc”表达式用可以使用“.”作为通配符
           命令“uname –m”将输出主机CPU的体系架构类型。本人使用Intel 奔腾系列的CPU,因此“uname –m”输出“i686”。 “i686”可以匹配命令“sed -e s/i.86/i386/”中的“i.86”,因此在作者的机器上执行Makefile,HOSTARCH将被设置成“i386” 。

       (2)定义主机操作系统类型
    HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
               sed -e 's/\(cygwin\).*/cygwin/')
           “uname –s”输出主机内核名字,本人使用Linux发行版Ubuntu10.04,因此“uname –s”结果是“Linux”。“tr '[:upper:]' '[:lower:]'”作用是将标准输入中的所有大写字母转换为响应的小写字母。因此执行结果是将HOSTOS 设置为“linux”。

        (3)定义执行shell脚本的shell
    # Set shell to bash if possible, otherwise fall back to sh
    SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; 
           else if [ -x /bin/bash ]; then echo /bin/bash; \
           else echo sh; fi; fi)
           "$$BASH"的作用实质上是生成了字符串“$BASH”(前一个$号的作用是指明第二个$是普通的字符)。若执行当前Makefile的shell中定义了“$BASH”环境变量,且文件“$BASH”是可执行文件,则SHELL的值为“$BASH”。否则,若“/bin/bash”是可执行文件,则SHELL值为“/bin/bash”。若以上两条都不成立,则将“sh”赋值给SHELL变量。
           由于本人的机器安装了bash shell,且shell默认环境变量中定义了“$BASH”,因此SHELL 被设置为$BASH 。

         (4)设定编译输出目录

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

           函数$( origin, variable) 输出的结果是一个字符串,输出结果由变量variable定义的方式决定,若variable在命令行中定义过,则origin函数返回值为"command line"。假若在命令行中执行了“export BUILD_DIR=/tmp/build”的命令,则“$(origin O)”值为“command line”,而BUILD_DIR被设置为“/tmp/build”。
    ifneq ($(BUILD_DIR),)
    saved-output := $(BUILD_DIR)
    # Attempt to create a output directory.
    $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
          若${BUILD_DIR}表示的目录没有定义,则创建该目录。

    # Verify if it was successful.
    BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
    $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
    endif # ifneq ($(BUILD_DIR),)
           若$(BUILD_DIR)为空,则将其赋值为当前目录路径(源代码目录)。并检查$(BUILD_DIR)目录是否存在

           #OBJTREE和LNDIR为存放生成文件的目录,TOPDIR与SRCTREE为源码所在目录
    OBJTREE           := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
    SRCTREE          := $(CURDIR)
    TOPDIR            := $(SRCTREE)
    LNDIR              := $(OBJTREE)
    … …

            #定义变量MKCONFIG:这个变量指向一个脚本,即顶层目录的mkconfig。
    MKCONFIG      := $(SRCTREE)/mkconfig
    … …
    ifneq ($(OBJTREE),$(SRCTREE))
    obj := $(OBJTREE)/
    src := $(SRCTREE)/
    else
    obj :=
    src :=
    endif
           CURDIR变量指示Make当前的工作目录,由于当前Make在U-Boot顶层目录执行Makefile,因此CURDIR此时就是U-Boot顶层目录。
           执行完上面的代码后, SRCTREE,src变量就是U-Boot代码顶层目录,而OBJTREE,obj变量就是输出目录,若没有定义BUILD_DIR环境变量,则SRCTREE,src变量与OBJTREE,obj变量都是U-Boot源代码目录。而MKCONFIG则表示U-Boot根目录下的mkconfig脚本。

          (5) make ti8168_dvr_config命令执行过程
    ti8168_dvr_config \
    ti8168_dvr_min_sd:      unconfig
            @mkdir -p $(obj)include
            @echo "#define CONFIG_TI81XX"   >>$(obj)include/config.h
            @echo "#define CONFIG_TI816X"   >>$(obj)include/config.h
            @if [ "$(findstring _min_sd,$@)" ] ; then \
                    echo "#define CONFIG_SYS_NO_FLASH"    >>$(obj)include/config.h ; \
                    echo "#define CONFIG_MLO_BOOT"    >>$(obj)include/config.h ; \
                    echo "Setting up TI8168_dvr SD boot minimal build..." ; \
            else    \
                    echo "#define CONFIG_SYS_NO_FLASH"    >>$(obj)include/config.h ; \
                    echo "#define CONFIG_NAND_ENV"    >>$(obj)include/config.h ; \
                    echo "Setting up TI8168_dvr default build with NAND..." ; \
            fi;
            @$(MKCONFIG) -a ti8168_dvr arm arm_cortexa8 ti8168_dvr ti ti81xx

            其中的依赖“unconfig”定义如下:
    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
           其中“@”的作用是执行该命令时不在shell显示。“obj”变量就是编译输出的目录,因此“unconfig”的作用就是清除上次执行make *_config命令生成的配置文件(如include/config.h,include/config.mk等)。
           $(MKCONFIG)在上面指定为“$(SRCTREE)/mkconfig”。即将 ./mkconfig  -a ti8168_dvr arm arm_cortexa8 ti8168_dvr ti ti81xx作为参数传递给当前目录下的mkconfig脚本执行。
           $(@:_config=) arm arm920t mini2440 samsung s3c24x0”
           $(@:_config=)为将传进来的所有参数中的_config替换为空(其中“@”指规则的目标文件名,在这里就是“mini2440_config ”。$(text:patternA=patternB),这样的语法表示把text变量每一个元素中结尾的patternA的文本替换为patternB,然后输出) 。因此$(@:_config=)的作用就是将mini2440_config中的_config去掉,得到mini2440。:
    ./mkconfig mini2440 arm arm920t mini2440 samsung s3c24x0
           即将“mini2440 arm arm920t mini2440 samsung s3c24x0”作为参数传递给当前目录下的mkconfig脚本执行。 

           (6) 在mkconfig脚本中给出了mkconfig的用法:
    # Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC]
            1> 因此传递给mkconfig的参数的意义分别是:
    ti8168_dvr:Target(目标板型号)
    arm:Architecture (目标板的CPU架构)
    arm_cortexa8:CPU (具体使用的CPU型号)
    ti8168_dvr:Board
    ti:VENDOR(生产厂家名)
    ti81xx:SOC

    APPEND=no      # no表示创建新的配置文件,yes表示追加到配置文件中
    BOARD_NAME="" # Name to print in make output
    TARGETS=""

    while [ $# -gt 0 ] ; do
        case "$1" in
        --) shift ; break ;;
        -a) shift ; APPEND=yes ;;
        -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
        -t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;
        *)  break ;;
        esac
    done
    ["${BOARD_NAME}"] || BOARD_NAME="$1" 
           环境变量$#表示传递给脚本的参数个数,这里的命令有6个参数,因此$#是6 。shift的作用是使$1=$2,$2=$3,$3=$4….,而原来的$1将丢失。因此while循环的作用是,依次处理传递给mkconfig脚本的选项。由于我们传递给mkconfig任“-a”选项,因此while循环中的代码起作用, APPEND=yes。
           最后将BOARD_NAME的值设置为$1的值,在这里就是“ti8168_dvr”。

         2> 检查参数合法性
    [ $# -lt 4 ] && exit 1
    [ $# -gt 6 ] && exit 1

    if [ "${ARCH}" -a "${ARCH}" != "$2" ]; then
           echo "Failed: \$ARCH=${ARCH}, should be '$2' for ${BOARD_NAME}" 1>&2
           exit 1
    fi
           上面代码的作用是检查参数个数和参数是否正确,参数个数少于4个或多于6个都被认为是错误的。-lt 表示小于 -gt 表示大于

          3> 创建到目标板相关的目录的链接
    #
    # Create link to architecture specific headers
    #
    if [ "$SRCTREE" != "$OBJTREE" ] ; then         #若编译目标输出到外部目录,则下面的代码有效
           mkdir -p ${OBJTREE}/include
           mkdir -p ${OBJTREE}/include2
           cd ${OBJTREE}/include2
           rm -f asm
           ln -s ${SRCTREE}/include/asm-$2 asm
           LNPREFIX="http://www.cnblogs.com/include2/asm/"
           cd ../include
           rm -rf asm-$2
           rm -f asm
           mkdir asm-$2
           ln -s asm-$2 asm
    else              
           cd ./include
           rm -f asm
           ln -s asm-$2 asm
    fi
           若将目标文件设定为输出到源文件所在目录,则以上代码在include目录下建立了到asm-arm目录的符号链接asm。其中的ln -s asm-$2 asm即ln -s asm-arm asm

    rm -f asm-$2/arch
    if [ -z "$6" -o "$6" = "NULL" ] ; then
           ln -s ${LNPREFIX}arch-$3 asm-$2/arch
    else
           ln -s ${LNPREFIX}arch-$6 asm-$2/arch
    fi
           建立符号链接include/asm-arm/arch ,若$6(SOC)为空,则使其链接到include/asm-arm/arch-arm_cortexa8目录,否则就使其链接到include/asm-arm/arch-ti81xx目录即ln -s  arch-ti81xx  asm-arm/arch。(事实上include/asm-arm/arch-arm_cortexa8并不存在,因此$6是不能为空的,否则会编译失败)

    if [ "$2" = "arm" ] ; then
           rm -f asm-$2/proc
           ln -s ${LNPREFIX}proc-armv asm-$2/proc
    fi
           若目标板是arm架构,则上面的代码将建立符号连接include/asm-arm/proc,使其链接到目录proc-armv目录即ln -s  proc-armv  asm-arm/proc
           建立以上的链接的好处:编译U-Boot时直接进入链接文件指向的目录进行编译,而不必根据不同开发板来选择不同目录。

         4> 构建include/config.mk文件
    #
    # Create include file for Make
    #
    echo "ARCH   = $2" >  config.mk
    echo "CPU    = $3" >> config.mk
    echo "BOARD  = $4" >> config.mk

    [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
    [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk

           上面代码将会把如下内容写入文件inlcude/config.mk文件:
    ARCH   = arm
    CPU    = arm_cortexa8
    BOARD  = ti8168_dv
    VENDOR = ti
    SOC    = ti81xx

          5> 指定开发板代码所在目录
    # Assign board directory to BOARDIR variable
    if [ -z "$5" -o "$5" = "NULL" ] ; then
        BOARDDIR=$4
    else
        BOARDDIR=$5/$4
    fi
       以上代码指定board目录下的一个目录为当前开发板专有代码的目录。若$5(VENDOR)为空则BOARDDIR设置为$4(BOARD),否则设置为$5/$4(VENDOR/BOARD)。在这里由于$5不为空,因此BOARDDIR被设置为ti/ti8168_dvr。

         6> 构建include/config.h文件
    #
    # Create board specific header file
    #
    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
           echo "#define CONFIG_MK_${i} 1" >>config.h ;
    done

    cat << EOF >> config.h
    #define CONFIG_BOARDDIR board/$BOARDDIR
    #include <config_defaults.h>
    #include <configs/$1.h>
    #include <asm/config.h>
    EOF  #EOF本意是 End Of File,表明到了文件末尾

    exit 0
           这里的“cat << EOF >> config.h”表示将输入的内容追加到config.h中,直到出现“EOF”这样的标识为止。
           若APPEND为no,则创建新的include/config.h文件。若APPEND为yes,则将新的配置内容追加到include/config.h文件后面。由于APPEND的值保持“no”,因此config.h被创建了,并添加了如下的内容:
           /* Automatically generated - do not edit */
           #define CONFIG_BOARDDIR board/ti/ti8168_dvr
           #include <config_defaults.h>
           #include <configs/ti8168_dvr.h>
           #include <asm/config.h>

           下面总结命令make ti8168_dvr_config执行的结果(仅针对编译目标输出到源代码目录的情况):
           1)创建到目标板相关的文件的链接
           ln -s asm-arm asm
           ln -s arch-ti81xx asm-arm/arch
           ln -s proc-armv asm-arm/proc

           2) 创建include/config.mk文件,内容如下所示:
           ARCH   = arm
           CPU    = arm_cortexa8
           BOARD  = ti8168_dvr
           VENDOR = ti
           SOC    = ti81xx

           3)创建与目标板相关的文件include/config.h,如下所示:
           /* Automatically generated - do not edit */
           #define CONFIG_BOARDDIR board/ti/ti8168_dvr
           #include <config_defaults.h>
           #include <configs/ti8168_dvr.h>
           #include <asm/config.h>

        3. make u-boot.ti命令执行过程

           若没有执行过“make <board_name>_config”命令就直接执行“make all”命令则会出现如下的才错误信息,然后停止编译:
           System not configured - see README

           U-Boot是如何知道用户没有执行过“make <board_name>_config”命令的呢?阅读U-Boot源代码就可以发现了,Makefile中有如下代码:
    ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk)) # config.mk存在
    all:
    sinclude $(obj)include/autoconf.mk.dep
    sinclude $(obj)include/autoconf.mk
    … …
    else        # config.mk不存在
    … …
           @echo "System not configured - see README" >&2
           @ exit 1
    … …
    endif      # config.mk

           若include/config.mk 文件存在,则$(wildcard $(obj)include/config.mk) 命令执行的结果是“$(obj)include/config.mk”展开的字符串,否则结果为空。由于include/config.mk是“make <board_name>_config”命令执行过程生成的,若从没有执行过“make <board_name>_config”命令则include/config.mk必然不存在。因此Make就执行else分支的代码,在输出“System not configured - see README”的信息后就返回了。

           下面再来分析“make u-boot.ti”命令正常执行的过程,在Makefile中有如下代码:
           (1)include/autoconf.mk生成过程
    all:
    sinclude $(obj)include/autoconf.mk.dep
    sinclude $(obj)include/autoconf.mk

           include/autoconf.mk文件中是与开发板相关的一些宏定义,在Makefile执行过程中需要根据某些宏来确定执行哪些操作。下面简要分析include/autoconf.mk生成的过程,include/autoconf.mk生成的规则如下:
    $(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/autoconf.mk依赖于make <board_name>_config 命令生成的include/config.h。因此执行make <board_name>_config命令后再执行make all将更新include/autoconf.mk。
           编译选项“-dM”的作用是输出include/common.h中定义的所有宏。根据上面的规则,编译器提取include/common.h中定义的宏,然后输出给tools/scripts/define2mk.sed脚本处理,处理的结果就是include/autoconf.mk文件。其中tools/scripts/define2mk.sed,脚本的主要完成了在include/common.h中查找和处理以“CONFIG_”开头的宏定义的功能。
           include/common.h文件包含了include/config.h文件,而include/config.h文件又包含了config_defaults.h,configs//ti8168_dvr.h,asm/config.h文件。因此include/autoconf.mk实质上就是config_defaults.h,configs//ti8168_dvr.h,asm/config.h三个文件中“CONFIG_”开头的有效的宏定义的集合。

           下面接着分析Makefile的执行。

    # load ARCH, BOARD, and CPU configuration
    include $(obj)include/config.mk
    export    ARCH CPU BOARD VENDOR SOC
           将make ti8168_dvr_config命令生成的include/config.mk包含进来。

           # 若主机架构与开发板结构相同,就使用主机的编译器,而不是交叉编译器
    ifeq ($(HOSTARCH),$(ARCH))
    CROSS_COMPILE ?=
    endif
           若主机与目标机器体系架构相同,则使用gcc编译器而不是交叉编译器。

    # load other configuration
    include $(TOPDIR)/config.mk
           最后将U-Boot顶层目录下的config.mk文件包含进来,该文件包含了对编译的一些设置。下面对U-Boot顶层目录下的config.mk文件进行分析:

          (2)config.mk文件执行过程

           1> 设置obj与src
           在U-Boot顶层目录下的config.mk文件中有如下代码:
    ifneq ($(OBJTREE),$(SRCTREE))
    ifeq ($(CURDIR),$(SRCTREE))
    dir :=
    else
    dir := $(subst $(SRCTREE)/,,$(CURDIR))
    endif
    obj := $(if $(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/)
    src := $(if $(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/)

    $(shell mkdir -p $(obj))
    else
    obj :=
    src :=
    endif
           由于目标输出到源代码目录下,因此执行完上面的代码后,src和obj都是空。

           2> 设置编译选项
    PLATFORM_RELFLAGS =
    PLATFORM_CPPFLAGS =          #编译选项
    PLATFORM_LDFLAGS =           #连接选项
           用这3个变量表示交叉编译器的编译选项,在后面Make会检查交叉编译器支持的编译选项,然后将适当的选项添加到这3个变量中。

    #
    # Option checker (courtesy linux kernel) to ensure
    # only supported compiler options are used
    #
    cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \
                  > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)
           变量CC和CFLAGS在后面的代码定义为延时变量,其中的CC即arm-linux-gcc。函数cc-option用于检查编译器CC是否支持某选项。将2个选项作为参数传递给cc-option函数,该函数调用CC编译器检查参数1是否支持,若支持则函数返回参数1,否则返回参数2 (因此CC编译器必须支持参数1或参数2,若两个都不支持则会编译出错)。可以像下面这样调用cc-option函数,并将支持的选项添加到FLAGS中:FLAGS +=$(call cc-option,option1,option2)

            3> 指定交叉编译工具
    #
    # Include the make variables (CC, etc...)
    #
    AS  = $(CROSS_COMPILE)as
    LD  = $(CROSS_COMPILE)ld
    CC  = $(CROSS_COMPILE)gcc
    CPP  = $(CC) -E
    AR = $(CROSS_COMPILE)ar
    NM = $(CROSS_COMPILE)nm
    LDR      = $(CROSS_COMPILE)ldr
    STRIP   = $(CROSS_COMPILE)strip
    OBJCOPY = $(CROSS_COMPILE)objcopy
    OBJDUMP = $(CROSS_COMPILE)objdump
    RANLIB      = $(CROSS_COMPILE)RANLIB

           对于arm开发板,其中的CROSS_COMPILE在lib_arm/config.mk文件中定义:
    CROSS_COMPILE ?= arm-linux-
           因此以上代码指定了使用前缀为“arm-linux-”的编译工具,即arm-linux-gcc,arm-linux-ld等等。

           4> 包含与开发板相关的配置文件

    # Load generated board configuration
    sinclude $(OBJTREE)/include/autoconf.mk

    ifdef      ARCH
    sinclude $(TOPDIR)/lib_$(ARCH)/config.mk   # include architecture dependend rules
    endif
           $(ARCH)的值是“arm”,因此将“lib_arm/config.mk”包含进来。lib_arm/config.mk脚本指定了交叉编译器,添加了一些跟CPU架构相关的编译选项,最后还指定了cpu/arm_cortexa8/u-boot.lds为U-Boot的连接脚本。

    ifdef      CPU
    sinclude $(TOPDIR)/cpu/$(CPU)/config.mk             # include  CPU specific rules
    endif
           $(CPU)的值是“arm920t”,因此将“cpu/arm_cortexa8/config.mk”包含进来。这个脚本主要设定了跟arm_cortexa8处理器相关的编译选项。

    ifdef      SOC
    sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk       # include  SoC  specific rules
    endif
           $(SOC)的值是s3c24x0,因此Make程序尝试将cpu/arm920t/ti81xx/config.mk包含进来,而这个文件并不存在,但是由于用的是“sinclude”命令,所以并不会报错。

    ifdef      VENDOR
    BOARDDIR = $(VENDOR)/$(BOARD)
    else
    BOARDDIR = $(BOARD)
    endif
           $(BOARD)的值是ti8168_dvr,VENDOR的值是ti,因此BOARDDIR的值是ti/ti8168_dvr, BOARDDIR变量表示开发板特有的代码所在的目录。

    ifdef      BOARD
    sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk   # include board specific rules
    endif
           Make将“board/ti/ti8168_dvr/config.mk”包含进来。该脚本只有如下的一行代码:

    TEXT_BASE = 0x80700000
           U-Boot编译时将使用TEXT_BASE作为代码段连接的起始地址。

    LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS)

    ifneq ($(TEXT_BASE),)
    LDFLAGS += -Ttext $(TEXT_BASE)
    endif
           执行完以上代码后,LDFLAGS中包含了“-Bstatic -T u-boot.lds ”和“-Ttext 0x80700000”的字样。

           5>  指定隐含的编译规则
    # Allow boards to use custom optimize flags on a per dir/file basis
    BCURDIR := $(notdir $(CURDIR))
    $(obj)%.s:     %.S
           $(CPP) $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $<
    $(obj)%.o:    %.S
           $(CC)  $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $< -c
    $(obj)%.o:    %.c
           $(CC)  $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $< -c
    $(obj)%.i:     %.c
           $(CPP) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $< -c
    $(obj)%.s:     %.c
           $(CC)  $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $< -c -S

           例如:根据以上的定义,以“.s”结尾的目标文件将根据第一条规则由同名但后缀为“.S”的源文件来生成,若不存在“.S”结尾的同名文件则根据最后一条规则由同名的“.c”文件生成。

           (3) 下面回来接着分析Makefile的内容:
    # U-Boot objects....order is important (i.e. start must be first)
    OBJS  = cpu/$(CPU)/start.o
    LIBS += cpu/$(CPU)/lib$(CPU).a
    ifdef SOC
    LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
    endif

    ifeq ($(CPU),ixp)
    LIBS += cpu/ixp/npe/libnpe.a
    endif

    LIBS += lib_$(ARCH)/lib$(ARCH).a
    LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
           fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a fs/yaffs2/libyaffs2.a \
           fs/ubifs/libubifs.a
    … …
    LIBS += common/libcommon.a
    LIBS += libfdt/libfdt.a
    LIBS += api/libapi.a
    LIBS += post/libpost.a
    LIBS := $(addprefix $(obj),$(LIBS))
           LIBS变量指明了U-Boot需要的库文件,包括平台/开发板相关的目录、通用目录下相应的库,都通过相应的子目录编译得到的。

           对于ti8168_dvr开发板,以上跟平台相关的有以下几个:
    cpu/$(CPU)/start.o
    board/$(VENDOR)/common/lib$(VENDOR).a
    cpu/$(CPU)/lib$(CPU).a
    cpu/$(CPU)/$(SOC)/lib$(SOC).a
    lib_$(ARCH)/lib$(ARCH).a
           其余都是与平台无关的。

    ifeq ($(CONFIG_NAND_U_BOOT),y)
    NAND_SPL = nand_spl
    U_BOOT_NAND = $(obj)u-boot-nand.bin
    endif

    ifeq ($(CONFIG_ONENAND_U_BOOT),y)
    ONENAND_IPL = onenand_ipl
    U_BOOT_ONENAND = $(obj)u-boot-onenand.bin
    ONENAND_BIN ?= $(obj)onenand_ipl/onenand-ipl-2k.bin
    endif
           对于有的开发板,U-Boot支持在NAND Flash启动,这些开发板的配置文件定义了CONFIG_NAND_U_BOOT,CONFIG_ONENAND_U_BOOT。对于ti8168_dvr,U-Boot原始代码支持NAND Flash启动,因此定义CONFIG_NAND_U_BOOT这个宏。
    ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)

    all:         $(ALL)
           其中U_BOOT_NAND与U_BOOT_ONENAND 为空,而u-boot.srec,u-boot.bin,System.map都依赖与u-boot。因此执行“make all”命令将生成u-boot,u-boot.srec,u-boot.bin,System.map 。其中u-boot是ELF文件,u-boot.srec是Motorola S-Record format文件,System.map 是U-Boot的符号表,u-boot.bin是最终烧写到开发板的二进制可执行的文件。

    $(obj)u-boot.ti:       $(obj)u-boot.bin
                    $(obj)tools/mkimage -T tiimage \
                    -e $(TI_LOAD_ADDR) -n $(TI_DEVICE) -d $< $(obj)$(TI_IMAGE)
           TI8168的u-boot.ti依赖u-boot.bin。 

           下面再来分析u-boot.bin文件生成的过程。ELF格式“u-boot”文件生成规则如下:
    $(obj)u-boot:       depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
                  $(GEN_UBOOT)
    ifeq ($(CONFIG_KALLSYMS),y)
                  smap=`$(call SYSTEM_MAP,u-boot) | \
                         awk '$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\\\000"}'` ; \
                  $(CC) $(CFLAGS) -DSYSTEM_MAP="\"$${smap}\"" \
                        -c common/system_map.c -o $(obj)common/system_map.o
                  $(GEN_UBOOT) $(obj)common/system_map.o
    endif
           这里生成的$(obj)u-boot目标就是ELF格式的U-Boot文件了。由于CONFIG_KALLSYMS未定义,因此ifeq ($(CONFIG_KALLSYMS),y)与endif间的代码不起作用。
           其中depend,$(SUBDIRS),$(OBJS),$(LIBBOARD),$(LIBS),$(LDSCRIPT), $(obj)u-boot.lds是$(obj)u-boot的依赖,而$(GEN_UBOOT)编译命令。   

           下面分析$(obj)u-boot的各个依赖:

           1依赖目标depend
    # Explicitly make _depend in subdirs containing multiple targets to prevent
    # parallel sub-makes creating .depend files simultaneously.
    depend dep: $(TIMESTAMP_FILE) $(VERSION_FILE) $(obj)include/autoconf.mk
                  for dir in $(SUBDIRS) cpu/$(CPU) $(dir $(LDSCRIPT)) ; do \                     $(MAKE) -C $$dir _depend ; done

           对于$(SUBDIRS),cpu/$(CPU),$(dir $(LDSCRIPT))中的每个元素都进入该目录执行“make _depend”,生成各个子目录的.depend文件,.depend列出每个目标文件的依赖文件。

           2 依赖SUBDIRS
    SUBDIRS    = tools \
             examples/standalone \
             examples/api
    $(SUBDIRS):     depend
           $(MAKE) -C $@ all
           执行tools ,examples/standalone ,examples/api目录下的Makefile。

           3OBJS
           OBJS的值是“cpu/arm_cortexa8/start.o”。它使用如下代码编译得到:
    $(OBJS):      depend
           $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
           以上规则表明,对于OBJS包含的每个成员,都进入cpu/$(CPU)目录(即cpu/arm_cortexa8)编译它们。

           4LIBBOARD
    LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
    LIBBOARD := $(addprefix $(obj),$(LIBBOARD))
    … …
    $(LIBBOARD): depend $(LIBS)
           $(MAKE) -C $(dir $(subst $(obj),,$@))
           这里LIBBOARD的值是 $(obj)board/ti/ti8168_dvr/libti8168_dvr.a。make执行board/ti/ti8168_dvr/目录下的Makefile,生成libti8168_dvr.a 。

           5LIBS
           LIBS变量中的每个元素使用如下的规则编译得到:
    $(LIBS):       depend $(SUBDIRS)
                  $(MAKE) -C $(dir $(subst $(obj),,$@))
           上面的规则表明,对于LIBS中的每个成员,都进入相应的子目录执行“make”命令编译它们。例如对于LIBS中的“common/libcommon.a”成员,程序将进入common目录执行Makefile,生成libcommon.a 。

           6LDSCRIPT
    LDSCRIPT := $(SRCTREE)/cpu/$(CPU)/u-boot.lds
    … …
    $(LDSCRIPT):   depend
                  $(MAKE) -C $(dir $@) $(notdir $@)
           “$(MAKE) -C $(dir $@) $(notdir $@)”命令经过变量替换后就是“make -C cpu/arm_cortexa8/  u-boot.lds”。也就是转到cpu/arm_cortexa8/目录下,执行“make u-boot.lds”命令。

           7$(obj)u-boot.lds
    $(obj)u-boot.lds: $(LDSCRIPT)
                  $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@

    /* 输出为ELF文件,小端方式, */
    OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
    OUTPUT_ARCH(arm)  
    ENTRY(_start)
    SECTIONS
    {
           . = 0x00000000;

           . = ALIGN(4);
           .text :
           {
             /* cpu/arm920t/start.o放在最前面,保证最先执行的是start.o */
                    arch/arm/cpu/arm_cortexa8/start.o  (.text)
                    arch/arm/cpu/arm_cortexa8/ti81xx/lowlevel_init.o (.text)
                    /* 其他文件的代码段 */
                    *(.text)

                    /*以下2个文件必须放在前4K,因此也放在前面,其中board/samsung/mini2440/lowlevel_init.o 包含内存初始化所需代码,而 board/samsung/mini2440/nand_read.o 包含U-Boot从NAND Flash搬运自身的代码 */
                    board/samsung/mini2440/lowlevel_init.o (.text)
                    board/samsung/mini2440/nand_read.o (.text)
           }

           /* 只读数据段 */
           . = ALIGN(4);
           .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

           /* 代码段 */
           . = ALIGN(4);
           .data : { *(.data) }

           /* u-boot自定义的got段 */
           . = ALIGN(4);
           .got : { *(.got) }

           __u_boot_cmd_start = .;          /*将 __u_boot_cmd_start指定为当前地址 */
           .u_boot_cmd : { *(.u_boot_cmd) }             /* 存放所有U-Boot命令对应的cmd_tbl_t结构体 */
           __u_boot_cmd_end = .;           /*  将__u_boot_cmd_end指定为当前地址  */

           /* bss段 */
           . = ALIGN(4);
           __bss_start = .;
           .bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
           _end = .;              /*  将_end指定为当前地址  */

    }
           u-boot.lds实质上是U-Boot连接脚本。对于生成的U-Boot编译生成的“u-boot”文件,可以使用objdump命令可以查看它的分段信息:

    $arm-none-linux-gnueabi-objdump -x u-boot | more

           部分输出信息如下:
    u-boot:     file format elf32-littlearm
    u-boot
    architecture: arm, flags 0x00000112:
    EXEC_P, HAS_SYMS, D_PAGED
    start address 0x80700000

    Program Header:
        LOAD off    0x00008000 vaddr 0x80700000 paddr 0x80700000 align 2**15
             filesz 0x00030a94 memsz 0x000686d8 flags rwx
       STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
             filesz 0x00000000 memsz 0x00000000 flags rwx
    private flags = 5000002: [Version5 EABI] [has entry point]

    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .text         00024568  80700000  80700000  00008000  2**5
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      1 .rodata       000013b0  80724568  80724568  0002c568  2**3
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      2 .rodata.str1.1 000064f5  80725918  80725918  0002d918  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      3 .data         00004744  8072be10  8072be10  00033e10  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      4 .u_boot_cmd   00000540  80730554  80730554  00038554  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      5 .bss          00037c40  80730a98  80730a98  00038a94  2**3
                      ALLOC
      6 .ARM.attributes 00000029  00000000  00000000  00038a94  2**0
                      CONTENTS, READONLY
      7 .comment      00000f1e  00000000  00000000  00038abd  2**0
                      CONTENTS, READONLY
      8 .debug_frame  00000040  00000000  00000000  000399dc  2**2
                      CONTENTS, READONLY, DEBUGGING
    SYMBOL TABLE:
    80700000 l    d  .text 00000000 .text
    80724568 l    d  .rodata 00000000 .rodata
    80725918 l    d  .rodata.str1.1 00000000 .rodata.str1.1
    8072be10 l    d  .data 00000000 .data
    ----------------------------------------------------------------------------------------------
    u-boot:     file format elf32-little
    u-boot
    architecture: UNKNOWN!, flags 0x00000112:
    EXEC_P, HAS_SYMS, D_PAGED
    start address 0x33f80000
    Program Header:
        LOAD off    0x00008000 vaddr 0x33f80000 paddr 0x33f80000 align 2**15
             filesz 0x0002f99c memsz 0x00072c94 flags rwx
       STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
             filesz 0x00000000 memsz 0x00000000 flags rwx
    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .text         00024f50  33f80000  33f80000  00008000  2**5
                      CONTENTS, ALLOC, LOAD, READONLY, CODE
      1 .rodata       00008b78  33fa4f50  33fa4f50  0002cf50  2**3
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      2 .data         00001964  33fadac8  33fadac8  00035ac8  2**2
                     CONTENTS, ALLOC, LOAD, DATA
      3 .u_boot_cmd   00000570  33faf42c  33faff2c  0003742c  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      4 .bss          00043294  33fafa00  33fafa00  0003799c  2**8
                      ALLOC
    … …
    -------------------------------------------------------------------------------------------------
           u-boot.lds还跟U-Boot启动阶段复制代码到RAM空间的过程以及U-Boot命令执行过程密切相关,具体请结合U-Boot源代码理解。

           编译命令GEN_UBOOT
    GEN_UBOOT = \
                  UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
                  sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
                  cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
                         --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
                         -Map u-boot.map -o u-boot
           以上命令使用$(LDFLAGS)作为连接脚本,最终生成“u-boot”文件。

    u-boot.bin文件生成过程
           生成u-boot.bin文件的规则如下:
    $(obj)u-boot.bin: $(obj)u-boot
                  $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
           从U-Boot编译输出信息中可以知道上面的命令实质上展开为:
           arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin
           编译命令中的“-O binary”选项指定了输出的文件为二进制文件。而“--gap-fill=0xff”选项指定使用“0xff”填充段与段间的空闲区域。这条编译命令实现了ELF格式的U-Boot文件到BIN格式的转换。

    u-boot.map文件的生成
           u-boot.map是U-Boot的符号表,它包含了U-Boot的全局变量和函数的地址信息。将u-boot.map生成的规则如下:
    GEN_UBOOT = \
                    UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
                    sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
                    cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
                            --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
                            -Map u-boot.map -o u-boot
    $vim u-boot.map
    Memory Configuration

    Name             Origin             Length             Attributes
    *default*        0x00000000         0xffffffff

    Linker script and memory map

                    0x00000000                . = 0x0
                    0x00000000                . = ALIGN (0x4)

    .text         0x80700000    0x24568
     arch/arm/cpu/arm_cortexa8/start.o(.text)
     .text        0x80700000      0x3a0 arch/arm/cpu/arm_cortexa8/start.o
                    0x80700040                _end_vect
                    0x80700048                _bss_start
                    0x8070004c                _bss_end
                    0x80700044                _armboot_start
                    0x80700000                _start
     arch/arm/cpu/arm_cortexa8/ti81xx/lowlevel_init.o(.text)
     .text        0x807003a0       0x5c arch/arm/cpu/arm_cortexa8/ti81xx/lowlevel_init.o
                    0x807003a4                lowlevel_init
     *(.text)
     .text        0x807003fc      0x31c arch/arm/lib/libarm.a(board.o)
                    0x807003fc                hang
                    0x80700504                start_armboot
     .text        0x80700718      0x264 arch/arm/lib/libarm.a(interrupts.o)
                    0x807008a4                do_fiq
                    0x80700958                do_undefined_instruction
                    0x80700724                show_regs
                    0x80700880                do_irq
                    0x80700864                bad_mode
                    0x80700910                do_prefetch_abort
                    0x8070071c                disable_interrupts
                    0x807008c8                do_not_used
                    0x807008ec                do_data_abort
                    0x80700934                do_software_interrupt

    --------------------------------------------------------------------------------------------------------
    System.map文件的生成
           System.map是U-Boot的符号表,它包含了U-Boot的全局变量和函数的地址信息。将System.map生成的规则如下:
    SYSTEM_MAP = \
                  $(NM) $1 | \
                  grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
                  LC_ALL=C sort
    $(obj)System.map:     $(obj)u-boot
                  @$(call SYSTEM_MAP,$<) > $(obj)System.map
    arm-linux-nm u-boot | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | LC_ALL=C sort  > System.map
           也就是将arm-linux-nm命令查看u-boot的输出信息经过过滤和排序后输出到System.map。为了了解System.map文件的作用,打开System.map:

    33f80000 T _start
    33f80020 t _undefined_instruction
    33f80024 t _software_interrupt
    33f80028 t _prefetch_abort
    33f8002c t _data_abort
    33f80030 t _not_used
    33f80034 t _irq
    33f80038 t _fiq
    33f80040 t _TEXT_BASE
    33f80044 T _armboot_start
    33f80048 T _bss_start
    33f8004c T _bss_end
    … …
           System.map表示的是地址标号到该标号表示的地址的一个映射关系。System.map每一行的格式都是“addr type name”,addr是标号对应的地址值,name是标号名,type表示标号的类型。
         U-Boot的编译和运行并不一定要生成System.map,这个文件主要是提供给用户或外部程序调试时使用的。

    三. U-Boot 启动全过程

    嵌入式QQ交流群:127085086
  • 相关阅读:
    Using Resource File on DotNet
    C++/CLI VS CSharp
    JIT VS NGen
    [Tip: disable vc intellisense]VS2008 VC Intelisense issue
    UVa 10891 Game of Sum(经典博弈区间DP)
    UVa 10723 Cyborg Genes(LCS变种)
    UVa 607 Scheduling Lectures(简单DP)
    UVa 10401 Injured Queen Problem(简单DP)
    UVa 10313 Pay the Price(类似数字分解DP)
    UVa 10635 Prince and Princess(LCS N*logN)
  • 原文地址:https://www.cnblogs.com/cslunatic/p/2982302.html
Copyright © 2011-2022 走看看