您的位置:首页 > 其它

“本命年计划”001—U-boot顶层Makefile的详细分析总结(结合众多网上高手资料)

2011-03-06 14:07 513 查看
VERSION = 1
PATCHLEVEL = 1
SUBLEVEL = 6
EXTR***ERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTR***ERSION) # $(xxx)相当于c中的宏定义,$(VERSION)即为 1,因为 VERSION = 1 ,其他三个类似
VERSION_FILE = $(obj)include/version_autogenerated.h #定义版本文件

# uname 将正在使用的操作系统名写到标准输出中
# -m 显示硬件运行系统的机器 ID 号
#定义变量,HOSTARCH里面存储的是机器ID号,即主机架构类型 | 表示管道 $(shell xxx):xxx表示shell命令,整个就表示执行shell命令
#sed 是一种在线编辑器,它一次处理一行内容。
#处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。
#接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等
# sed -e '1,5d' -e 's/test/check/' example-----(-e)选项允许在同一行里执行多条命令。
#如例子所示,第一条命令删除1至5行,第二条命令用check替换test。命令的执行顺序对结果有影响。
#如果两个命令都是替换命令,那么第一个替换命令将影响第二个替换命令的结果
HOSTARCH := $(shell uname -m | /
sed -e s/i.86/i386/ / #i386替换成i.86
-e s/sun4u/sparc64/ /
-e s/arm.*/arm/ / #把uname -m的结果(主机架构类型或者称为机器ID号)通过管道传递给sed命令,然后把前缀为arm的所有模式替换为arm
-e s/sa110/arm/ /
-e s/powerpc/ppc/ /
-e s/macppc/ppc/)

#定义变量HOSTOS里面存放的是主机安装的,并且当前正在运行的操作系统
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | / sed -e 's//(cygwin/).*/cygwin/') #tr是一个shell命令,可以实现许多sed的功能,这里 tr '[:upper:]''[:lower:]'的意思是把管道中的Linux 中的大写字母L 转换成小写字母l
#这一句的意思是检测出主机安装的,并且当前正在运行的操作系统名,并把这个系统名中的大写字母转换为小写字母

# 然后在通过sed流编辑器匹配所有的,这个系统名中出现的"/(cygwin/).*"模式,然后再用"cygwin"模式替换。
export HOSTARCH HOSTOS #输出两个makefile变量HOSTARCH和HOSTOS

# Deal with colliding definitions from tcsh etc. #用来处理来自tcsh的互相冲突的定义等等
# 一般来说,shell可以分成两类。第一类是由 Bourne shell 衍生出来的包括 
# sh,ksh,bash,与zsh。第二类是由 C shell 衍生出来的,包括 csh 与 
# tcsh。除此之外还有一个rc,有人认为该自成一类,有人认为该归类在Bourne shell。
VENDOR= #开发商

#########################################################################
#
# U-boot build supports producing a object files to the separate external
#U-boot 的编译过程可以支持向一个自己定义的路径生成最终的目标文件
# directory. Two use cases are supported:
#这里提供两种方法
# 1) Add O= to the make command line #第一种用法:通过在终端执行命令make O=/dir(即你指定的生成的目标文件的存放目录)
# 'make O=/tmp/build all'
#
# 2) Set environement variable BUILD_DIR to point to the desired location #第二种用法:通过设置环境变量来指定目标文件存放目录,如下所示:
# 'export BUILD_DIR=/tmp/build'
# 'make'
#
# The second approach can also be used with a MAKEALL script #第二种方法也可以写成一个MAKEALL脚本,然后执行MAKEALL,如下所示:
# 'export BUILD_DIR=/tmp/build'
# './MAKEALL'
#
# Command line 'O=' setting overrides BUILD_DIR environent variable. #命令行'O='设置会覆盖环境变量BUILD_DIR的设置
#
# When none of the above methods is used the local build is performed and #如果都不采用上面两种方法,那么目标文件放到源码顶层目录,也就是U-BOOT顶层目录
# the object files are placed in the source directory.
#
#方法1
ifdef O #如果O已经被定义
ifeq ("$(origin O)", "command line") #如果‘O’在命令行中被定义
BUILD_DIR := $(O) #那就把‘O’的值赋给 BUILD_DIR
endif
endif
#方法2
ifneq ($(BUILD_DIR),) #如果 BUILD_DIR 不为空
saved-output := $(BUILD_DIR) #那就把BUILD_DIR的值赋给saved-output

