分享

UBoot的配置编译过程

 guitarhua 2015-11-03

UBoot的配置编译过程


UBoot,全称 Universal BootLoader,官方名称为 DENX U-Boot 或者 Das U-Boot,是嵌入式系统业内用的最多的BootLoader。之所以能在名字开头冠以 Universal 一字,原因之一,是因为它能支持众多的CPU体系结构,包括x86,powerpc,mips,arm,blackfin等等;原因之二,是因为它能在上 面这些体系结构中引导相当多的操作系统,包括Linux,VxWorks,NetBSD,QNX等等。因此我们说,其名并非虚传。

现今网路上关于UBoot的资料已相当多,但其针对的,却多是X.Y.Z形式的老旧版本。其实,自2008年年底以来,UBoot新版本的发行机制就改 了,现在基本上是每两个月出一个新的版本,其对应的版本号就是 YYYY.MM 的形式。相对于老旧版本的UBoot,代码的布局、内容等方面都已经做出了修改,而且在官方的新版本中也加入了对yaffs2文件系统的支持,这在老版本 中是需要自己动手加的。

 所以,虽然我们说前后版本不同、原理却相同,但是对一个新手来说,拿新版本来学习研究和移植自然是有现实意义的。本人不才,但在嵌入式Linux开发方 面却稍有经验。所以为帮助那些新手少走弯路,也为追求一份因帮助过别人才能得到的快乐,我考虑将自己移植新版本UBoot的经验体会,记录成一系列的文 章,发表在我位于JulianTec的博客上。大家可以自由转载以供学习研究之用,当然,写明出处是起码也是必要的。

我手上待移植的板子,是JulianTec设计监制并提供的,其上使用的CPU(SOC, actually)是三星提供的ARM9芯片S3C2440。要往这样一块板子上移植UBoot,第一步显然是下载官方发布的新版本UBoot代码(我这 里使用的新版本是2010.06),下载下来解压后,作为开发人员的本能反应就是找出解压目录中的README、INSTALL之类的文件,打开并详加研 读,希望找出移植所需要的第一手信息。(我在这里废话那么多,是希望我们的新手也能养成如此的习惯,并使之质变为本能。)

根据README中 Porting Guide 的指示精神,我们要在新板子上移植UBoot,最快速的办法就是查看当前UBoot代码中是否有对相似于待移植板子的其他板子的支持(这应该又是一个可考 虑成为习惯甚至本能的做法。也即拿到不熟悉的软件包后,看看里面有没有自己熟悉的、或者和自己目前要做的东西很相似的部分,从这个部分入手往往能很快的解 决问题)。很幸运,我们在里面找到了三星公司所生产的SMDK2410参考板,这是三星公司早先为推销其生产的ARM9芯片-S3C2410所推出的一块 PCB参考设计板(推出时随板子附加了很多的软硬件资料)。知道这个后,我们很高兴,因为我们知道我们板子上的CPU——S3C2440正是 S3C2410的升级版。所以,在真正动手移植之前,分析一下新版本UBoot中如何支持SMDK2410的,自然成为接下来要做的事情。

UBoot本身是用GNU工具链开发的,那这就意味着其代码包里面必然会有很多的Makefile文件,因为GNU Make正是用来管理软件项目编译的GNU工具。而且,正如我们前面说的,UBoot能支持如此多的CPU体系结构和操作系统,那它就必定会有很多的配置 选项用于配置。所以分析支持SMDK2410参考板的具体代码之前,我们必须先弄懂UBoot的配置编译过程。我们只有对此了然于胸了,才能比较顺利的完 成移植。所幸的是,不像Linux内核代码,UBoot的代码量并不多,分析起来并不痛苦。

作为UBoot学习移植系列的第一篇文章,我在这里就以SMDK2410板子的支持作为例子,分析新版本UBoot(2010.06)的配置编译过程。作 为前提,你应该知道一些GNU Make以及一些Bash Shell Script的知识。但是,为帮助你理解,我在相对应的地方也会做出一些解释,你如果有不明白的地方,可直接在Mail List上进行交流,或直接发邮件向我咨询。

