zoukankan      html  css  js  c++  java
  • uboot的配置、编译及链接

      第一次写技术博客,还有些兴奋呢。我是CrazyCatJack,大家可以叫我CCJ或者疯猫。我即将成为一名嵌入式Linux的驱动工程师,现在还是一枚大四狗,呼呼~大学期间做了一些项目和比赛,都是基于32位的MCU(例如STM32、Freescale K60),这些呢都是根据网上的视频,PDF自学的。现在想更进一步,学习一下嵌入式Linux、UCOS-II等嵌入式系统。因为给板子加系统是一个必然趋势,控制会越来越复杂,内容也越来越多的。有一个系统统筹管理是非常棒的选择。好了,废话少说,今天开始我的第一篇技术博客:u-boot的配置、编译和链接^_^

      看到有的小伙伴好像不太了解u-boot,因此我简单介绍一下,u-boot是一种bootloader。在嵌入式开发过程中,bootloader用于配置平台/开发板,最主要的功能就是从flash中读出内核,然后启动内核。我们平时在PC上使用windows系统也是类似的。PC上是BIOS对硬件配置,从硬盘读出windows内核,然后启动windows内核,进入windows系统。相对的,嵌入式平台是bootloader对硬件配置,读出并启动嵌入式系统内核(例如Linux内核),进入你所用的嵌入式系统。

    1.u-boot的配置

      首先,我们要想了解u-boot,最好是从Makefile开始看,就能知道u-boot要执行的操作了。就像C语言中的main函数一样。在Makefile文件里,和配置相关的语句如下:

    OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
    SRCTREE        := $(CURDIR)
    TOPDIR        := $(SRCTREE)
    LNDIR        := $(OBJTREE)
    export    TOPDIR SRCTREE OBJTREE
    
    MKCONFIG    := $(SRCTREE)/mkconfig
    export MKCONFIG
    
    
    CCJ_config    :    unconfig
        @$(MKCONFIG) $(@:_config=) arm arm920t CCJ NULL s3c24x0

      根据Makefile中ReadMe文件中的描述,我们要配置u-boot,就要执行"make <board name>_config"这条指令。所以从Makefile中要查找一下你所用的开发板型号的相关配置信息。一般厂家都会给你配置好u-boot,你也可以自己写。在这里就假设我们使用的开发板名字为“CCJ”。分析最后这条语句:

    @$(MKCONFIG) $(@:_config=) arm arm920t CCJ NULL s3c24x0

      第一个MKCONFIG在Makefile文件中已有定义,可以看到MKCONFIG:=$(SRCTREE)/mkconfig,也就是说我们要用源代码树下的mkconfig替换这条语句中的MKCONFIG。

      第二个$(@的含义是用前面板子的名字替换掉后面的内容,而且不要“_config”,也就是说替换之后为CCJ。

      现在经过替换,这条语句变成了:

    ./mkconfig CCJ arm arm920t CCJ NULL s3c24x0

      后面的5个参数其实分别代表着你现在用到的硬件平台的架构、CPU、开发板型号名称、供应商、SOC名称。这个我们后面还会有讲解。所以能够看出这里我用到的是ARM架构,CPU为ARM920T,开发板名称为CCJ,SOC为S3C24X0。

      那现在我们已经分析了这条配置语句所代表的含义,但它具体是怎么工作的呢?是怎样将我们写好的这些硬件相关的信息进行具体配置赋值的呢?其实这里我们调用了根目录下的./mkconfig这一文件,就像C语言中的函数调用一样,调用mkconfig这个文件,并传入参数(arch,CPU,boardname,vendor,soc)为(arm arm920t CCJ NULL s3c24x0),来执行相应的操作。这么说大家应该就理解了吧。所以接下来我们看mkconfig文件中是怎样使用这些参数进行配置的。

      现在我们打开mkconfig文件,先分析第一段代码:

    APPEND=no    # Default: Create new config file
    BOARD_NAME=""    # Name to print in make output
    
    while [ $# -gt 0 ] ; do
        case "$1" in
        --) shift ; break ;;
        -a) shift ; APPEND=yes ;;
        -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
        *)  break ;;
        esac
    done
    
    [ "${BOARD_NAME}" ] || BOARD_NAME="$1"
    
    [ $# -lt 4 ] && exit 1
    [ $# -gt 6 ] && exit 1
    
    echo "Configuring for ${BOARD_NAME} board..."

      首先我们看到两个参数的赋值,APPEND为“no”,这个参数我们后面会用到,BOARD_NAME为“”,非空。这里大家会看到很多“$1,$2,$3...”等等,大家不要晕,其实很简单的,他们都分别对应我们刚刚所说传入的参数:

    Makefile命令:             ./mkconfig CCJ arm arm920t CCJ NULL s3c24x0
    mkconfig对应符号表示:           $0    $1   $2   $3     $4  $5     $6

      所以这里用到的$1其实就是"CCJ",$2就是"arm",以此类推。进入while语句,由于这句Makefile命令中没有“--”,“-a”,“-n”。所以这个while其实什么都没干。如果大家在写Makefile命令的时候,用到了“--”,“-a”,“-n”,则这里会改变APPEND和BOARD_NAME两个参数的值。我们这里没有改变。

      接下来看下一句话 [ "${BOARD_NAME}" ] || BOARD_NAME="$1" 这句话是说如果BOARD_NAME不为空,则BOARD_NAME等于$1,即“CCJ”。然后是 echo "Configuring for ${BOARD_NAME} board..." 这句话的含义就是打印“Configuring for CCJ board”,所以在我们执行“make CCJ_config”命令时,一定会打印出这句话来。其中CCJ是你开发板的型号。

      

    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="../../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
    
    rm -f asm-$2/arch

      接着往下看,上面这段代码主要完成链接工作。首先if判断 $SRCTREE 是否等于 $OBJTREE,这里要注意了,这两个参数是我们在Makefile文件中定义的。看我发的第一个代码片中就有对它们的赋值。

    OBJTREE        := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
    SRCTREE        := $(CURDIR)

      第一条语句的意思是如果我们定义了BUILD_DIR,则OBJTREE等于 $BUILD_DIR,否则等于 $CURDIR。那么这里我没有定义BUILD_DIR,所以OBJTREE= $CURDIR。所以现在OBJTREE=SRCTREE,则不执行if语句中的内容,而转去执行else分支的内容。

      首先进入./include目录下,删除原来生成的asm文件,重新建立asm文件,并链接到asm-$2文件。经过前面的讲解,相信你已经知道asm-$2经过转换,得到asm-arm。也就是说现在asm->asm-arm。最后删除asm-arm/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
    
    if [ "$2" = "arm" ] ; then
        rm -f asm-$2/proc
        ln -s ${LNPREFIX}proc-armv asm-$2/proc
    fi

      接着往下分析,如果$6不为空,或$6等于NULL,则执行if内的语句,我们$6=s3c24x0。所以执行分支语句,建立文件asm-am/arch,并指向arch-sac24x0。

      第二个if中,因为$2等于arm,所以执行语句,删除asm-arm/proc,建立文件asm-arm/proc,并指向proc-armv。

    #
    # 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
    
    #
    # 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
    echo "#include <configs/$1.h>" >>config.h
    
    exit 0

      现在是不是有点累了?我们开始分析mkconfig的最后一大段^_^。坚持就是胜利!!!

      根据注释,第一段代码的作用是创建包含的文件,为配置做准备。这里我们主要是创建config.mk文件。这里说明一下">"表示创建某文件,">>"表示将内容添加到某文件。所以接下来几行的含义就分别是:创建config.mk文件,并添加"ARCH  = arm",添加"CPU  = arm920t",添加"BOARD  = CCJ"语句。$5为NULL,不执行语句。$6为s3c24x0,添加"SOC  =s3c24x0"所以,如果你尝试着执行config.mk文件一定会出现如下信息:

    ARCH  = arm
    CPU  = arm920t
    BOARD  = CCJ
    SOC = s3c24x0

      接下来看第二段代码,根据作者提供的注释我们知道要创建开发板指定头文件了。根绝最开始mkconfig中我们定义的APPEND值,执行语句,由于这里我定义的是APPEND=no,所以执行else分支,创建config.h头文件,并添加  /* Automatically generated - do not edit */    #include <configs/$1.h>  语句到config.h头文件中。 最后执行 exit 0 退出mkconfig文件。

     

    2.u-boot的编译

      接下来我们要做的就是编译了,即make。我们回到Makefile文件,看与你所使用的硬件平台相关的代码,这里我使用的是ARM9。根据注释,可以得知这段代码用于载入架构,开发板信息,CPU的配置。

    # load ARCH, BOARD, and CPU configuration
    include $(OBJTREE)/include/config.mk
    export    ARCH CPU BOARD VENDOR SOC
    
    ifndef CROSS_COMPILE
    ifeq ($(HOSTARCH),ppc)
    CROSS_COMPILE =
    else
    ifeq ($(ARCH),ppc)
    CROSS_COMPILE = powerpc-linux-
    endif
    ifeq ($(ARCH),arm)
    CROSS_COMPILE = arm-linux-
    endif
    ifeq ($(ARCH),i386)
    ifeq ($(HOSTARCH),i386)
    CROSS_COMPILE =
    else
    CROSS_COMPILE = i386-linux-
    endif
    endif

      在包含了我们之前所配置的config.mk文件后,Makefile代码就会根据我们配置的具体架构信息选择合适的交叉编译,很明显,因为我的是ARM架构,这里CROSS_COMPILE=arm-linux-。

    export    CROSS_COMPILE
    
    # load other configuration
    include $(TOPDIR)/config.mk

      接着往下看,最下面我们包含了顶层目录下的config.mk文件。其实这里我发现有一个问题,就是之前我们不是也生成了一个config.mk吗?这其实是两个文件。之前那个完完全全是我们自己创建的config.mk,它的作用就是为了给接下来包含的这个原作者书写的config.mk传值,传递CPU、SOC、ARCH、BOARD、VENDOR的参数值。如果你手头上有下载好的u-boot源文件,打开u-boot文件夹,你就会看到一个config.mk。打开它就明白我刚刚所说的,传递的参数进入这个config,mk配置交叉编译选项、选择结构依赖规则。如下图:

    ifeq ($(ARCH),arm)
    ifeq ($(CROSS_COMPILE),powerpc-netbsd-)
    PLATFORM_CPPFLAGS+= -D__ARM__
    endif
    ifeq ($(CROSS_COMPILE),powerpc-openbsd-)
    PLATFORM_CPPFLAGS+= -D__ARM__
    endif
    endif
    
    ifeq ($(ARCH),blackfin)
    PLATFORM_CPPFLAGS+= -D__BLACKFIN__ -mno-underscore
    endif
    
    ifdef    ARCH
    sinclude $(TOPDIR)/$(ARCH)_config.mk    # include architecture dependend rules
    endif
    ifdef    CPU
    sinclude $(TOPDIR)/cpu/$(CPU)/config.mk    # include  CPU    specific rules
    endif
    ifdef    SOC
    sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk    # include  SoC    specific rules
    endif

      在这个文件中,也有调用结构依赖规则,就是你所用的硬件架构对应的结构规则config文件,这里不再详述。可以自己打开看看。

      

      

      接下来接着看Makefile的命令:

      

    # U-Boot objects....order is important (i.e. start must be first)
    
    OBJS  = cpu/$(CPU)/start.o
    OBJS := $(addprefix $(obj),$(OBJS))
    
    LIBS  = lib_generic/libgeneric.a
    LIBS += board/$(BOARDDIR)/lib$(BOARD).a
    LIBS += cpu/$(CPU)/lib$(CPU).a
    ifdef SOC
    LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
    endif
    LIBS += lib_$(ARCH)/lib$(ARCH).a
    LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a 

      首先是目标文件start.o,然后是给LIBS变量指定平台/开发板相关的库。这些库是由各个模块自身编译生成的。(.a文件)

    $(OBJS):
        echo $(OBJS)    
            $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
    
    $(LIBS):
            $(MAKE) -C $(dir $(subst $(obj),,$@))
    
    usb:
        $(MAKE) -C drivers/usb
    
    $(SUBDIRS):
            $(MAKE) -C $@ all

      这些.o文件和.a文件就是由上面的语句编译生成的。其中库文件是由每个模块子目录自己make后生成的。编译过程就此结束。

    3.u-boot的链接

      通过连接,我们可以得到最终的u-boot的hex文件,srec文件,二进制文件,img文件。

    ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
    
    all:        $(ALL)
    
    $(obj)u-boot.hex:    $(obj)u-boot
            $(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
    
    $(obj)u-boot.srec:    $(obj)u-boot
            $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
    
    $(obj)u-boot.bin:    $(obj)u-boot
            $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
    
    $(obj)u-boot.img:    $(obj)u-boot.bin
            ./tools/mkimage -A $(ARCH) -T firmware -C none \
            -a $(TEXT_BASE) -e 0 \
            -n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
                sed -e 's/"[     ]*$$/ for $(BOARD) board"/') \
            -d $< $@
    
    $(obj)u-boot.dis:    $(obj)u-boot
            $(OBJDUMP) -d $< > $@
    
    $(obj)u-boot:        depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
            UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*\(__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确定了程序地址和代码段、数据段的排列位置。下图是board/CCJ/u-boot.lds文件

    SECTIONS
    {
        . = 0x00000000;
    
        . = ALIGN(4);
        .text      :
        {
          cpu/arm920t/start.o    (.text)
              board/CCJ/boot_init.o (.text)
          *(.text)
        }
    
        . = ALIGN(4);
        .rodata : { *(.rodata) }
    
        . = ALIGN(4);
        .data : { *(.data) }
    
        . = ALIGN(4);
        .got : { *(.got) }
    
        . = .;
        __u_boot_cmd_start = .;
        .u_boot_cmd : { *(.u_boot_cmd) }
        __u_boot_cmd_end = .;
    
        . = ALIGN(4);
        __bss_start = .;
        .bss : { *(.bss) }
        _end = .;
    }

      首先定义了基地址0x00000000,这个地址并不是我们实际存储程序的地址,而是在board目录下我们还要定义一个config.mk,包含内容TEXT_BASE = 0x33F80000。这个地址是我们自己定义的,基地址+偏移地址,才是u-boot程序最终存储的地址(0+0x33F80000=0x33F80000)。首先存储start.o的代码段,初始化代码段,然后是只读数据段,数据段。就此,u-boot链接完毕。

      

      第一次写博客,感觉真挺累的,但是一写就停不下来。还是很高兴的,将自己学到的知识分享给大家,其实嵌入式的学习路程真的很长,需要不断地努力,还要有兴趣。我很喜欢乔布斯说的那句话:“人类追求极致,并分享给同类,然后才能共同进步。”在实际的编程、比赛、项目中,我们都需要合作和互相学习,尤其是不同领域的人们合作更会创造出令人惊叹的事物,会让我们感叹人类创造力的同时让生活更美好。这大概也是GPL协议的初衷吧,开源、但又尊重人们的自由和所属权。

      今天写的博客都是基于这段时间的学习。希望能帮到大家。有写的不对的地方也希望大家给我指正,我会非常高兴,因为我喜欢交流^_^。

      最后谢谢u-boot的作者,能够将u-boot开源,并提供下载以供全世界的人们学习使用。我尊重u-boot的作者的版权:Wolfgang Denk, DENX Software Engineering, wd@denx.de.

                                        

                                        CCJ

                                   2016-11-12 23:43:13

      

      

      

      

  • 相关阅读:
    2.Android之按钮Button和编辑框EditText学习
    《DSP using MATLAB》Problem 3.8
    《DSP using MATLAB》Problem 3.7
    《DSP using MATLAB》Problem 3.6
    《DSP using MATLAB》Problem 3.5
    《DSP using MATLAB》Problem 3.4
    《DSP using MATLAB》Problem 3.3
    《DSP using MATLAB》Problem 3.2
    《DSP using MATLAB》Problem 3.1
    《DSP using MATLAB》Problem 2.20
  • 原文地址:https://www.cnblogs.com/CrazyCatJack/p/6056564.html
Copyright © 2011-2022 走看看