# Attempt to create a output directory. #生成一个输出路径,即目标文件存放目录BUILD_DIR
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) # 判断build_dir是不是一个目录 ,如果没有就创建, [ ] 就是个条件判断语句

# Verify if it was successful. 测试目录是否创建成功
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd) # 进入BUILD_DIR这个目录,并且用pwd这个命令显示当前路径,然后将这个路径赋给BUILD_DIR
$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist)) #这里用了一个if函数,意思是如果如果$(BUILD_DIR) 非空,则什么都不执行(返回空),否则执行error函数,输出错误信息
endif # ifneq ($(BUILD_DIR),)
# ifneq ($(BUILD_DIR),) #意思是:如果没有定义目标文件存放目录
#Makefile中定义了源码以及生成目标文件存放的目录,目标文件存放目录BUILD_DIR可以通过make O=dir指定。如果没有指定,则设定为源码顶层目录。
#一般编译的时候不指定输出目录,则BUILD_DIR为空。其他目录变量如下:
#TOPDIR SRCTREE OBJTREE这三个目录会给下层的makefile调用,需要在这里指定并export
#OBJTREE和LNDIR为存放生成文件的目录,TOPDIR与SRCTREE为源码所在目录
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR)) #如果$(BUILD_DIR)不为空,则返回$(BUILD_DIR),并赋给OBJTREE,即自己定制的目标存放目录
SRCTREE := $(CURDIR) #把当前源码所在目录 $(CURDIR) 赋给SRCTREE
TOPDIR := $(SRCTREE) #把当前源码所在目录 $(CURDIR) 赋给TOPDIR
LNDIR := $(OBJTREE) #存放生成的目录文件
export TOPDIR SRCTREE OBJTREE

MKCONFIG := $(SRCTREE)/mkconfig #定义变量MKCONFIG:这个变量指向一个脚本,即顶层源码所在目录的mkconfig。
export MKCONFIG #导出这个变量

ifneq ($(OBJTREE),$(SRCTREE)) #如果OBJTREE 与 SRCTREE所表示的路径不同
REMOTE_BUILD := 1 #设定变量REMOTE_BUILD =1,相当于flag的作用
export REMOTE_BUILD #导出这个变量
endif

# $(obj) and (src) are defined in config.mk but here in main Makefile #$(obj) and $(src)都被定义在顶层目录下的config.mk脚本配置文件里面,
# we also need them before config.mk is included which is the case for #但是在这个主Makefile里面,我们同样需要他们,
# some targets like unconfig, clean, clobber, distclean, etc. #因为在主Makefile文件包含config.mk之前,$(obj) and $(src)偶尔地会成为象unconfig, clean, clobber, distclean, etc这些目标的case:
ifneq ($(OBJTREE),$(SRCTREE)) #当目标存放目录不是U-BOOT顶层目录(源码目录)时
obj := $(OBJTREE)/ #定义obj,让其等于目标存放目录
src := $(SRCTREE)/ #定义src,让其等于源码所在目录,即u-boot顶层目录
else #否则就全置为空
obj :=
src :=
endif
export obj src #导出这两个目录

#########################################################################
# 通配符在规则中可以自动扩展,但设置在变量中或在函数的参数中通配符一般不能正常扩展。
# 如果您需要在这些场合扩展通配符,您应该使用函数wildcard,格式如下:
# $(wildcard pattern...)
ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))

# load ARCH, BOARD, and CPU configuration 下载相关配置
include $(OBJTREE)/include/config.mk #上面两个config.mk相同了才包含config.mk
export ARCH CPU BOARD VENDOR SOC