从UBOOT用户的角度来讲,其编译配置过程倒是非常的简单,只需要在命令行中切换到UBoot目录下输入两个命令:

[csicong@juliantec u-boot-2010.06]$ make ARCH=arm CROSS_COMPILE=arm-linux- smdk2410_config
[csicong@juliantec u-boot-2010.06]$ make ARCH=arm CROSS_COMPILE=arm-linux-

第一个命令完成UBoot for smdk2410参考板的配置,第二个命令则真正编译出所需要的 UBoot 二进制映像文件,编译出来之后我们需要将其下载到FLASH中。在这两个命令中,参数ARCH表示我们要给具有何种体系结构的CPU编译UBoot,因为 不管SMDK2410参考板上的CPU——S3C2410,还是我手头板子上的CPU,皆为ARM9芯片,所以这里显然应该为 arm;CROSS_COMPILE为交叉编译工具链各工具的名称前缀。我们需要用到arm-linux-gcc作为编译器、arm-linux-ld为 链接器。。。所以这里取值为 arm-linux- 。

在第一个命令中,我们以 smdk2410_config 作为本次 make 的目标。查找UBoot项目顶层 Makefile 得到关于此目标的规则如下:

smdk2410_config :       unconfig
        @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 samsung s3c24x0

根据 Makefile 规则的定义,GNU Make在处理这条规则的时候,先判断其依赖——也就是 unconfig 是否需要更新。而我们在同一Makefile中找到 unconfig 的规则为:

unconfig: @rm -f $(obj)include/config.h $(obj)include/config.mk $(obj)board*/config.tmp \ $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep

由于目录下没有一个叫 unconfig 的文件存在,所以和 unconfig 相关的这条规则总是得到处理,也就是其中的 rm 命令总是得到执行,该命令的目的是删除一些配置编译过程中产生的文件。在这些文件中,与本文讨论密切相关的是前面两个,以及后面两个。前面两个是用第一条 命令来配置UBoot的过程中产生的,后面则是用第二条命令来编译UBoot的过程中产生的。这条规则先把他们全部删除。注意rm命令前面的@符号是取消 该命令执行时的回显。

执行完对依赖——unconfig的处理,GNU Make 回到对 smdk2410_config 的处理。它接下来执行命令:

       @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 samsung s3c24x0

在这条命令中,$(MKCONFIG) 指代的就是 UBoot 根目录下的 mkconfig 脚本,因为你可以在同一Makefile中找到该变量的定义:

MKCONFIG        := $(SRCTREE)/mkconfig
export MKCONFIG

其中 $(SRCTREE) 指代的就是UBoot 根目录。另外在上面的命令中,$(@:_config=) 的部分实际上是处理自动变量 $@,也就是第一次 make 的目标 smdk2410_config,这里将其中"_config"的部分用空来代替。去掉"_config"的部分后,剩余的 smdk2410 也就是三星参考板的名称。所以最后上面的命令也就可直接写作:

        ./mkconfig smdk2410 arm arm920t smdk2410 samsung s3c24x0

很清楚,执行 mkconfig 脚本,并传之以所在的目录名称,sansung 是产商名称,s3c24x0是对应的SOC芯片名称。有人对arm920和s3c24x0两者所指东西混淆不清,我自己在平时为方便起见也经常混用这两个 词,有时候也将它们统称为CPU。但实际上,更精确的说法认为arm920t是arm9类型的CPU核,而s3c2410则是用该核搭配另外一些外设做在 一块芯片内形成的SOC芯片。由于三星本身出了很多使用arm920tCPU核的芯片,如s3c2400、s3c2410以及s3c2440,所以在 UBoot代码中,用s3c24x0来统称这一类SOC芯片。

mkconfig 脚本是 bash 脚本。对应于smdk2410参考板,其主要做三件事情:

1,在include目录下制作一些软连接,参见代码:
#
# 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}/arch/$2/include/asm asm
        LNPREFIX=${SRCTREE}/arch/$2/include/asm/
        cd ../include
        rm -f asm
        ln -s ${SRCTREE}/arch/$2/include/asm asm
