uboot——配置编译

一.什么是uboot?

     uboot: Universal Bootloader,是一种普遍的嵌入式系统bootloader。本篇以mini2440开发板为例.


二.uboot的作用

最终目标:启动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 ?

对于我这样一个菜鸟来说,这个问题有点尴尬......

个人理解,无论学习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的配置编译


1.配置

我们在编译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

想要想编译mini2440bootloader,就需要我们自己添加一笔。下面来详细解读一下这些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.hconfig.mkboard/*/config.tmpboard/*/*/config.tmp,值得一提的是顶层目录下的config.mk用于设置编译选项,这里暂且不表。

接下来执行: 

@$(MKCONFIG) $(@:_config=) arm arm920t mini2440 tekkamanninja s3c24x0

顶层MakefileMKCONFIG的定义是: 

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 s3c24x0mkconfig 本身也作为bash的参数),分别对应参数06

接下来看顶层目录下的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: 定义芯片架构(如MIPSPOWERPCARM等)

CPU: 定义芯片指令集版本(如ARM7ARM9ARM11等)

Board: 字面意思......

[VENDOR]: 按厂商划分(如AT9200S3C44B0等),这里一般写NULL,移植的这位大神写上了自己的名字.......

[SOC]: 系统芯片,按SOC类型(如S3C2440S3C2410等)

 

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个参数,即参数16,显然大于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后发生的,配置完就可以编译了。

 
   
   
  
 
  

 

2.编译

命令: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.srecu-boot.binSystem.mapu-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 = 0xffobjcopy的参数,表示在拷贝过程中,用0xff来填充段与段之间的空隙。(这个以前不知道,长姿势了)

$< 表示第一个依赖,$@表示目标。

编译的规则都定义在顶层的config.mk中。

 

u-boot.srecu-boot.binSystem.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.11uboot的生成。

 

先了解一下各个依赖文件是什么: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds

 

第一个依赖——depend 

在顶层Makefile中:

depend dep:
for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done

两个目标dependdep,他们是一样的。然后利用一个循环,在所有的子目录中使用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,依赖顶层目录下的Makefileconfig.mk,这应该是为了使用这两个文件中的一些变量和编译选项,用到的时候再做分析。

还有一个一依赖是$(SRCS)

SRCS := $(SOBJS:.o=.S) $(COBJS-y:.o=.c)

我没有找到SOBJSCOBJS的定义,变量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定义为1autoconf.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)用于执行toolsexamplespostpost/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)arm920tOBJS  = 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.onotdir内嵌函数返回的文件名,即返回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

LDSCRIPTCONFIG_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_ALIGNMENTSORT_BY_NAME的含义,字面意思是按照ALIGNMENT排序和按照NAME排序。

关于lds文件的解读,在之后会讲到,此处暂且不表。

uboot——配置编译_第1张图片

所以第七个依赖生成了根部录下的u-boot.lds文件。

 

现在再来看一下ubootELF格式是如何生成的

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 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方、任何时候使用,不需要有额外的程序进行支持。而且,这是唯一能找出程序崩溃的地方的救星。 

 

说实话,生成ubootLog还是有很多不理解,对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库文件的依赖就可以看出来







































你可能感兴趣的:(Uboot,uboot,arm,makefile,移植,嵌入式系统)