1、uboot初见
我从网上下载的源码是s3c-u-boot-1.1.6_rel-4-3-2_20080917.tar.bz2,解压后得到s3c-u-boot-1.1.6这个目录。根据顶层的README文件我们可以获取到如下信息:
1.1 顶层目录结构
Board:和一些已有开发板相关的文件,比如Makefile和u-boot.lds等都和具体开发板的硬件和地址分配有关。
Common:与体系结构无关的文件,实现各种命令的C文件。
CPU:CPU相关文件,其中的子目录都是以u-boot所支持的CPU为名,比如有子目录arm926ejs、mips、mpc8260和nios等。
Disk:硬盘接口程序
Doc:开发、试用文档。
Drivers:通用设备驱动程序,比如各种网卡、支持CFI的flash、串口和USB总线等。
Dtt:数字温度测量器或者传感器的驱动
Examples:一些独立运行的应用程序的例子。
Fs:支持文件系统的文件,u-boot现在支持cramfs、fat、fdos、jffs2、yaffs和registerfs。
Include:头文件,还有对各种硬件平台支持的会变文件,系统的配置文件和对文件系统支持的文件。
Lib_arm:存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数,与ARM体系结构相关的代码。
Lib_*:某一架构通用的文件。
Lib_generic:通用的多功能函数实现。
Nand_spl:Uboot一般从ROM、NORFlash等设备启动,现在开始执行NANDFlash启动,但是支持的CPU种类还不多。
Net:与网络有关的代码,BOOTP协议、TFTP协议RARP协议和NFS文件系统的实现。
Post:上电自检程序。
Rtc: 实时时钟驱动。
Tools:创建S-Record格式文件和U-BOOT images的工具。
1.2 编译程序
进入源码目录执行:
make distclean --清除所有生成的文件
make NAME_config --根据自己使用的开发板进行配置
make all --编译源码
2、uboot的配置过程
从README文件中我们知道了make NAME_config是用来对自己的开发板进行配置的,那就先来看下到底是怎么配置的。
这边假设我们的板子是smdk6410,那么应该在源码目录执行make smdk6410_config
make调用的是顶层目录下的Makefile文件,我们传参数是smdk6410_config,
所以在Makefile中找到smdk6410_config的相应部分如下:
smdk6410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s3c64xx smdk6410 samsung s3c6410
先分析下这条规则:
smdk6410_config:规则的目标名
unconfig:规则的依赖,这个在Makefile文件的342行可以找到,如下:
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk \
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
这个就不在分析,就是删除一些文件。
第二行开头的@:表示该行命令的输出被抑制,即执行的时候不会在终端上打印出来。
$(MKCONFIG):MKCONFIG这个变量在92行定义MKCONFIG:=$(SRCTREE)/mkconfig,而变量SRCTREE在87行定义SRCTREE :=$(CURDIR),而CURDIR是Makefile自带的变量表示当前目录,所以$(MKCONFIG)就是当前目录下的mkconfig文件,即顶层目录下的mkconfig文件。
$(@:_config=):$@是make的自动变量,标识规则的目标名,即smdk6410_config。$(@:_config=)意思就是 $@ 中的“_config”替换为空,也就是删掉目标中“_config”这个子串。所以这边$(@:_config=)为smdk6410。
这样看的话在配置时,执行make smdk6410_config实际上执行的是:
./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410
到这才真正地开始配置开发板,接下来带着这条命令详细分析下mkconfig这个文件。
1 #!/bin/sh -e
//选项-e表示一个命令在执行后返回一个非0状态值时,就退出
2
3 # Script to create header files and links to configure
4 # U-Boot for a specific board.
5 #
6 # Parameters: Target Architecture CPU Board [VENDOR] [SOC]
7 #
8 # (C) 2002-2006 DENX Software Engineering, Wolfgang Denk <wd@denx.de>
9 #
10
11 APPEND=no # Default: Create new config file
//APPEND=no这边表示重新创建一个文件,APPEND=yes表示将内容追加到原来的文件中
12 BOARD_NAME="" # Name to print in make output
//BOARD_NAME为开发板的名字
13
14 while [ $# -gt 0 ] ; do
15 case "$1" in
16 --) shift ; break ;;
17 -a) shift ; APPEND=yes ;;
18 -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
19 *) break ;;
20 esac
21 done
//"$#" 将扩展成传递给脚本的参数的数目
//"$*" 将扩展成传递给脚本的所有参数
//"shift" 将$*中的剩余的参数向左移动一个位置并减少$#的值1
//14-21行判断“./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410”命令中是否有“--”,“-a”,“-n”等符号,
//这边没有,所以不做任何事情,11行和12行的变量仍然维持原来的值。
22
23 [ "${BOARD_NAME}" ] || BOARD_NAME="$1"
//先判断BOARD_NAME是否为空,为空将命令中的第一个参数smdk6410赋值给BOARD_NAME,即这边BOARD_NAME为“smdk6410”
24
25 [ $# -lt 4 ] && exit 1
26 [ $# -gt 6 ] && exit 1
//"$#" 表示参数的个数,如果参数个数小于4或者大于6就退出,这边$#=6,所以可以继续往下执行。
27
28 echo "Configuring for ${BOARD_NAME} board..."
//打印出开发板的名字
29
30 #
31 # Create link to architecture specific headers
//创建体系结构相关的头文件链接
32 #
33 if [ "$SRCTREE" != "$OBJTREE" ] ; then
34 mkdir -p ${OBJTREE}/include
35 mkdir -p ${OBJTREE}/include2
36 cd ${OBJTREE}/include2
37 rm -f asm
38 ln -s ${SRCTREE}/include/asm-$2 asm
39 LNPREFIX="../../include2/asm/"
40 cd ../include
41 rm -rf asm-$2
42 rm -f asm
43 mkdir asm-$2
44 ln -s asm-$2 asm
45 else
46 cd ./include
47 rm -f asm
48 ln -s asm-$2 asm
49 fi
//33行判断源码目录SRCTREE和目标文件目录SRCTREE是否一样,SRCTREE和SRCTREE在顶层Makefile中定义,
//这边要判断,是因为可以选择在其他目录下编译u-boot的源码,这样可以令源代码目录保存干净,这时SRCTREE和SRCTREE的值就不一样了
//不过我们一般都习惯直接就在源码目录下编译的,所以SRCTREE和OBJTREE都为当前目录,
//那么33行条件不成立,将执行else分支的代码。
//46-48行进入include目录,删除asm文件(这是上一次配置时建立的链接文件),
//然后再次建立asm文件,并令它链接向asm-$2目录,即asm-arm($2表示第二个参数arm)
50
51 rm -f asm-$2/arch
//删除asm-$2/arch目录,即删除asm-arm/arch
52
53 if [ -z "$6" -o "$6" = "NULL" ] ; then
54 ln -s ${LNPREFIX}arch-$3 asm-$2/arch
55 else
56 ln -s ${LNPREFIX}arch-$6 asm-$2/arch
57 fi
//$6表示第6个参数s3c6410,不为空,也不为“NULL”,53行条件不满足,将执行else分支
//其中LNPREFIX为空,$6表示第6个参数s3c6410,$2表示第2个参数arm
//所以“ln -s ${LNPREFIX}arch-$6 asm-$2/arch”即ln -s arch-s3c6410 asm-arm/arch
//前面有进入include目录,这时还在include目录下,
//所以意思是在asm-arm下创建符号链接arch指向arch-s3c6410
58
59 # create link for s3c24xx SoC
60 if [ "$3" = "s3c24xx" ] ; then
61 rm -f regs.h
62 ln -s $6.h regs.h
63 rm -f asm-$2/arch
64 ln -s arch-$3 asm-$2/arch
65 fi
66
67 # create link for s3c64xx SoC
68 if [ "$3" = "s3c64xx" ] ; then
69 rm -f regs.h
70 ln -s $6.h regs.h
71 rm -f asm-$2/arch
72 ln -s arch-$3 asm-$2/arch
73 fi
//$3表示第三个参数s3c64xx,所以满足68行的if语句,注意的是,这时还在include目录下操作
//删除regs.h文件,$6表示第6个参数s3c6410,所以“ln -s $6.h regs.h”表示“ln -s s3c6410.h regs.h”
//$2表示第2个参数arm,$3表示第三个参数s3c64xx,所以后面两句分别是
//rm -f asm-arm/arch
//ln -s arch-s3c6xx asm-arm/arch
74
75 if [ "$2" = "arm" ] ; then
76 rm -f asm-$2/proc
77 ln -s ${LNPREFIX}proc-armv asm-$2/proc
78 fi
//$2表示第2个参数arm,75行条件满足,还是在include目录下操作
//“rm -f asm-$2/proc”表示“rm -f asm-arm/proc”
//${LNPREFIX}为空,“ln -s ${LNPREFIX}proc-armv asm-$2/proc”表示“ln -s proc-armv asm-arm/proc”
79
80 # create link for s3c64xx-mp SoC
81 if [ "$3" = "s3c64xx-mp" ] ; then
82 rm -f regs.h
83 ln -s $6.h regs.h
84 rm -f asm-$2/arch
85 ln -s arch-$3 asm-$2/arch
86 fi
//$3表示第三个参数s3c64xx,81行条件不满足
87
88 #
89 # Create include file for Make
90 #
91 echo "ARCH = $2" > config.mk
92 echo "CPU = $3" >> config.mk
93 echo "BOARD = $4" >> config.mk
94
95 [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
96
97 [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
//91-97行创建config.mk文件,并定义变量ARCH、CPU、BOARD、VENDOR、SOC
//91行用了'>'会创建config.mk文件,如果之前存在config.mk文件则将其清空,然后将变量输入到文件中,
//其他行的'>>'会将变量追加到文件中
//最后config.mk文件中的内容为:
//ARCH = arm
//CPU = s3c64xx
//BOARD = smdk6410
//VENDOR = samsung
//SOC = s3c6410
98
99 #
100 # Create board specific header file
101 #
102 if [ "$APPEND" = "yes" ] # Append to existing config file
103 then
104 echo >> config.h
105 else
106 > config.h # Create new config file
107 fi
//创建开发板相关的头文件include/config.h
//前面定义了APPEN的值为“no”,102行条件不成立,执行else分支
//'>'会创建config.h文件,如果原来存在config.h文件,则会覆盖其中的内容清空。
108 echo "/* Automatically generated - do not edit */" >>config.h
109 echo "#include <configs/$1.h>" >>config.h
//108行表示向config.h中追加数据“/* Automatically generated - do not edit */”
//$1表示第一个参数smdk6410,所以109行表示向config.h文件中追加数据“#include <configs/smdk6410.h>”
110
111 exit 0
//执行完正常退出
现在总结下,配置命令“make smdk6410_config”,
实际的作用就是执行“./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410”命令,
其产生的结果如下:
1、设置开发板名称BOARD_NAME为smdk6410
2、创建体系结构相关头文件链接,如下所示:
ln -s asm-arm asm
ln -s arch-s3c64xx asm-arm/arch
ln -s proc-armv asm-arm/proc
3、创建顶层Makefile包含的头文件include/config.mk,如下所示:
ARCH = arm
CPU = s3c64xx
BOARD = smdk6410
VENDOR = samsung
SOC = s3c6410
4、创建开发板相关头文件include/config.h,如下所示:
/* Automatically generated - do not edit */
#include <configs/smdk6410.h>
3、uboot的编译、链接过程
分析完了配置的命令make smdk6410_config,接下来分析下make all是如何进行编译和链接的。
首先对整个Makefile进行分析,如下:
24 VERSION = 1 //主版本号
25 PATCHLEVEL = 1 //次版本号
26 SUBLEVEL = 6 //修正版本号
27 EXTRAVERSION = //扩展版本号
28 U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
//设置了这个uboot的版本U_BOOT_VERSION = 1.1.6
29 VERSION_FILE = $(obj)include/version_autogenerated.h
//$(obj)在这边还未定义,所以VERSION_FILE = include/version_autogenerated.h,VERSION_FILE在289有用到,用于记录uboot的版本信息。
30
31 HOSTARCH := $(shell uname -m | \
32 sed -e s/i.86/i386/ \
33 -e s/sun4u/sparc64/
34 -e s/arm.*/arm/ \
35 -e s/sa110/arm/ \
36 -e s/powerpc/ppc/ \
37 -e s/macppc/ppc/)
//首先执行shell命令uname -m得到i686/,通过管道传送给sed命令,然后sed命令执行sed -e s/i.86/i386/,意思就是将i686替换成i386,可以自己查下sed命令的用法,因为uname -m得到的只有i686,所以后面几个-e选项就不用看了。之所以将i686改成i386,是因为i686也属于i386体系。再ubuntu-11.10xia执行uname -p可以查看到处理器类型为i686,用uname -i可以查看到硬件平台类型为i386。最后的结果是HOSTARCH=i386,表示主机体系结构为i386。
38
39 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
40 sed -e 's/\(cygwin\).*/cygwin/')
//首先执行shell命令uname -s得到Linux,表示linxu系统,然后通过管道传送给tr命令,tr命令利用特殊字符类[:upper:]和[:lower:]将大写字母转化成小写字母,即将Linux转化为linux,后面再执行sed命令,不过uname -s得到只有Linux,没有cygwin之类的东西,所以sed可以不看了,最好的结果是HOSTOS=linux,表示主机操作系统为linux。
41
42 export HOSTARCH HOSTOS
//用export 增加了HOSTARCH和HOSTOS这两个环境变量,供下层的Makefile用
43
44 # Deal with colliding definitions from tcsh etc.
45 VENDOR=
//VENDOR表示芯片厂商,这边设置为空,不过在include/config.mk有设置成samsung,下面的程序会把include/config.mk这个文件包含到这个Makefile文件中include/config.mk是在make smdk6410_config的时候创建并设置变量的。
69 ifdef O
70 ifeq ("$(origin O)", "command line")
71 BUILD_DIR := $(O)
72 endif
73 endif
//Uboot支持将目标文件生成在外部的文件夹中,有两种命令可以实现:一是加O选项,比如make O=/tmp/build all将目标文件放在/tmp/build;二是设置环境变量BUILD_DIR,比如export BUILD_DIR=/tmp/build将目标文件放在/tmp/build。如果以上两种方式都没有定义,那么它将会被存放在源码目录下。
//69-73的意思如果定义可O选项,并且并且O=指定的目录和command line指定的目录一样,BUILD_DIR就为O=定义的目录。origin是Makefile中自带的函数,他并不操作
变量的值,只是告诉你这个变量从哪里来自己可以去查下具体的用法。command line就是我们编译的时候输入的命名,如make O=/tmp/build all。
//实际上我们编译的时候用的是make all,没有指定O选项,所以69-73不执行。
74
75 ifneq ($(BUILD_DIR),)
76 saved-output := $(BUILD_DIR)
77
78 # Attempt to create a output directory.
79 $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
80
81 # Verify if it was successful.
82 BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
83 $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
84 endif # ifneq ($(BUILD_DIR),)
//75行 判断BUILD_DIR是否为0,若不为0,则save-output保存BUILD_DIR指定的输出目录
//79行 判断BUILD_DIR这个目录是否存在,如果不存在,就创建这个目录
//82行 先进入这个目录,再调用pwd显示当前路径,在将这个路径值赋给BUILD_DIR
//83行 如果BUILD_DIR还不存在的话,则输出saved-output中的目录does not exist
//实际上我们编译的时候用的是make all,没有设置BUILD_DIR,所以75-84不执行。
85
86 OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
//判断变量BUILD_DIR是否为空,如果不为空,则OBJTREE等于BUILD_DIR的值,如果为空,则OBJTREE等于CURDIR的值,CURDIR是Makefile自带的变量,表示当前目录。实际上这边我们没有设置BUILD_DIR变量,所以OBJTREE=$(CURDIR),即OBJTREE为当前目录。OBJTREE表示目标文件目录。
87 SRCTREE := $(CURDIR)
88 TOPDIR := $(SRCTREE)
89 LNDIR := $(OBJTREE)
//OBJTREE和LNDIR为存放生成文件的目录,TOPDIR与SRCTREE为源码顶层目录,都设置成了当前目录。
90 export TOPDIR SRCTREE OBJTREE
//用export 增加了TOPDIR SRCTREE OBJTREE这三个环境变量,供下层的Makefile用
91
92 MKCONFIG := $(SRCTREE)/mkconfig
93 export MKCONFIG
//定义变量MKCONFIG,$(SRCTREE)为源码顶层目录,所以MKCONFIG为源码顶层目录的mkconfig,这个在配置开发板的时候有用到,上篇分析make smdk6410_config的时候,有详细分析过这个文件。
94
95 ifneq ($(OBJTREE),$(SRCTREE))
96 REMOTE_BUILD := 1
97 export REMOTE_BUILD
98 endif
//如果输出目录与源码目录不等,则设定REMOTE_BUILD项,并导出。
103 ifneq ($(OBJTREE),$(SRCTREE))
104 obj := $(OBJTREE)/
105 src := $(SRCTREE)/
106 else
107 obj :=
108 src :=
109 endif
110 export obj src
//如果输出目录OBJTREE与源码顶层目录SRCTREE不等的话,对obj和src进行赋值,否则则obj,src为空。
//obj src会在主目录中的config.mk定义,但在主makefile包含config.mk之前也需要,譬如unconfig, clean, clobber, distclean
//这边OBJTREE和SRCTREE相等,所以obj src都为空。
114 ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))
115
116 # load ARCH, BOARD, and CPU configuration
117 include $(OBJTREE)/include/config.mk
118 export ARCH CPU BOARD VENDOR SOC
119
120 ifndef CROSS_COMPILE
121 ifeq ($(HOSTARCH),ppc)
122 CROSS_COMPILE =
...
...
155 ifeq ($(ARCH),avr32)
156 CROSS_COMPILE = avr32-
157 endif
158 endif
159 endif
//114行判断config.mk是否存在,config.mk是在执行make smdk6410_config的时候生成的,里面设置了ARCH CPU BOARD VENDOR SOC这几个变量。
//117行include/config.mk中的东西都包含在这个Makefile中了
//118行用export 增加了ARCH CPU BOARD VENDOR SOC这五个环境变量,可以供下层的Makefile用
//120-159行根据体系结构设置CROSS_COMPILE变量,即编译的时候使用的编译器。
160
161 CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux-
162 export CROSS_COMPILE
//直接修改CROSS_COMPILE变量,设置自己的编译器,这边加这两句的话,120-159的设置就没用了,以后我们要修改编译器的时候,也可以直接在这边改就行了。
165 include $(TOPDIR)/config.mk
//包含顶层目录下的config.mk文件,这个下面会用到,下面会说明。
170 OBJS = cpu/$(CPU)/start.o
171 ifeq ($(CPU),i386)
172 OBJS += cpu/$(CPU)/start16.o
173 OBJS += cpu/$(CPU)/reset.o
174 endif
...
...
187 endif
//170行开始就是设置需要的目标文件了,170行表示需要目标文件start.o
//由于我们CPU为s3c64xx,在前面包含的config.mk中设置的,所以171-187都没用到。
189 OBJS := $(addprefix $(obj),$(OBJS))
//addprefix是Makefile中的一个加前缀函数函数,将$(obj)加到$(OBJS),不过这边$(obj)为空,107行设置的
191 LIBS = lib_generic/libgeneric.a
...
...
215 LIBS += $(BOARDLIBS)
//191-215行设置链接时需要这些文件,这些文件涉及到的目录有lib_generic,board,cpu,lib_arm,fs,net,disk,rtc,dtt,drivers,post,common
217 LIBS := $(addprefix $(obj),$(LIBS))
218 .PHONY : $(LIBS)
//217行和189行的意思一样的,218行用到.PHONY定义了一个伪目标,伪目标挺简单的,可以自己去查下。
221 PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
//加入GCC的库,CC和CFLAGS都是make的隐含变量,CC表示C编译器,CFLAGS表示执行CC时的命令行参数
225 SUBDIRS = tools \
226 examples \
227 post \
228 post/cpu
229 .PHONY : $(SUBDIRS)
//tools、examples等这个目录可以单独编译一些工具,所以把这些目录都包含在SUBDIRS这个变量中。
230
231 ifeq ($(CONFIG_NAND_U_BOOT),y)
232 NAND_SPL = nand_spl
233 U_BOOT_NAND = $(obj)u-boot-nand.bin
234 endif
//支持nandflash启动,我们之前执行make smdk6410_config进行配置,这样的配置默认状态是不支持nandflash启动的,以后要支持nandflash启动的话要进行修改,具体改的时候再分析把。
235
236 __OBJS := $(subst $(obj),,$(OBJS))
237 __LIBS := $(subst $(obj),,$(LIBS))
//subst是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作的字串,中间两个逗号中间什么也没有,所以这句的意思就是直接去掉$(LIBS)变量中的$(obj)。
242 ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
243
244 all: $(ALL)
//我们在编译的时候用的是make all,所以会找到244行的all,$(ALL)为all的依赖,在242行有定义,因为$(obj)为空,$(U_BOOT_NAND)默认状态未定义,所以ALL=u-boot.srec u-boot.bin System.map,即all的依赖为u-boot.srec u-boot.bin System.map,那么就会先去检测u-boot.srec u-boot.bin System.map这三个文件是否需要重新生成,而这个三个文件取决于其各自的依赖是否发生改变。
u-boot.srec是Motorola的S-Record格式的U-Boot映像,该映像来自于ELF格式的U-boot映像u-boot文件。通过以下命令生成:
249 $(obj)u-boot.srec: $(obj)u-boot
250 $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
OBJCOPY和OBJCFLAGS在顶层config.mk中定义,因此,实际上是调用objcopy命令生成的,arm-linux-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec。objcopy将进行映像格式的转化,其中-O srec说明输出映像的格式为serc的格式。
u-boot.bin表示原始二进制格式的镜像文件,是我们最后需要下载到板子上的二进制文件,该映像也是通过objcopy命令对ELF映像u-boot转化而成,只是转化的参数不同而已,在252行可以找到。
252 $(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(OBJDUMP) -d $< > $<.dis
//-O binary即让其生成二进制格式的映像。
System.map是索引文件,是uboot的符号表,即该文件中给出内存地址和相应的符号的映射关系,通过System.map文件可以知道函数,全局变量和静态变量的地址。当然有地址也可以查出对应的符号。和Linux内核中System.map生产一样,System.map是通过nm工具从目标文件u-boot中提取对应的符号表:
320 $(obj)System.map: $(obj)u-boot
321 @$(NM) $< | \
322 grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
323 sort > $(obj)System.map
而实际上调用nm工具从u-boot中提取符号表,然后调用grep打印相匹配的行,而sort则是对这些符号排序后输出到System.map中。
当然,其中这些映像和符号表都取决于ELF映像的u-boot,这是这节的重点。u-boot映像取决于以下语句:
266 $(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
267 UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
268 cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
269 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
270 -Map u-boot.map -o u-boot
即u-boot依赖于depend,version,$(SUBDIRS),$(OBJS),$(LIBS),$(LDSCRIPT)
其中depend定义了文件的依赖关系,而这些依赖关系最终将被包含进同级的Makefile中,以便编译的时候使用,比如当执行make omap1510inn_config 进行配置后,执行Make 命令将首先生成对应的.depend 文件,比如对于tools目录下的.depend文件如下:
environment.o: environment.c ../include/config.h \
../include/configs/omap1510inn.h ../include/cmd_confdefs.h \
../include/configs/omap1510.h ../include/asm/arch/sizes.h \
../include/environment.h
通过这种方式,把相应的配置结果给用起来了,即把相应的config.h使用起来了。
version主要在设置U-boot的版本
287 version:
288 @echo -n "#define U_BOOT_VERSION \"U-Boot " > $(VERSION_FILE); \
289 echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); \
290 echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion \
291 $(TOPDIR)) >> $(VERSION_FILE); \
292 echo "\"" >> $(VERSION_FILE)
而宏SUBDIRS的命令如下:
225 SUBDIRS = tools \
226 examples \
227 post \
228 post/cpu
278 $(SUBDIRS):
279 $(MAKE) -C $@ all
即进入到各个列举出来的子目录比如tools,example目录,并执行make all操作。
$(OBJS)的定义如下:
170 OBJS = cpu/$(CPU)/start.o
而CPU 的类型取决于配置的结果,前一节“U-Boot的配置”中提到,最终把ARCH, CPU, BOARD 的配置写入了config.mk文件,比如对于smdk6410而言,ARM为s3c64xx,因此OBJS =cpu/s3c64xx/start.o这也是整个u-boot映像的第一个文件。
而$(LIBS)将会生成LIBS 定义的所有文件,具体可参看U-Boot顶层目录的Makefile文件191行,此处不再赘述。
$(LDSCRIPT)的定义在顶层目录下的config.mk中,如下:
143 LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
因此根据config.mk 的定义,将包含具体目标板所在目录的u-boot.lds链接脚本文件
//268行的LDFLAGS确定了连接方式,LDFLAGS在顶层config.mk中定义,165行有将这个文件包含进来。
//在config.mk中的189行有
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
在board/smdk6410/config.mk中,定义了“TEXT_BASE=0x33F80000”。所以,最终结果如下:LDFLAGS中有-T board/smdk6410/U-boot.lds -Ttext 0x33F80000的字样,这些字样指定了程序的布局、地址。
board/smdk6410/U-boot.lds文件如下
24 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
25 /*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
26 OUTPUT_ARCH(arm)
27 ENTRY(_start)
28 SECTIONS
29 {
//指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成
30 . = 0x00000000;
//从0x0位置开始
31
//代码段
32 . = ALIGN(4);
//代码以4字节对齐
33 .text :
34 {
35 cpu/s3c64xx/start.o (.text)
//代码段的第一个代码部分为start.o
//按顺序依次存储下面的部门
36 cpu/s3c64xx/s3c6410/cpu_init.o (.text)
37 cpu/s3c64xx/onenand_cp.o (.text)
38 cpu/s3c64xx/nand_cp.o (.text)
39 cpu/s3c64xx/movi.o (.text)
40 *(.text)
41 lib_arm/div0.o
42 }
43
//const只读常量数据段
44 . = ALIGN(4);
45 .rodata : { *(.rodata) }
46
//static/global 常量段
47 . = ALIGN(4);
48 .data : { *(.data) }
49
//指定got段, got段式是uboot自定义的一个段, 非标准段
50 . = ALIGN(4);
51 .got : { *(.got) }
52
//命令定义段
//把__u_boot_cmd_start赋值为当前位置, 即起始位置
53 __u_boot_cmd_start = .;
//指定u_boot_cmd段, uboot把所有的uboot命令放在该段
54 .u_boot_cmd : { *(.u_boot_cmd) }
//指定u_boot_cmd段, uboot把所有的uboot命令放在该段
55 __u_boot_cmd_end = .;
56
//mmudata段
57 . = ALIGN(4);
58 .mmudata : { *(.mmudata) }
59
//堆栈段/内部临时变量段-bbs
60 . = ALIGN(4);
//把__bss_start赋值为当前位置,即bss段的开始位置
61 __bss_start = .;
//指定bss段
62 .bss : { *(.bss) }
//把_end赋值为当前位置,即bss段的结束位置
63 _end = .;
64 }
从35行可知,cpu/s3c64xx/start.o被放在程序的最前面,所以U-boot的入口点在cpu/s3c64xx/start.S中。
现在总结下U-boot的编译流程:
1、首先编译cpu/$(CPU)/start.S,对于不同的CPU还可能编译cpu/$(CPU)下的其他文件。
2、然后,对于平台/开发板相关的每个目录,每个通用目录都使用他们各自的Makefile生成相应的库。
3、将1、2步骤生产的.o、.a文件按照board/$(BOARD)/config.mk文件中指定的代码段起始地址、board/$(BOARD)/U-boot.lds连接脚本进行连接。
4、第三步得到的是ELF格式的U-boot,后面Makefile还会将它转化为二进制格式、S-Record格式。
//326行else对应114行的ifeq,因为条件满足,所以这边else后面的语句就不执行了。
#########################################################################
326 else
all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin \
$(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot \
$(SUBDIRS) version gdbtools updater env depend \
dep tags ctags etags $(obj)System.map:
@echo "System not configured - see README" >&2
@ exit 1
endif
.PHONY : CHANGELOG
CHANGELOG:
git log --no-merges U-Boot-1_1_5.. | \
unexpand -a | sed -e 's/\s\s*$$//' > $@