U-boot分析1Makefile分析详解

来源:互联网 发布:刺客信条大革命优化差 编辑:程序博客网 时间:2024/05/16 13:46

分析启动代码一直是我的一个小小的理想,很长时间找不到方法,后来读了一些基础的书,看了了一些视频总算的有些入门了,因此自己同样想分析一些心得,看看朋友们是否也和我一样在某些方面存在问题。

闲言少许,下面开始分析,这里先下载u-boot-1.1.6版本,不要被代码吓怕,只要我们知道学习的流程没有什么可怕的,分析U-boot首先分析Makefile,这里建议朋友们打开两本书,《Makefile编程》和《高级Bash脚本编程指南》,如果有毅力的朋友们可以读一下,这里我希望作为两本字典,我们用到什么看不懂的查找一下可以提高我们的学习效率。

打开文件后,我们开始逐步分析

#解释uboot的版本
 #主版本号
VERSION = 1
#补丁版本号
PATCHLEVEL = 1 
#子版本号
SUBLEVEL = 6   
#扩展版本号
EXTRAVERSION = 
#定义版本号变量
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)

涉及到变量的引用建议大家看看《高级Bash脚本编程指南》的变量部分。

obj在后面定义

VERSION_FILE = $(obj)include/version_autogenerated.h

#uname -m 输出处理器架构名称
#sed一般用于过滤,-e多个sed命令选项,s/re/string用string替换正则表达式re
#:=函数调用一次赋值,=调用变量就得调用一次函数
不可取
#=的好处就如上面VERSION_FILE的定义,obj可以再下面定义。

HOSTARCH := $(shell uname -m | \
sed -e s/i.86/i386/ \
  -e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
   -e s/sa110/arm/ \
   -e s/powerpc/ppc/ \
   -e s/macppc/ppc/)

#tr 转换命令后面参数是正则表达式,将所有大写字符转换成小写字符
#\(\)正则表达式
#cygwin应用平台,这里我们用linux 这个不用管了
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
   sed -e 's/\(cygwin\).*/cygwin/')

#导出环境变量
export HOSTARCH HOSTOS

#shell版本是tcsh需要定义冲突处理
# Deal with colliding definitions from tcsh etc.
VENDOR=

#make 目标文件位置定义,(1)make O=dir(2)设置BUILD_DIR=dir 
#export BUILD_DIR=/tmp/build直接./MAKEALL
#origin是函数,O是否为命令行参数
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif


#如果BUILD_DIR不为空,将值=saved-output
ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)


# Attempt to create a output directory.
# 创建一个输出目标文件目录
#||逻辑或前面的如果为真,后面的就不执行了
#[ -d ${BUILD_DIR} ]是个 判断语句,不一定非得有if
#(mkdir)-p 即使父目录不存在也会自动创建
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})


#进入目录,执行pwd命令,校验BUILD_DIR是否存在
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)


#if 三段式判断if(a,b,c)a为假执行b,b为假执行c
$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
endif # ifneq ($(BUILD_DIR),)


#目标树
#CURDIR为makefile内置变量,值为当前目录
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
#源码树
SRCTREE := $(CURDIR)
#源码树
TOPDIR := $(SRCTREE)
#目标树
LNDIR := $(OBJTREE)
#导出环境变量
export TOPDIR SRCTREE OBJTREE
#定义MKCONFIG指向一个脚本
MKCONFIG := $(SRCTREE)/mkconfig
#导出
export MKCONFIG


#如果目标路径和源码路径系统定义REMOTE_BUILD=1
ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD := 1
export REMOTE_BUILD
endif


#$(obj) and (src)这两个变量在config.mk定义,指定这两个变量的值
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src


#wildcard函数寻找和config.mk一样的文件名,相同则执行下面
ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))


#导入 ARCH, BOARD, and CPU配置信息(架构,硬件板子和CPU)
#这里停止读取该Makefile 转去读$(OBJTREE)/include/config.mk文件,读完在回来-_-,
#将得到的 ARCH CPU BOARD VENDOR SOC值导出
include $(OBJTREE)/include/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


ifeq ($(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
#导入其他配置文件
include $(TOPDIR)/config.mk


#########################################################################
# U-Boot objects....order is important (i.e. start must be first)
#U-boot需要的目标文件,顺序很重要,start.o必须放第一位,
#针对不同架构,安排目标文件的布局,对于不同的CPU追加相应的目标文件。
#这段代码,其#实也是定义变量,根据不同的条件,给变量定义不同的值。
#这些变量都是在后面编译的时
#候,写到运行命令参数里面的,这些变量的意义,还有各种参数的意义,可以查看相应的
#工具的参数手册来得到,跟移植没有什么太大的关系
#需要注意一下各个变量值所添加的顺序,第一个添加的,编译的时候,变量进行替换,就
#会被放在前面,然后就会被首先编译,连接,以此类推。可以看到,最先执行的就是start.o
#这个文件里的内容,这个文件是由start.S来生成的


# 不同的CPU加载不同的目标编译

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.ocpu/$(CPU)/cache.o
OBJS += cpu/$(CPU)/cplbhdlr.o cpu/$(CPU)/cplbmgr.ocpu/$(CPU)/flush.o
endif
#为每个目录加个前缀(obj路径)
OBJS := $(addprefix $(obj),$(OBJS))
#加载静态库
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)
#为每个目录加个前缀(obj路径)
LIBS := $(addprefix $(obj),$(LIBS))
#伪目标,利用这个进行并行递归
.PHONY : $(LIBS)


# Add GCC lib
# 添加gcc库
#dirname 去掉路径的文件名只输出路径
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)
#tools文件下的内容要很早需要,所以放到一个
#不要包含stuff,在$(LIBS)已经做了
SUBDIRS = tools \
 examples \
 post \
 post/cpu
