uboot: Universal Bootloader,是一种普遍的嵌入式系统bootloader。本篇以mini2440开发板为例.
最终目标:启动Linux内核。
作用:硬件初始化,建立适当的软硬件环境。
uboot的工作流程可分为两个Stage:
(1)Stage1:
a. 硬件初始化:异常向量表和中断向量表的实现,关看门狗,初始化SDRAM,设置栈,设置时钟,设置I/D catch,nand初始化,代码重定位(Nor和Nand两种启动方式),串口初始化,清BSS等等;
b. 调用C函数进入Stage2。
Stage1主要用依赖于CPU体系结构的代码来实现,通常为汇编,会调用少量的C。
(2)Stage2:
a. 确定Linux内核所需的启动参数
b. 将这些参数以特定的结构(uboot用结构体来表示)存放到特定的地址中。
Stage2用C语言实现,更好的可读性和可移植性。
对于我这样一个菜鸟来说,这个问题有点尴尬......
个人理解,无论学习uboot还是Linux内核,都需要一个切入点,就是Makefile。Makefile又分顶层Makefile(或者叫根目录Makefile?)和子目录Makefile,在make之前,通常有一些配置信息生成,供Makefile包含或调用,这是后话。Makefile中一定会使用链接脚本,链接脚本显示了二进制可执行文件中各个Section的内容,这样你就知道了uboot.bin的大概结构。
通过Makefile和链接脚本,我们可以获得的信息是:第一个被执行的程序,加载地址等。下面就是顺藤摸瓜看代码了。
另外一点就是uboot源码的选取,我是用的u-boot1.1.6的老版本,我们不需要了解uboot的全部代码,只需理解其核心功能。网上对u-boot1.1.6源码解析的文章比较多,便于学习。
我们在编译uboot的时候,一般分为两步
在根目录下输入:
a. make xxxx_config b. make
当输入 make mini2440_config时,在Makefile中执行的语句是:
mini2440_config : unconfig @$(MKCONFIG) $(@:_config=) arm arm920t mini2440 tekkamanninja s3c24x0
事实上,下载的官方uboot源码中是没有这条语句的,相似的可以看到
smdk2410_config : unconfig @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
想要想编译mini2440的bootloader,就需要我们自己添加一笔。下面来详细解读一下这些make语句
执行make mini2440_config时,会自动跳转到上面的语句,紧接着执行unconfig,在Makefile中找到unconfig的定义
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk \
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
unconfig用于删除(rm -f)上一次make mini2440_config产生的文件,它们是:include/config.h、config.mk、board/*/config.tmp、board/*/*/config.tmp,值得一提的是顶层目录下的config.mk用于设置编译选项,这里暂且不表。
接下来执行:
@$(MKCONFIG) $(@:_config=) arm arm920t mini2440 tekkamanninja s3c24x0
顶层Makefile中MKCONFIG的定义是:
MKCONFIG := $(SRCTREE)/mkconfig
又有:
SRCTREE := $(CURDIR)
CURDIR可以在配置命令的时候用参数指定,或者像我一样在代码根目录下进行配置,默认CURDIR是当前目录。所以@$(MKCONFIG)就是当前目录(根目录)下的mkconfig文件。
下面来看 $(@:_config=),首先要了解一个规则,s:a=b是将s中的a替换成b(其实这是一个简化的GNU函数),把@所表示的变量中的_config替换成空,那么@表示什么?说一下Makefile中@的用法:有的时候命令行以'@'打头,表示在执行到的时候不回显相应的命令内容,只显示命令的输出。这里的@并非如此,@与$共同组成$@表示目标文件mini2440_config,所以此处是将mini2440_config文件中的_config换成空格。
替换后为:
./mkconfig mini2440 arm arm920t mini2440 tekkamaninja s3c24x0
到顶层mkconfig文件中执行语句,参数是./mkconfig mini2440 arm arm920t mini2440 tekkamaninja s3c24x0(mkconfig 本身也作为bash的参数),分别对应参数0到6。
接下来看顶层目录下的mkconfig文件:
#!/bin/sh -e # Script to create header files and links to configure # U-Boot for a specific board. # # Parameters: Target Architecture CPU Board [VENDOR] [SOC] # # (C) 2002-2006 DENX Software Engineering, Wolfgang Denk
# APPEND=no # Default: Create new config file 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" [ $# -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 echo "Configuring for ${BOARD_NAME} board..." # # 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="../../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 [ -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 # # 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 for i in ${TARGETS} ; do echo "#define CONFIG_MK_${i} 1" >>config.h ; done echo "#include " >>config.h echo "#include " >>config.h exit 0 #!/bin/sh -e指定了脚本所用的解析器,如果你学过shell脚本,应该不会陌生。
“Script to create header files and links to configure U-Boot for a specific board.”这是一个脚本,用于创建头文件和链接,生成的头文件和链接用来配置特定的board.
Parameters: Target Architecture CPU Board [VENDOR] [SOC]
刚好对应了:mini2440 arm arm920t mini2440 tekkamanninja s3c24x0
Target: 宿主机平台
Architecture: 定义芯片架构(如MIPS、POWERPC、ARM等)
CPU: 定义芯片指令集版本(如ARM7、ARM9、ARM11等)
Board: 字面意思......
[VENDOR]: 按厂商划分(如AT9200、S3C44B0等),这里一般写NULL,移植的这位大神写上了自己的名字.......
[SOC]: 系统芯片,按SOC类型(如S3C2440、S3C2410等)
APPEND=no# Default: Create new config file 默认创建一个新的配置文件,那么可以猜测一下APPEND=yes时则不创建新配置文件,有待验证。
BOARD_NAME=""# Name to print in make output 没什么好说的
while [ $# -gt 0 ] ; docase "$1" in--) shift ; break ;;-a) shift ; APPEND=yes ;;-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;*) break ;;esacdone
while [ $# -gt 0 ] ; ##在[ ]中进行判断,$#表示参数的个数,这里有6个参数,即参数1到6,显然大于0,那么执行后面的语句。
docase "$1" in--) shift ; break ;;-a) shift ; APPEND=yes ;;-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;*) break ;;esacdone
判断标号为1的参数即mini2440是否是下列值之一,显然不是,跳出。
[ "${BOARD_NAME}" ] || BOARD_NAME="$1" [ $# -lt 4 ] && exit 1[ $# -gt 6 ] && exit 1
BOARD_NAME为空,执行则将mini2440赋给BOARD_NAME。
参数个数判断,大于4不小于6,所以不会退出。
echo "Configuring for ${BOARD_NAME} board..." #打印Configuring formini2440 board...
if [ "$SRCTREE" != "$OBJTREE" ] ;
比较$SRCTREE和$OBJTREE,在Makefile中找到
SRCTREE := $(CURDIR) OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
如果定义了BUILD_DIR,那么OBJTREE := $(BUILD_DIR)否则OBJTREE := $(CURDIR)
又有:
BUILD_DIR := $(O)
所以BUILD_DIR没有被定义,即if [ "$SRCTREE" != "$OBJTREE" ] 为假。
执行
cd ./include rm -f asm ln -s asm-$2 asm
asm是编译时自动生成的。“ln -s 源文件 目标文件”软连接命令,这里在include目录下建立了asm-arm目录的符号链接asm。当编译例如#include
语句的时候,将自动转换为#include ,编译的时候不用因为处理器的不同手动去改变。使用ls -l可以看到链接标志。记住,目标指向源。
rm -f asm-$2/arch #删除asm-arm/arch if [ -z "$6" -o "$6" = "NULL" ] ; -z:[ -z STRING ] “STRING” 的长度为零则为真。 -o:表示或
即判断$6是长度为0的字符串或是NULL,显然都不满足,执行else。
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
LNPREFIX没有被定义,为空,带入$6和$2得到
ln -s arch-s3c24x0 asm-arm/arch
同样是软连接,建立了arch-s3c24x0目录的符号链接asm-arm/arch。
if [ "$2" = "arm" ] ; then rm -f asm-$2/proc ln -s ${LNPREFIX}proc-armv asm-$2/proc Fi
$2的值是arm,满足条件,删除asm-arm/proc目录,然后建立asm-arm/proc目录的符号链接并命名为proc-armv目录。
# Create include file for Make 接下来要做的工作从字面上来看应该是产生一个文件,该文件的作用是用于Make,想必是被Makefile所用,即产生后被Makefile包含。
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
这几行比较简单了,产生一个config.mk文件,内容为
ARCH = arm
CPU = arm920t
BOARD = mini2440
VENDOR = tekkamanninja
SOC = s3c24x0
bash语言的重定向:echo与>、>>配合使用,>表示新建一个文件,>>表示追加字符串。
接下来# 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
" >>config.h exit 0
前面有过定义APPEND=no,跳转执行else,创建一个config.h头文件。
将 "/* Automatically generated - do not edit */" 和"#include
" 追加到头文件中。此时config.h文件的内容为:
/* Automatically generated - do not edit */ #include
这也证实了我们之前的猜想,由APPEND来控制是否创建新文件,默认是创建的,这个文件就是config.h。
总结一下mkconfig的作用,首先是接收Makefile传递过来的参数,利用这些参数做了以下工作:
a.完成一些目录的软连接。这些目录通常与CPU架构有关,软连接方便编译时寻找架构有关的文件。
b.生成include/config.mk文件,该文件存放了顶层Makefile传递过来的一些参数,在之后被Makefile包含调用。(这里有一个疑问,既然参数是由Makefile传递过来的,那么Makefile直接用就好,为什么还要单独放到一个文件中......)
c.生成include/config.h文件,显然是留给C语言调用的。
以上就是配置过程,这一系列语句的执行是在执行make mini2440_config后发生的,配置完就可以编译了。
命令:make
在顶层目录下执行make命令,那么执行的自然就是Makefile。
在Makefile中调用的语句是all: $(ALL)
根据Makefile的规则,目标是all,依赖是$(ALL)。
Makefile中有:
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
所以依赖文件是当前目录(顶层目录)下的u-boot.srec、u-boot.bin、System.map、u-boot-nand.bin等文件
u-boot.srec :U-Boot映像的S-Record格式 (没看懂......)
u-boot.bin :U-Boot映像原始的二进制格式
System.map :U-Boot映像的符号表
查了一下System.map:
System.map用于存放内核符号表信息。符号表是所有符号和其对应地址的一个列表,随着每次内核的编译,就会产生一个新的对应的System.map文件,当内核运行出错时,通过System.map中的符号表解析,就可以查到一个地址值对应的变量名,或反之。
文 件 名 称
说 明
文 件 名 称
说 明
System.map
U-Boot映像的符号表
u-boot.bin
U-Boot映像原始的二进制格式
u-boot
U-Boot映像的ELF格式
u-boot.srec
U-Boot映像的S-Record格式 (没看懂......)
顶层Makefile中:
$(obj)u-boot.srec: $(obj)u-boot $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@ $(obj)u-boot.bin: $(obj)u-boot $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@ $(obj)System.map: $(obj)u-boot @$(NM) $< | \ grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \ sort > $(obj)System.map $(U_BOOT_NAND): $(NAND_SPL) $(obj)u-boot.bin cat $(obj)nand_spl/u-boot-spl-16k.bin $(obj)u-boot.bin > $(obj)u-boot-nand.bin
在根目录下的config.mk文件中:
OBJCOPY = $(CROSS_COMPILE)objcopy
顶层Makefile中:
ifndef CROSS_COMPILE ifeq ($(HOSTARCH),ppc) CROSS_COMPILE = else . . . ifeq ($(ARCH),arm) CROSS_COMPILE = arm-linux- Endif # load other configuration include $(TOPDIR)/config.mk
所以顶层Makefile包含了顶层目录的config.mk文件,$(OBJCOPY)即为arm-linux-objcopy
OBJCFLAGS += --gap-fill=0xff; #--gap-fill = 0xff是objcopy的参数,表示在拷贝过程中,用0xff来填充段与段之间的空隙。(这个以前不知道,长姿势了)
$< 表示第一个依赖,$@表示目标。
编译的规则都定义在顶层的config.mk中。
u-boot.srec、u-boot.bin、System.map的依赖都是u-boot,接下来看一下u-boot是如何生成
官方u-boot 1.1.6中
$(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
在kangear-U-boot-2009.11版本的Makefile,你会看到另一种形式:
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 "\\\\000"}'` ; \ $(CC) $(CFLAGS) -DSYSTEM_MAP="\"$${smap}\"" \ -c common/system_map.c -o $(obj)common/system_map.o $(GEN_UBOOT) $(obj)common/system_map.o
注意$(GEN_UBOOT)是第一个规则,而不是依赖。CONFIG_KALLSYMS未定义,所以规则只有$(GEN_UBOOT)一个,将$(GEN_UBOOT)替换后与前一种生成uboot的规则是一样的。
两种生成方式虽然规则相同,但是依赖文件略有不同。我用的kangear-U-boot-2009.11,下面就分析kangear-U-boot-2009.11中uboot的生成。
先了解一下各个依赖文件是什么: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
第一个依赖——depend
在顶层Makefile中:
depend dep: for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
两个目标depend和dep,他们是一样的。然后利用一个循环,在所有的子目录中使用make _depend。
这就调用了子目录的Makefile,以kangear-U-boot-2009.11/cpu/arm920t/s3c24x0下的Makefile为例:
s3c24x0目录下的文件有mmc.c speed.c timer.c usb.c usb_ohci.c interrupts.c usb_ohci.h Makefile
Makefile代码如下
include $(TOPDIR)/config.mk LIB = $(obj)lib$(SOC).a COBJS-$(CONFIG_USE_IRQ) += interrupts.o COBJS-y += speed.o COBJS-y += timer.o COBJS-y += usb.o COBJS-y += usb_ohci.o COBJS-y += mmc.o SRCS := $(SOBJS:.o=.S) $(COBJS-y:.o=.c) OBJS := $(addprefix $(obj),$(SOBJS) $(COBJS-y)) all: $(obj).depend $(LIB) $(LIB): $(OBJS) $(AR) $(ARFLAGS) $@ $(OBJS) ######################################################################### # defines $(obj).depend target include $(SRCTREE)/rules.mk sinclude $(obj).depend #########################################################################
一开始并没有看到执行make _depend要实现的目标_depend,但是发现include $(SRCTREE)/rules.mk,找到顶层目录下的rules.mk,代码如下:
rules.mk: _depend: $(obj).depend $(obj).depend: $(src)Makefile $(TOPDIR)/config.mk $(SRCS) @rm -f $@ @for f in $(SRCS); do \ g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \ $(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \ done
所以在cpu/arm920t/s3c24x0目录下执行make _depend的时候,调用的是以上代码。
目标_depend,依赖当前目录下的.depend
目标.depend,依赖顶层目录下的Makefile和config.mk,这应该是为了使用这两个文件中的一些变量和编译选项,用到的时候再做分析。
还有一个一依赖是$(SRCS)
SRCS := $(SOBJS:.o=.S) $(COBJS-y:.o=.c)
我没有找到SOBJS和COBJS的定义,变量SOBJS应该表示所有由.S文件编译得到的.o文件的名字,此时将所有.o替换为.S,那么得到的就是所有.S文件的名字。
同理变量COBJS应该表示所有由.c文件编译得到的.o文件的名字,此时将所有.o替换为.c,那么得到的就是所有.c文件的名字。
所以.depend的依赖是顶层Makefile、顶层config.mk、当前目录下所有.c、.S文件。
接下来的
@for f in $(SRCS); do \ g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \ $(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \
表示看不懂,无心深入研究正则表达式,摘抄网上的一段话:
“
需要注意的是,这个规则的语法确实太麻烦,我至今没有看清楚,不过不要紧,知道它是把上述
各个源文件的依赖关系取出放入到.depend文件就行。要想弄得更清楚的话,要好好看看sed
命令的用法,这里不讨论了。
生成的.depend 文件的内容如下(给出概略形式):
.depend
---------
flash.o: flash.c xxx.h xxx.h ...
smdk2410.o: smdk2410.c xxx.h xxx.h ...
lowlevel_init.o: lowlevel_init.s xxx.h xxx.h ...
---------
”
如此来看,每个子目录下都会生成一个表示依赖关系的.depend文件,列出了各个子目录下文件之间的依赖关系。
生成的.depend文件有何作用?
子目录的Makefile中有:
sinclude $(obj).depend
子目录Makefile是如何调用.depend,看子目录的Makefile。
include $(TOPDIR)/config.mk #包含顶层目录下的config.mk LIB = $(obj)lib$(SOC).a #LIB = libs3c24x0.a COBJS-$(CONFIG_USE_IRQ) += interrupts.o COBJS-y += speed.o COBJS-y += timer.o COBJS-y += usb.o COBJS-y += usb_ohci.o COBJS-y += mmc.o
CONFIG_USE_IRQ显然是一个宏,定义在C语言中
用命令搜索 grep "CONFIG_USE_IRQ" * -nr
include/configs/mini2440.h:62:#define CONFIG_USE_IRQ 1
include/autoconf.mk:41:CONFIG_USE_IRQ=y
最初在mini2440.h定义为1,autoconf.mk是一个自动生成的文件,应该是经过转换得到CONFIG_USE_IRQ=y。
COBJS-$(CONFIG_USE_IRQ) += interrupts.o
即为COBJS-y += interrupts.o,这里加个CONFIG_USE_IRQ就使得interrupts.o在编译的时候变成了可选择项。
引用一段话:
“The kbuild Makefile specifies object files for vmlinux in the $(obj-y) lists. These lists depend on the kernel configuration.Kbuild compiles all the $(obj-y) files. It then calls "$(LD) -r" to merge these files into one built-in.o file. built-in.o is later linked into vmlinux by the parent Makefile. The order of files in $(obj-y) is significant. Duplicates in the lists are allowed: the first instance will be linked into built-in.o and succeeding instances will be ignored.( 翻译:Kbuild Makefile规定所有编译进vmlinux的目标文件都在$(obj-y)列表中,这些列表依赖于内核配置。Kbuild编译所有的$(obj-y)文件,然后调用"$(LD) -r"合并这些文件到一个built-in.o文件中. built-in.o之后通过父makefile文件链接到vmlinux. $(obj-y)中的文件顺序很重要.列表中文件允许重复,文件第一次出现将被链接到built-in.o,后续出现该文件将被忽略.)”
这里的COBJS -y应该是将.o文件加到目标文件列表中,并且是编译进内核的。至于COBJS在哪里定义,是怎么加入目标文件列表的,还有待研究.......
all: $(obj).depend $(LIB)
目标:all,依赖:当前目录下的.depend文件、libs3c24x0.a
$(LIB): $(OBJS)
$(AR) $(ARFLAGS) $@ $(OBJS)
目标:libs3c24x0.a,依赖:$(OBJS)
又有:OBJS := $(addprefix $(obj),$(SOBJS) $(COBJS-y))
addprefix 加前缀函数,前缀为$(obj),由前面可知obj为当前目录,所以这句话的意思是在$(SOBJS)和$(COBJS-y)前面加前缀,前缀为当前目录。归根结底,OBJS是所有的.o文件,不管是由.S文件生成的还是.c文件生成的。那么$(LIB): $(OBJS)可理解为libs3c24x0.a依赖的所有的.o文件
arm-linux-ar 把多个.o 合并成一个.o 或静态库.a,这里生成的显然是静态库.a。
所以在子目录下执行make,默认参数是make all,目标all依赖于.depend文件和所有.o文件。
个人理解,.o文件是“原材料”,.depend文件控制“原材料”的依赖,all依赖所有的.o文件,所有的.o文件又有各自的依赖,由.depend文件控制,通过COBJS -y被加入目标文件列表编译,这样形成一种“递归的依赖”,编译的时候也会这样“递归编译”(有点用词不当,你懂就行)。
所以第一个依赖是在各个子目录下生成.depend文件。
第二个依赖——$(SUBDIRS)
在顶层目录Makefile中做了定义:
$(SUBDIRS): $(MAKE) -C $@ all SUBDIRS = tools \ examples \ post \ post/cpu .PHONY : $(SUBDIRS)
$(SUBDIRS)用于执行tools、examples、post、post/cpu这几个目录下的make all。
所以第二个依赖是在目标表示的几个目录下执行make。
第三个依赖——$(OBJS)
顶层目录Makefile中的的定义:
OBJS = cpu/$(CPU)/start.o OBJS := $(addprefix $(obj),$(OBJS)) $(OBJS): depend $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
通过前面的内容知道,$(CPU)是arm920t,OBJS = cpu/arm920t/start.o
OBJS := $(addprefix $(obj),$(OBJS))#加上当前路径作为前缀,当前路径为根目录,即/cpu/arm920t/start.o
又有:
ifneq ($(OBJTREE),$(SRCTREE)) REMOTE_BUILD := 1 export REMOTE_BUILD Endif
所以REMOTE_BUILD为空,$(if $(REMOTE_BUILD),$@,$(notdir $@))的值为$(notdir $@),$@的值是/cpu/arm920t/start.o,notdir内嵌函数返回的文件名,即返回start.o。
展开得到Make -C cpu/arm920t start.o,依赖于/cpu/arm920t/下的depend文件,start.o就是这么生成的。
有的人或许会疑问OBJS = cpu/$(CPU)/start.o之后还有一些语句:
ifeq ($(CPU),i386) OBJS += cpu/$(CPU)/start16.o OBJS += cpu/$(CPU)/reset.o endif ifeq ($(CPU),ppc4xx) OBJS += cpu/$(CPU)/resetvec.o endif ifeq ($(CPU),mpc83xx) OBJS += cpu/$(CPU)/resetvec.o endif ifeq ($(CPU),mpc85xx) OBJS += cpu/$(CPU)/resetvec.o endif ifeq ($(CPU),mpc86xx) OBJS += cpu/$(CPU)/resetvec.o endif ifeq ($(CPU),bf533) OBJS += cpu/$(CPU)/start1.o cpu/$(CPU)/interrupt.o cpu/$(CPU)/cache.o OBJS += cpu/$(CPU)/cplbhdlr.o cpu/$(CPU)/cplbmgr.o cpu/$(CPU)/flush.o endif
很显然,通过比较CPU,都不满足条件,所以OBJS只能等于/cpu/arm920t/start.o。
所以第三个依赖为/cpu/arm920t/start.o
第四个依赖——$(LIBBOARD)
顶层目录Makefile中的的定义:
$(LIBBOARD): depend $(LIBS) $(MAKE) -C $(dir $(subst $(obj),,$@)) LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
顶层config.mk文件中:
ifdef VENDOR BOARDDIR = $(VENDOR)/$(BOARD) else BOARDDIR = $(BOARD) endif ifdef BOARD sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules endif
且VENDOR=tekkamanninja BOARD = mini2440
所以BOARDDIR = tekkamanninja/mini2440
所以目标$(LIBBOARD) 是board/tekkamanninja/mini2440/libmini2440.a,它依赖于depend文件和很多编译出来的库($(LIBS)是第五个依赖,随后讲到)。
所以第四个依赖为board/tekkamanninja/mini2440/libmini2440.a。
第五个依赖$(LIBS)
顶层目录Makefile中的的定义:
LIBS = lib_generic/libgeneric.a LIBS += lib_generic/lzma/liblzma.a LIBS += lib_generic/lzo/liblzo.a LIBS += $(shell if [ -f board/$(VENDOR)/common/Makefile ]; then echo \ "board/$(VENDOR)/common/lib$(VENDOR).a"; fi) 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 += net/libnet.a LIBS += disk/libdisk.a LIBS += drivers/bios_emulator/libatibiosemu.a LIBS += drivers/block/libblock.a LIBS += drivers/dma/libdma.a LIBS += drivers/fpga/libfpga.a LIBS += drivers/gpio/libgpio.a LIBS += drivers/hwmon/libhwmon.a LIBS += drivers/i2c/libi2c.a LIBS += drivers/input/libinput.a LIBS += drivers/misc/libmisc.a LIBS += drivers/mmc/libmmc.a LIBS += drivers/mtd/libmtd.a LIBS += drivers/mtd/nand/libnand.a LIBS += drivers/mtd/onenand/libonenand.a LIBS += drivers/mtd/ubi/libubi.a LIBS += drivers/mtd/spi/libspi_flash.a LIBS += drivers/net/libnet.a LIBS += drivers/net/phy/libphy.a LIBS += drivers/net/sk98lin/libsk98lin.a LIBS += drivers/pci/libpci.a LIBS += drivers/pcmcia/libpcmcia.a LIBS += drivers/power/libpower.a LIBS += drivers/spi/libspi.a ifeq ($(CPU),mpc83xx) LIBS += drivers/qe/qe.a endif ifeq ($(CPU),mpc85xx) LIBS += drivers/qe/qe.a LIBS += cpu/mpc8xxx/ddr/libddr.a LIBS += cpu/mpc8xxx/lib8xxx.a TAG_SUBDIRS += cpu/mpc8xxx endif ifeq ($(CPU),mpc86xx) LIBS += cpu/mpc8xxx/ddr/libddr.a LIBS += cpu/mpc8xxx/lib8xxx.a TAG_SUBDIRS += cpu/mpc8xxx endif LIBS += drivers/rtc/librtc.a LIBS += drivers/serial/libserial.a LIBS += drivers/twserial/libtws.a LIBS += drivers/usb/gadget/libusb_gadget.a LIBS += drivers/usb/host/libusb_host.a LIBS += drivers/usb/musb/libusb_musb.a # Apollo + LIBS += drivers/usb/slave/libusb_slave.a # Apollo - LIBS += drivers/video/libvideo.a LIBS += drivers/watchdog/libwatchdog.a LIBS += common/libcommon.a LIBS += libfdt/libfdt.a LIBS += api/libapi.a LIBS += post/libpost.a LIBS := $(addprefix $(obj),$(LIBS)) .PHONY : $(LIBS) $(TIMESTAMP_FILE) $(VERSION_FILE)
不难看出, uboot 的生成需要很多 lib 库文件,前几个库文件是与架构、 CPU 、 SOC 、 VENDOR 等相关的,不同开发板可能不一样,而后面一些是通用的。举个栗子:
ifdef SOC LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a endif
替换后得到LIBS += cpu/arm920t/s3c24x0/lib3sc24x0.a
这不正是我们之前分析第一个依赖depend的时候的例子吗,回忆一下,depend依赖只是在个子目录下生成了.depend文件并被子目录的Makefile包含,那么该子目录的Makefile又是被谁调用呢?
来看一下$(LIBS):
$(LIBS): $(MAKE) -C $(dir $(subst $(obj),,$@))
subst 用法是$(subst FROM,TO,TEXT),即将TEXT中的东西从FROM变为TO,$(dir names...),抽取‘names’中每一个文件名的路径部分,文件名的路径部分包括从文件名的开始到最后一个斜杠(含斜杠)。
所以$(dir $(subst $(obj),,$@))最后的值是将要编译的库文件所在的目录的路径。
我是这么理解的,LIBS是“库目标文件列表”(没有找到相关定义,我自己这么理解的......),这个列表中的所有的文件的路径会被取出,然后进入执行make,从而调用子目录的make all,生成相关的库文件。
所以第五个依赖生成了$(LIBS)所表示的库文件。
第六、七个依赖——$(LDSCRIPT) $(obj)u-boot.lds
顶层目录Makefile中的的定义:
$(obj)u-boot.lds: $(LDSCRIPT) $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@ $(LDSCRIPT): depend $(MAKE) -C $(dir $@) $(notdir $@)
顶层config.mk中:
ifndef LDSCRIPT #LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug ifeq ($(CONFIG_NAND_U_BOOT),y) LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds else LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds endif endif
LDSCRIPT和CONFIG_NAND_U_BOOT都没有被定义,所以LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds有效,替换后得到/board/tekkamanninja/mini2440/u-boot.lds
继续替换:
/board/tekkamanninja/mini2440/u-boot.lds: depend
Make -C /board/tekkamanninja/mini2440/ u-boot.lds
进入/board/tekkamanninja/mini2440/后执行make u-boot.lds,如果你足够仔细的话,会发现该目录下的Makefile并没有uboot.lds目标且没有u-boot.lds文件,所以第七个依赖是/board/tekkamanninja/mini2440/u-boot.lds,但是什么也不做。
再来分析:
$(obj)u-boot.lds: $(LDSCRIPT) $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@
这里一个一个去研究太麻烦,采用一个取巧的方法,以下是make的时候该语句$(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@的Log:
arm-linux-gcc -E -g -Os -fno-common -ffixed-r8 -malignment-traps -D__KERNEL__ -DTEXT_BASE=0x33F80000 -I/home/yin/Desktop/kangear-U-boot-2009.11/include -fno-builtin -ffreestanding -nostdinc -isystem /usr/local/arm-linux/arm-linux/bin/../lib/gcc/arm-linux/3.4.5/include -pipe -DCONFIG_ARM -D__ARM__ -marm -mapcs-32 -mno-thumb-interwork -march=armv4 -include /home/yin/Desktop/kangear-U-boot-2009.11/include/u-boot/u-boot.lds.h -DLD_MAJOR=2 -DLD_MINOR=15 -ansi -D__ASSEMBLY__ -P - u-boot.lds
$(CPP)是arm-linux-gcc -E,所以标红字的部分应该就是$(CPPFLAGS) $(LDPPFLAGS)的内容,这里就不去细致研究这两个变量了。
这些指令总的作用:对 arch/arm/cpu/arm920t/ 目录下的 u-boot.lds 文件作预处理,并将结果保存为 uboot 根目录下名叫 u-boot.lds 的文件中。
用merge工具比较一下,左边是kangear-U-boot-2009.11\cpu\arm920t\u-boot.lds,右边是kangear-U-boot-2009.11\u-boot.lds。
唯一的差别在于:.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) },没有找到SORT_BY_ALIGNMENT、SORT_BY_NAME的含义,字面意思是按照ALIGNMENT排序和按照NAME排序。
关于lds文件的解读,在之后会讲到,此处暂且不表。
所以第七个依赖生成了根部录下的u-boot.lds文件。
现在再来看一下uboot的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)
我们了解了七个依赖,接下来看规则如何利用依赖生成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
对应的build log是:
UNDEF_SYM=`arm-linux-objdump -x board/tekkamanninja/mini2440/libmini2440.a lib_generic/libgeneric.a lib_generic/lzma/liblzma.a lib_generic/lzo/liblzo.a cpu/arm920t/libarm920t.a cpu/arm920t/s3c24x0/libs3c24x0.a lib_arm/libarm.a 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 net/libnet.a disk/libdisk.a drivers/bios_emulator/libatibiosemu.a drivers/block/libblock.a drivers/dma/libdma.a drivers/fpga/libfpga.a drivers/gpio/libgpio.a drivers/hwmon/libhwmon.a drivers/i2c/libi2c.a drivers/input/libinput.a drivers/misc/libmisc.a drivers/mmc/libmmc.a drivers/mtd/libmtd.a drivers/mtd/nand/libnand.a drivers/mtd/onenand/libonenand.a drivers/mtd/ubi/libubi.a drivers/mtd/spi/libspi_flash.a drivers/net/libnet.a drivers/net/phy/libphy.a drivers/net/sk98lin/libsk98lin.a drivers/pci/libpci.a drivers/pcmcia/libpcmcia.a drivers/power/libpower.a drivers/spi/libspi.a drivers/rtc/librtc.a drivers/serial/libserial.a drivers/twserial/libtws.a drivers/usb/gadget/libusb_gadget.a drivers/usb/host/libusb_host.a drivers/usb/musb/libusb_musb.a drivers/usb/slave/libusb_slave.a drivers/video/libvideo.a drivers/watchdog/libwatchdog.a common/libcommon.a libfdt/libfdt.a api/libapi.a post/libpost.a | sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`; cd /home/yin/Desktop/kangear-U-boot-2009.11 && arm-linux-ld -Bstatic -T u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o --start-group lib_generic/libgeneric.a lib_generic/lzma/liblzma.a lib_generic/lzo/liblzo.a cpu/arm920t/libarm920t.a cpu/arm920t/s3c24x0/libs3c24x0.a lib_arm/libarm.a 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 net/libnet.a disk/libdisk.a drivers/bios_emulator/libatibiosemu.a drivers/block/libblock.a drivers/dma/libdma.a drivers/fpga/libfpga.a drivers/gpio/libgpio.a drivers/hwmon/libhwmon.a drivers/i2c/libi2c.a drivers/input/libinput.a drivers/misc/libmisc.a drivers/mmc/libmmc.a drivers/mtd/libmtd.a drivers/mtd/nand/libnand.a drivers/mtd/onenand/libonenand.a drivers/mtd/ubi/libubi.a drivers/mtd/spi/libspi_flash.a drivers/net/libnet.a drivers/net/phy/libphy.a drivers/net/sk98lin/libsk98lin.a drivers/pci/libpci.a drivers/pcmcia/libpcmcia.a drivers/power/libpower.a drivers/spi/libspi.a drivers/rtc/librtc.a drivers/serial/libserial.a drivers/twserial/libtws.a drivers/usb/gadget/libusb_gadget.a drivers/usb/host/libusb_host.a drivers/usb/musb/libusb_musb.a drivers/usb/slave/libusb_slave.a drivers/video/libvideo.a drivers/watchdog/libwatchdog.a common/libcommon.a libfdt/libfdt.a api/libapi.a post/libpost.a board/tekkamanninja/mini2440/libmini2440.a --end-group -L /usr/local/arm-linux/arm-linux/bin/../lib/gcc/arm-linux/3.4.5 -lgcc -Map u-boot.map -o u-boot
解读这段Log:
UNDEF_SYM:一直到紫色字体,这个变量实际上就是把中间这一群的静态库里面__u_boot_cmd_开头的symbol都提取出来,并在每个的开头加上了-u,以备后用。
“UNDEF_SYM is a shell variable in the main Makefile used to force the linker to add all u-boot commands to the final image.”
cd /home/yin/Desktop/kangear-U-boot-2009.11:进入根目录
接下来是链接部分:
arm-linux-ld -Bstatic -T u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o :-Bstatic 表示静态链接 、表示使用u-boot.lds,linker script来链接、-Ttext 0x33F80000 表示将text段,放到绝对地址为0x33F80000 的地方。
--start-group到--end-group之间的库是所有的archives(档案?),同一般的直接写archive不同在于,当search了一遍仍然有undefined reference的时候,会继续搜索。
-Map:什么是 MAP 文件?简单地讲, MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方、任何时候使用,不需要有额外的程序进行支持。而且,这是唯一能找出程序崩溃的地方的救星。
说实话,生成uboot的Log还是有很多不理解,对bash语言和arm-linux工具链的命令还是不够熟悉,只晓得一些常用的,以后多多研究吧。
3.配置编译总结
1)过程总结
a.终端输入make mini2440_config
b.到Makefile:
mini2440_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t mini2440 tekkamanninja s3c24x0
c.到顶层mkconfig执行:
1.完成一些目录的软连接。
2.生成include/config.mk文件,存放配置参数
3.生成include/config.h文件,给C语言调用。
d.终端输入make
调用
all: $(ALL)
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)
e.u-boot.bin依赖于uboot
f.uboot依赖于depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LIBS) $(obj)u-boot.lds
depend在各个子目录下生成.depend文件。
$(SUBDIRS)在目标表示的几个目录下执行make。
$(OBJS)为/cpu/arm920t/start.o
$(LIBBOARD)为board/tekkamanninja/mini2440/libmini2440.a
$(LIBS)生成了$(LIBS)所表示的库文件。
$(LIBS) do nothing
$(obj)u-boot.lds生成了根部录下的u-boot.lds文件。
然后通过规则,利用依赖来生成uboot。
g. 顶层Makefile中:
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
生成uboot.bin文件。
2)比较杂的知识点(持续补充中)
(1).a为静态库,是好多个.o合在一起,用于静态连接,这一点从lib库文件的依赖就可以看出来