else
        cd ./include
        rm -f asm
        ln -s ../arch/$2/include/asm asm
fi
 
rm -f asm/arch
 
if [ -z "$6" -o "$6" = "NULL" ] ; then
        ln -s ${LNPREFIX}arch-$3 asm/arch
else
        ln -s ${LNPREFIX}arch-$6 asm/arch
fi
 
if [ "$2" = "arm" ] ; then
        rm -f asm/proc
        ln -s ${LNPREFIX}proc-armv asm/proc
fi

由于我们编译UBoot时,通常都是在UBoot原有目录下编译的,并且针对smdk2410参考板,"$2"="arm","$6"="s3c24x0",所以,以上代码的效果等同于我们用下面这些命令来手工创建软连接。

[csicong@juliantec include]$ ln -s ../arch/arm/include/asm ./asm
[csicong@juliantec include]$ ln -s arch-s3c24x0 ./asm/arch
[csicong@juliantec include]$ ln -s proc-armv ./asm/proc

2, 在include目录下制作顶层Makefile要包含的文件 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

很明显,制作出来的文件 include/config.mk,其内容非常简单,只包括 ARCH、CPU、BOARD、VENDOR、SOC等变量的定义。后面我们会知道,这个文件虽然简单,但是却决定了顶层 Makefile的一大部分逻辑框架。

3, 在include目录下制作产生特定于smdk2410参考板的头文件 include/config.h,参见代码:

# Assign board directory to BOARDIR variable
if [ -z "$5" -o "$5" = "NULL" ] ; then
    BOARDDIR=$4
else
    BOARDDIR=$5/$4
fi
 
#
# 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 "" >>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

需要说明的是,上面一行 "> config.h" 的作用是创建一个空文件。有的同志可能不太知道,你可以在shell提示符下试验一下,其效果就和用 touch 命令一样。这段代码会创建文件include/config.h ,其内容如下:

#define CONFIG_BOARDDIR board/samsung/smdk2410
#include <config_defaults.h>
#include <configs/smdk2410.h>
#include <asm/config.h>

至此,针对smdk2410参考板来配置新版本的UBoot代码完成了,接下来,我们能使用上面的第二个命令来编译可以该参考板上引导Linux内核的UBoot了。

在讨论编译UBoot的第二个命令之前,你需要稍微看一下 UBoot 顶层Makefile的内容,其实里面最重要的部分就是一个条件判断结构,抽出代码如下:

...
ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))
...//A é?¨???
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)) $(TIMESTAMP_FILE) $(VERSION_FILE) gdbtools updater env depend dep tags ctags etags cscope $(obj)System.map:
        @echo "System not configured - see README" >&2
        @ exit 1
 
tools:
        $(MAKE) -C tools
tools-all:
        $(MAKE) -C tools HOST_TOOLS_ALL=y
endif   # config.mk
...
...//B é?¨???
...

这个判断结构,其实就是看是否存在文件 include/config.mk,如果存在的,则说明第一步的配置过程已经完成,因为这个文件正式配置过程中产生的。所以此时GNU Make就会处理上面代码中的 A 部分,否则就会处理else后面、endif前面的部分。从这里我们可以看出,如果不对UBoot进行配置,而直接去编译,那么它会给出一个警告,并要求 我们去看README。上面中的 B 部分就是UBoot中对各种所支持板子的规则定义,其中包括了我们前面提到过的对应于smdk2410_config 的规则。

上面代码中的 A 部分是整个顶层 Makefile 的精华所在,其中包含了生成UBoot二进制映像的大部分规则定义,也是我们关注的重点。好了,知道这样一个框架后,我们再回头来看看第二个命令的编译过程。

在开头提到的第二个命令中,我们并没有给 make 指定一个目标,所以按照 GNU Make 的规定,它将去着手处理默认规则,也即出现在顶层Makefile第一个规则 all ,如下:

# Always append ALL so that arch config.mk's can add custom ones
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)
 
all:            $(ALL)

