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

      

      

      

      

  • 相关阅读:
    Haskell 差点儿无痛苦上手指南
    蛋疼的Apple IOS Push通知协议
    css概述
    数据挖掘十大经典算法
    序员工作究竟能干多久?程序猿的前途怎样?
    怎样将程序猿写出来的程序打包成安装包(最简单的)
    Denny Zhang:一辈子做一个自由职业者
    自己动手写操作系统--个人实践
    结构体数组
    英雄会挑战失败求原因
  • 原文地址:https://www.cnblogs.com/CrazyCatJack/p/6056564.html
Copyright © 2011-2022 走看看