ifndef CROSS_COMPILE #如果没定义交叉编译器
ifeq ($(HOSTARCH),ppc)
CROSS_COMPILE =
else
ifeq ($(ARCH),ppc)
CROSS_COMPILE = powerpc-linux-
endif
#这里你可以把交叉编译器的安装路径加到arm-linux-之前,比如你的交叉编译器安装路径是/root/u-boot/usr/local/arm/3.3.2/bin/
#你可以这样定义CROSS_COMPILE = /root/u-boot/usr/local/arm/3.3.2/bin/arm-linux-
#这样一来,你在终端进行编译的时候就不用指定CROSS_COMPILE=arm-linux-了
#但请注意:在编译内核的时候,交叉编译器必须安装在/usr/local/arm下,否则会发生错误!!!!!
ifeq ($(ARCH),arm) #如果ARCH = arm
CROSS_COMPILE = arm-linux- #指定交叉编译器的路径
endif
ifeq ($(ARCH),i386)
ifeq ($(HOSTARCH),i386)
CROSS_COMPILE =
else
CROSS_COMPILE = i386-linux-
endif
endif
ifeq ($(ARCH),mips)
CROSS_COMPILE = mips_4KC-
endif
ifeq ($(ARCH),nios)
CROSS_COMPILE = nios-elf-
endif
ifeq ($(ARCH),nios2)
CROSS_COMPILE = nios2-elf-
endif
ifeq ($(ARCH),m68k)
CROSS_COMPILE = m68k-elf-
endif
ifeq ($(ARCH),microblaze)
CROSS_COMPILE = mb-
endif
ifeq ($(ARCH),blackfin)
CROSS_COMPILE = bfin-elf-
endif
ifeq ($(ARCH),avr32)
CROSS_COMPILE = avr32-
endif
endif
endif

export CROSS_COMPILE #导出交叉编译器

# load other configuration
#加载其他设置,这里是包含顶层目录下的config.mk配置文件,这个文件主要做了三个工作:
# 1、定义了交叉编译器 2、定义了编译选项 3、定义了编译规则
include $(TOPDIR)/config.mk

#########################################################################
# U-Boot objects....order is important (i.e. start must be first)
#start.o必须放在目标文件的第一位,因为uboot执行的第一段代码就是start.S
#下面这一段是根据我们$(cpu)来确定OBJS
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

OBJS := $(addprefix $(obj),$(OBJS)) #这句的意思是把目标文件存放路径以前缀的形式加到start.O之前,然后再赋给OBJS
#以下是编译UBOOT需要的库文件
LIBS = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a #根据所用平台
LIBS += cpu/$(CPU)/lib$(CPU).a #根据所用平台
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a #根据所用平台
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a #根据所用平台
#通用的库文件
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a /
fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)

LIBS := $(addprefix $(obj),$(LIBS))
.PHONY : $(LIBS) #伪目标
#根据所生成的include/config.mk文件定义的几个变量ARCH, CPU, BOARD, SOC,我们可以
#确定硬件平台依赖的目录文件。smdk2410平台相关(依赖)目录以及对应生成的库文件如下:
#board/smdk2410/: 库文件board/smdk2410/libsmdk2410.a
#cpu/arm920t/: 库文件cpu/arm920t/libarm920t.a
#cpu/arm920t/s3c24x0: 库文件cpu/arm920t/s3c24x0/libs3c24x0.a
#lib_arm: 库文件lib_arm/libarm.a
#include/asm-arm: 头文件
#include/cnofigs/smdk2410.h:头文件
# Add GCC lib
PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc

# The "tools" are needed early, so put this first
# Don't include stuff already done in $(LIBS) 不要包含已经在 $(LIBS) 中的任何东西
# 伪目标SUBDIRS:用于执行tools、examples、post、post/cpu子目录下的make文件
SUBDIRS = tools /
examples /
post /
post/cpu
.PHONY : $(SUBDIRS)

ifeq ($(CONFIG_NAND_U_BOOT),y)
NAND_SPL = nand_spl
U_BOOT_NAND = $(obj)u-boot-nand.bin
endif

__OBJS := $(subst $(obj),,$(OBJS))
__LIBS := $(subst $(obj),,$(LIBS))

#########################################################################
#########################################################################
#下面最终生成各种镜像文件,最后要生成的文件,U_BOOT_NAND在前面定义了,为u-boot-nand.bin
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
#all依赖于$(ALL) 最终生成镜像文件,注意各种依赖关系
all: $(ALL)

$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@

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

$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

$(obj)u-boot.img: $(obj)u-boot.bin
./tools/mkimage -A $(ARCH) -T firmware -C none /
-a $(TEXT_BASE) -e 0 /
-n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | /
sed -e 's/"[ ]*$$/ for $(BOARD) board"/') /
-d $< $@

$(obj)u-boot.dis: $(obj)u-boot
$(OBJDUMP) -d $< > $@

$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*/(__u_boot_cmd_.*/)/-u/1/p'|sort|uniq`;/
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) /
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) /
-Map u-boot.map -o u-boot

$(OBJS):
$(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))

$(LIBS):
$(MAKE) -C $(dir $(subst $(obj),,$@))

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