这条规则里面并没有命令,所以GNU Make在处理的时候,会依次去处理ALL中的这五个目标。而针对smdk2410参考板,没有定义CONFIG_NAND_U_BOOT和 CONFIG_ONENAND_U_BOOT,所以依据下面的规则,后面两个目标:$(U_BOOT_NAND) $(U_BOOT_ONENAND)将为空。

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

所以对UBoot的这次编译过程,将会在UBoot顶层目录中生成三个文件,u-boot.srec、u-boot.bin 以及 System.map。其中u-boot.bin正式我们所需要的二进制映像文件,我们可以把它直接下载到板子的FLASH上用于引导Linux操作系 统。

对于第一个文件 u-boot.srec,其对应的规则为:

$(obj)u-boot.srec:      $(obj)u-boot
                $(OBJCOPY) -O srec $< $@

这其实是二进制代码的SRecord格式表示。这种格式最先用在摩托罗拉6800处理器上,专门用于处理二进制数据在不同设备之间的传输上。但是就用 GNU工具进行smdk2410参考板的开发来说,是用不到的。我们只需要最原始的二进制数据映像u-boot.bin即可,里面包含纯粹的二进制指令和 数据,并没有用过任何格式包装过。

不过先不管到底u-boot.srec有没有用,根据上面处理u-boot.srec的规则定义,在处理 u-boot.srec 之前,先要处理 u-boot(它是一个ELF格式可执行文件) ,查找对应的规则定义如下:

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
$(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 "\"}'` ;                 $(CC) $(CFLAGS) -DSYSTEM_MAP="\"$${smap}\""                         -c common/system_map.c -o $(obj)common/system_map.o
                $(GEN_UBOOT) $(obj)common/system_map.o
endif

由上面的规则可知,目标 u-boot 的依赖有很多个,为:depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds。 $(GEN_UBOOT) 是该规则命令集的第一个部分。

而对于SMDK2410参考板来说,CONFIG_KALLSYMS并未定义,所以该规则的命令实际上只有实际上只有变量$(GEN_UBOOT)所指代的部分。

此规则处理的第一个目标为 depend,查找同一 Makefile 可得出规则如下:

# 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) $(CPUDIR) $(dir $(LDSCRIPT)) ; do                         $(MAKE) -C $$dir _depend ; done

该规则先会处理三个依赖,$(TIMESTAMP_FILE)、$(VERSION_FILE)以及$(obj)include/autoconf.mk。前两个分别生成UBoot本次编译的时间戳头文件和版本头文件。而后者则由一个稍微复杂一点的规则处理,如下所示:

#
# Auto-generate the autoconf.mk file (which is included by all makefiles)
#
# This target actually generates 2 files; autoconf.mk and autoconf.mk.dep.
# the dep file is only include in this top level makefile to determine when
# to regenerate the autoconf.mk file.
$(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 $(HOSTCFLAGS) $(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 $@

处理 autoconf.mk 的规则看起来有点繁,但实际上它是以tools/scripts/define2mk.sed作为sed脚本来调用sed程序,其功能是将C语言中定义的 宏配置转换成Makefile能理解的变量定义形式。就smdk2410参考板来说我们可以举个例子:在 include/configs/smdk2410.h 文件中有宏 CONFIG_S3C2410 的定义:

#define CONFIG_S3C2410  1       

那么这里的这个命令就会将其转换成如下的形式写到 include/autoconfi.mk 文件中去。

CONFIG_S3C2410=y

最终出来的 include/autoconf.mk 文件中都是类似的变量定义。从下面的代码中可以看出这个文件将会被顶层 Makefile 所包含进去:

# 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

那么,include/autoconf.mk.dep 又是做什么用的呢?实际上它内部定义了目标 include/autoconf.mk 的依赖有哪些。我们可以列出 autoconf.mk.dep的内容如下:

include/autoconf.mk: include/common.h   /home/yihect/u-boot-2010.06/include/config.h   /home/yihect/u-boot-2010.06/include/config_defaults.h   /home/yihect/u-boot-2010.06/include/configs/smdk2410.h   /home/yihect/u-boot-2010.06/include/config_cmd_default.h   /home/yihect/u-boot-2010.06/include/asm/config.h   /home/yihect/u-boot-2010.06/include/linux/bitops.h   /home/yihect/u-boot-2010.06/include/asm/types.h   /home/yihect/u-boot-2010.06/include/asm/bitops.h   /home/yihect/u-boot-2010.06/include/asm/proc/system.h   /home/yihect/u-boot-2010.06/include/linux/config.h   /home/yihect/u-boot-2010.06/include/linux/types.h   /home/yihect/u-boot-2010.06/include/linux/posix_types.h   /home/yihect/u-boot-2010.06/include/linux/stddef.h   /home/yihect/u-boot-2010.06/include/asm/posix_types.h   /home/yihect/u-boot-2010.06/include/linux/string.h   /home/yihect/u-boot-2010.06/include/asm/string.h   /home/yihect/u-boot-2010.06/include/asm/ptrace.h   /home/yihect/u-boot-2010.06/include/asm/proc/ptrace.h   /home/yihect/eldk/usr/bin/../lib/gcc/arm-linux-gnueabi/4.2.2/include/stdarg.h   /home/yihect/u-boot-2010.06/include/part.h   /home/yihect/u-boot-2010.06/include/ide.h   /home/yihect/u-boot-2010.06/include/flash.h   /home/yihect/u-boot-2010.06/include/image.h   /home/yihect/u-boot-2010.06/include/compiler.h   /home/yihect/eldk/usr/bin/../lib/gcc/arm-linux-gnueabi/4.2.2/include/stddef.h   /home/yihect/u-boot-2010.06/include/asm/byteorder.h   /home/yihect/u-boot-2010.06/include/linux/byteorder/little_endian.h   /home/yihect/u-boot-2010.06/include/linux/byteorder/swab.h   /home/yihect/u-boot-2010.06/include/linux/byteorder/generic.h   /home/yihect/u-boot-2010.06/include/lmb.h   /home/yihect/u-boot-2010.06/include/asm/u-boot.h   /home/yihect/u-boot-2010.06/include/command.h   /home/yihect/u-boot-2010.06/include/asm/global_data.h   /home/yihect/u-boot-2010.06/include/asm/mach-types.h   /home/yihect/u-boot-2010.06/include/asm/setup.h   /home/yihect/u-boot-2010.06/include/asm/u-boot-arm.h   /home/yihect/u-boot-2010.06/include/net.h   /home/yihect/u-boot-2010.06/include/u-boot/crc.h

这样一个文件就是由上面那条对应 autoconf.mk.dep 的规则所生成的。由上面可知,这样一个文件的内容也会被包含到UBoot 顶层 Makefile 中。 讨论到这里,也许您觉得关于 autoconf 的这两条规则都没什么要说的了。但是还有一个问题:那就是 autoconf.mk 和 autoconf.mk.dep 是如何生成的?因为这两个文件都是被 sinclude 到顶层 Makefile 中去的,而在我们使用 Make 编译 UBoot 的时候此两个文件并不存在,所以按照 GNU Make的规范,它在全部读入顶层Makefile后,需要重新生成做 sinclude 时找不到的这两个文件。生成后它又会重新处理顶层Makefile,并重新包含这两个文件。所以此处(depend规则) 对$(obj)include/autoconf.mk处理时,它要做的只是检查检查相关的依赖,决定是否需要重新生成 autoconf.mk 文件。

depend规则中,处理完所有依赖后,GNU Make 会循环进入一些目录去 make 一个特定的目标 _depend,我们这里不再详细深入考察这个部分。我觉得唯一需要您注意的是,对于smdk2410参考板来说,变量LDSCRIPT定义在文件 arch/arm/config.mk 中,如下示:

LDSCRIPT := $(SRCTREE)/$(CPUDIR)/u-boot.lds

这个变量定义了链接时需要使用到的链接脚本,其内容我们会在后续的文章中深入分析。另外如下面所示,我们也知道 arch/arm/config.mk 也是一个被间接 sinclude 到顶层 Makefile 中的文件,该文件的其他内容比较简单,请读者自行分析。

sinclude $(TOPDIR)/arch/$(ARCH)/config.mk       # include architecture dependend rules

好了,回到我们对 u-boot 规则的处理上面来,下一个要处理的依赖是 $(SUBDIRS),我们可找到相关的规则如下:

$(SUBDIRS):     depend
                $(MAKE) -C $@ all

从上面可以看出,它要依赖于 depend,这个我们前面已经处理过了,所以GNU Make要依次进入一系列的目录去 make 目标all。这里值得一说的是,它会进入到 tools 目录下去编译一些工具,其中最重要的当数 mkimage 工具。作为留给读者的练习,请您预先去查找google,了解它的用途及用法。

接下来,GNU Make下一个要处理的依赖是 $(OBJS)。变量OBJS定义在下面:

#########################################################################
# U-Boot objects....order is important (i.e. start must be first)
 
OBJS  = $(CPUDIR)/start.o
ifeq ($(CPU),i386)
OBJS += $(CPUDIR)/start16.o
OBJS += $(CPUDIR)/resetvec.o
endif
ifeq ($(CPU),ppc4xx)
OBJS += $(CPUDIR)/resetvec.o
endif
ifeq ($(CPU),mpc85xx)
OBJS += $(CPUDIR)/resetvec.o
endif
 
OBJS := $(addprefix $(obj),$(OBJS))

以 $(OBJS) 为目标的规则可找到如下:

$(OBJS):        depend
                $(MAKE) -C $(CPUDIR) $(if $(REMOTE_BUILD),$@,$(notdir $@))

对于 smdk2410 参考板来说,变量 OBJS 只定义有一个对象文件。就是这样一个文件start.o,在后面链接形成 u-boot 时将被放在u-boot的最前面。后面会知道,这个文件里的程序将至关重要。因为系统上电后要执行的第一条指令将位于这个文件内。

下面要处理的依赖是 $(LIBBOARD),找到对应的变量定义以及规则如下:

LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
LIBBOARD := $(addprefix $(obj),$(LIBBOARD))
 
$(LIBBOARD):    depend $(LIBS)
                $(MAKE) -C $(dir $(subst $(obj),,$@))

其中变量 BOARDDIR 定义在根目录的config.mk中,如下所示:

ifdef   VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
ifdef   BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk  # include board specific rules
endif

针对smdk2410参考板,BOARDDIR的值计算出来是:samsung/s3c24x0。为什么呢?因为根目录中的 config.mk 是被直接包含进顶层Makefile的,在此之前,顶层Makefile包含 include/config.mk 的。而通过前面的讨论,我们知道正是在这个文件定义了ARCH/CPU/BOARD/VENDOR/SOC 等变量的定义,这是在配置过程中产生的。

讨论到这里,读者也许迷惑于 UBoot 中如此多的 *.mk 文件包含。干脆我们把它们列一下是形成下面的图片。其实比较运气的是,UBoot中的 Makefile 包含文件,都是以 .mk 结尾的,所以我们理起来,也比较方便。

uBoot内各makefile之间的包含关系

顶层 Makefile 包含了根目录下的 config.mk,而根目录下的 config.mk 则包含了不同板子、不同CPU架构特定的 config.mk,它们都被安排在不同的目录下面。这里需要引起注意的是,对于smdk2410参考板,在目录 arch/arm/cpu/arm920t/s3c24x0 下面并不存在 config.mk,但是这里却将它 sinclude 进来。这种做法并不会引发 make 时的错误,因为其用的是 sinclude,而非 include。

另外,从 LIBBOARD 的处理规则上可以看出来,LIBBOARD 目标是依赖于 LIBS的,所以在处理 LIBBOARD 之前,GNU Make 会依次编译生成用到的不同种类的库。只有 LIBS 处理完成后,GNU Make才会动手去编译生成 LIBBOARD。

好,从 u-boot 对应规则上看,GNU Make 要处理的下一步要做的是处理 $(LIBS) 目标,但是正因为刚刚提到的 LIBBOARD 要依赖于 LIBS,所以实际上 LIBS 先于 LIBBOARD 而得到处理,其在 Makefile 中对应的变量定义及规则如下:

LIBS  = lib/libgeneric.a
LIBS += lib/lzma/liblzma.a
LIBS += lib/lzo/liblzo.a
......
LIBS := $(addprefix $(obj),$(LIBS))
.PHONY : $(LIBS) $(TIMESTAMP_FILE) $(VERSION_FILE)
 
 
$(LIBS):        depend $(SUBDIRS)
                $(MAKE) -C $(dir $(subst $(obj),,$@))

它就要进入各个目录中去编译一个个的归档库文件,这没什么好说的。

接下来对u-boot规则要处理的依赖是 $(LDSCRIPT),变量 LDSCRIPT 被定义在文件:arch/arm/config.mk 中:

LDSCRIPT := $(SRCTREE)/$(CPUDIR)/u-boot.lds

而处理 $(LDSCRIPT) 的规则则被放在顶层Makefile 中:

$(LDSCRIPT):    depend
                $(MAKE) -C $(dir $@) $(notdir $@)

正如你所看到的,这条规则会使 GNU Make 进去到目录 $(SRCTREE)/$(CPUDIR)/ 中去做 make u-boot.lds 的动作。而针对 smdk2410 参考板来说,这个目录为 arch/arm/cpu/arm920t/,观察一下,此目录的 Makefile 中并无 u-boot.lds 的规则定义,所以这个规则针对 smdk2410 参考板来说将不会使 GNU Make 做任何动作。

好,接下来,处理 u-boot 目标的最后一个依赖是 $(obj)u-boot.lds,在顶层 Makefile 中查找出对应的规则定义如下:

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

这条规则的意思就是要对 arch/arm/cpu/arm920t/ 目录下的 u-boot.lds 文件作预处理,并将结果保存为 UBoot 根目录下名叫 u-boot.lds 的文件中,其内容我们在以后的文章中再行分析。

好,GNU Make 处理完 u-boot 规则的所有依赖后,接下来要执行该规则对应的命令集来生成 u-boot 。首当其冲的是要执行 GEN_UBOOT 所指代的命令。查找出 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

由上面的定义可知,这里的命令正是进入到 UBoot 根目录中,并使用链接器将前面产生的 start.o、各种库链接起来,构成一个 ELF 格式的可执行映像 u-boot,这可以从编译 UBoot 的输出结果的最后一部分中看出来(用省略号代替了部分内容):

UNDEF_SYM=`arm-linux-objdump -x board/samsung/smdk2410/libsmdk2410.a .....`; 
        cd /home/yihect/u-boot-2010.06 && arm-linux-ld -Bstatic -T u-boot.lds  -Ttext 0x33F80000 
        $UNDEF_SYM arch/arm/cpu/arm920t/start.o --start-group lib/libgeneric.a ...... board/samsung/smdk2410/libsmdk2410.a
        --end-group /home/yihect/u-boot-2010.06/arch/arm/lib/eabi_compat.o 
        -L /home/yihect/eldk/usr/bin/../lib/gcc/arm-linux-gnueabi/4.2.2/soft-float -lgcc -Map u-boot.map -o u-boot
arm-linux-objcopy -O srec u-boot u-boot.srec
arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin

有了 u-boot 这个ELF映像后,从上面输出结果的最后两行可以看出来,GNU Make 将会用 arm-linux-objcopy 工具将 srec 格式映像文件转换出来;对于 u-boot.bin 也以类似的方式 用 objcopy 转出来,这个文件里面包含了纯粹的二进制指令和数据,没有使用任何的格式进行封装。这个正是smdk2410参考板所需要的,开发者会将这个文件下载到板 子的FLASH里面来引导操作系统。

总结一下:对于要使用UBoot的用户来说,UBoot的配置编译时非常简单的,只需要两个命令的调用就好。但是正如你所见,其台面下的动作还是非常之 多,要了解规模不算大也不算小的这样一个工程还是得花费一些时间的。当然,我们讲正是一份汗水、一份收获。等到你要动手移植UBoot的时候,就会懂得这 份汗水的珍贵。另外,如果您想分析Linux内核代码的配置编译过程,不乏以UBoot工程代码作为开始和练习的机会。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多