.PHONY : $(SUBDIRS)
#是否配置nand的uboot
ifeq ($(CONFIG_NAND_U_BOOT),y)
NAND_SPL = nand_spl
#目标文件名
U_BOOT_NAND = $(obj)u-boot-nand.bin
endif
#subst函数将$(OBJS)中的$(obj)替换成空赋值给__OBJS
__OBJS := $(subst $(obj),,$(OBJS))
#subst函数将$(LIBS)中的$(obj)替换成空赋值给__OBJS
__LIBS := $(subst $(obj),,$(LIBS))



#########################################################################
#这里就是定义了各种目标的target。我们最终想要的目标就是$(obj)u-boot,
#然后看后面的,#它都信赖了好多其它的目标,然后这些个目标,也是在此处定义的,
#然后有相应的$(MAKE)
#来执行相应的操作,所以这样就实现了一个Makefile文件,
#这里介绍很多个其它的Makefile文件来编译整个工程的情况。
#########################################################################
#定义编译生成的目标文件
#all 为伪目标,makefile文件自动处理第一个出现的目标,这里先编译all代表的目标
#其他伪目标放在后面则需要make 加 伪目标名进行编译
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
#u-boot.srec U-Boot映像的S-Record格式
#u-boot.bin U-Boot映像原始的二进制格式
#System.map U-Boot映像的符号表
###################################################
#U-Boot的3种映像格式都能烧写到Flash中,但需要看加载器能否识别这些格式。
#一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就能了。
#U-Boot和u-boot.srec格式映像都自带定位信息。
all: $(ALL)
#拷贝u-boot.hex
#${OBJCFLAGS}在config.mk中 OBJCFLAGS += --gap-fill=0xff
#$u-boot.hex通过(OBJCOPY)拷贝u-boot得到的ihex格式的目标文件
#实际上就是将elf格式去头去尾,并将各segment填充合并。
#OBJCOPY是gnu的工具链之一,主要是copy成不同格式所用的命令
$(obj)u-boot.hex: $(obj)u-boot #elf格式
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
#拷贝同上u-boot.srec
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
#拷贝同上u-boot.bin
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
#使用u-boot.bin编译u-boot.img,压缩型的镜像-C none表示不压缩
#sed 这里为替换命令s/,$(obj)include/version_autogenerated.h中.*U_BOOT_VERSION替换空
#-n 和/p一起使用表示打印替换的行
#sed如果使用shell变量需要"",否则用''
#-A指定体系结构
#-T指定镜像类型
#-C是否压缩
#-a指定镜像在内存中加载地址
#-e映象运行的入口点地址
#-n指定映象名
#-d指定制作映象的源文件
$(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 $< $@
#编译u-boot.dis
#OBJDUMP(查看文件类似 cat) -d 反汇编那些应该还有指令机器码的section
$(obj)u-boot.dis: $(obj)u-boot
$(OBJDUMP) -d $< > $@

#编译u-boot
#依赖项 depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
#sort 排序
#uniq 过滤重复行
$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
#查找各源文件中的u_boot_cmd段。
#如果不想逐条分析,可以直接make,把编译提示的信息最后面的部分拿过来看一下
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
#链接各源程序为u-boot可执行文件,并生成u-boot.map文件。
#U-boot中包含了一些调试信息,比如说debug_frame ,debug_info等。
#u-boot.map中记录了各函数/变量的地址。

#$(MAKE)用于make 递归调用,-C代表修改子目录,M表示回到目录
#如:$(MAKE) -C /usr/src/linux-source-2.6.15/ M=/home/vmeth modules
#上面等价cd/usr/src/linux-source-2.6.15/ && $(MAKE) M=/home/vmeth modules
#编译子目录cpu/$(CPU),如果REMOTE_BUILD为0,OBJS为空,过滤出OBJS的非目录文件一起编译
$(OBJS):
$(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
#subst 将LIBS中的$(obj)替换成空
$(LIBS):
$(MAKE) -C $(dir $(subst $(obj),,$@))
#编译SUBDIRS下的所有all伪目录
#其实就是到SUBDIRS的目录下,运行make all命令。
$(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
#@echo控制命令回显
# >拷贝到目标文件中,并覆盖源文件
# >>拷贝到目标文件的末尾,不覆盖源文件
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
#所有在命令或者文件名中使用“$”时需要用两个美元符号“$$”来表示
#实际使用时,如果在函数的展开结果中存在引用(格式为:$(x)),
#那么在函数的参数中应该使用“$$”来代替“$”,也就是需要二次展开的话
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*$$//' > $@
#这里就明说了,如果$(OBJTREE)/include/config.mk文件不存在,则编译不能进行下去了。
#########################################################################
#删除配置信息
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk \
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp



后面的代码就是针对不同的平台进行的配置,这篇文章今后我还会继续补充,也希望朋友们指正哪里需要更加详细的了解。