$(NAND_SPL): version
$(MAKE) -C nand_spl/board/$(BOARDDIR) all

$(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

version:
@echo -n "#define U_BOOT_VERSION /"U-Boot " > $(VERSION_FILE); /
echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); /
echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion /
$(TOPDIR)) >> $(VERSION_FILE); /
echo "/"" >> $(VERSION_FILE)

gdbtools:
$(MAKE) -C tools/gdb all || exit 1

updater:
$(MAKE) -C tools/updater all || exit 1

env:
$(MAKE) -C tools/env all || exit 1

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

tags ctags:
ctags -w -o $(OBJTREE)/ctags `find $(SUBDIRS) include /
lib_generic board/$(BOARDDIR) cpu/$(CPU) lib_$(ARCH) /
fs/cramfs fs/fat fs/fdos fs/jffs2 /
net disk rtc dtt drivers drivers/sk98lin common /
/( -name CVS -prune /) -o /( -name '*.[ch]' -print /)`

etags:
etags -a -o $(OBJTREE)/etags `find $(SUBDIRS) include /
lib_generic board/$(BOARDDIR) cpu/$(CPU) lib_$(ARCH) /
fs/cramfs fs/fat fs/fdos fs/jffs2 /
net disk rtc dtt drivers drivers/sk98lin common /
/( -name CVS -prune /) -o /( -name '*.[ch]' -print /)`

$(obj)System.map: $(obj)u-boot
@$(NM) $< | /
grep -v '/(compiled/)/|/(/.o$$/)/|/( [aUw] /)/|/(/./.ng$$/)/|/(LASH[RL]DI/)' | /
sort > $(obj)System.map

#########################################################################
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*$$//' > $@



#########################################################################

unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk /
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
# 这句话已经分析过了,就是在mkconfig是先执行的操作,即删除上一次makeXXX时存在的头文件和包含文件
#接下来很长一段是与平台和开发板相关的xxx_config定义,譬如在执行make smdk2410_config时
#会找到如下定义:
smdk2410_config : unconfig

省略了很长的一段

找到了smdk2410_config : unconfig

#在分析这个及以下命令之前,我们需要了解在在编译U-BOOT之前,先要执行# make smdk2410_config
#smdk2410_config是Makefile的一个目标,定义如下:
#smdk2410_config : unconfig
# @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
#unconfig:
#@rm -f $(obj)include/config.h $(obj)include/config.mk /
# $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
#首先执行unconfig,obj src为上面所定义的,这个命令清理上一次执行make *_config时生成的头文件和makefile的包含文件。主要是include/config.h 和include/config.mk文件。
#然后再执行@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 Null s3c24x0
#MKCONFIG 是顶层目录下的mkcofig脚本文件,后面五个是传入的参数。顶层目录下的mkcofig稍后分析
#主要生成生成Makefile包含文件include/config.mk,用五个传入的参数定义五个变量
#ARCH = arm
#CPU = arm920t
#BOARD = smdk2410
#VENDOR = NULL
#SOC = s3c24x0

smdk2410_config : unconfig #$(@:_config=)就是将“smdk2410_config”中的“_config”去掉,结果为“smdk2410”。
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0 #传入5个参数

剩下的我就不详细分析了。。时间有限,能力有限。。呵呵

最后总结一下,当然这里也参考了前辈们的许多宝贵经验,顶层Makefile的主要任务就是组织整个u-boot工程的编译,概括可以分为一下几个步骤:

1、首先通过执行make *_config传入$(@:_config=), ARCH, CPU, BOARD, VENDOR, SOC参数(一共六个参数但不

一定同时存在),给mkconfig。

2、mkconfig接收到传递过来的参数后,将include头文件夹相应的头文件夹链接好,生成config.h

3、然后执行make分别调用各个子目录的makefile文件,以生成所有的obj文件(包括start.o)和obj库文件*.a。

4、最后,通过链接器把所有目标文件链接起来,生成uboot镜像。不同格式的镜像都是调用相应工具,经

由elf镜像间接或者直接的生成的。

5.链接脚本/u-boot-1.1.6/board/smdk2410/u-boot.lds和config.mk的解释请看我转来的两篇文章,讲的很详细也很好懂

总算是大致分析完了,花了我将近一天的时间,参考了很多篇文章,分析到这里,对理解U—boot架构和编译应该是很有帮助的

001步就到这了,002步就是分析 start.s了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: