顶层Makefile对于uboot的配置和编译都非常重要,对顶层Makefile的分析有助于进一步的了解uboot的启动流程和uboot的配置及移植,uboot的其它内容分析可以参考博客《序言和目录》。
顶层Makefile的内容主要结构为:
- 确定版本号及主机信息
- 实现静默编译功能
- 设置各种路径
- 设置编译工具链
- 设置规则
- 设置与cpu相关的伪目标
需要注意的是,结构顺序并不代表代码执行顺序。
1.确定版本号及主机信息
VERSION = 2010
PATCHLEVEL = 06
SUBLEVEL =
EXTRAVERSION =
ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif
TIMESTAMP_FILE = $(obj)include/timestamp_autogenerated.h
VERSION_FILE = $(obj)include/version_autogenerated.h
- 最开始四个变量的含义依次为:主版本号、次版本号、再次版本号、附加的版本信息(值为空,可以给用户使用)
- U-Boot_VERSION 这个变量的值为真正的uboot版本号,其值为2010.06
- VERSION_FILE变量的值是一个.h文件
- timestamp_autogenerated.h 这个文件是在make之后自动生成的,文件内容是一条宏,这条宏给将其他.c文件提供uboot的编译时间
- version_autogenerated.h这个文件是在make之后自动生成的,文件内容是一条宏,这条宏给将其他.c文件提供uboot的版本号
HOSTARCH := $(shell uname -m | \
sed -e s/i.86/i386/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
-e s/sa110/arm/ \
-e s/ppc64/powerpc/ \
-e s/ppc/powerpc/ \
-e s/macppc/powerpc/)
- makefile的函数调用与变量调用很类似,格式是$( function arguments),其实上面一大段的意思是变量HOSTARCH的值是一个函数的返回值shell是makefile中的一个函数,$(shell XXX)会被解析成执行shell命令XXX;此处是执行了一条 uname -m |sed -e ……,符号 \是makefile的换行符其中,|是shell语法中的管道结构,例如:XXX | YYY ,表达式XXX 的输出将作为表达式YYY的输入,YYY的输出才是整句表达式的输出uname -m 指令将输出负责编译的主机cpu架构,比如ixx86;sed -e是替换命令,比如把ixx86替换为i386,由此可见这个HOSTARCH变量的值将得到负责编译的主机cpu架构。大部分情况下我们得到的都是i386
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
- 这个HOSTOS变量和上一句HOSTARCH变量的原理类似,管道第一部分uname -s会得到负责编译的主机的OS,比如Linux
- 管道第二部分是将大写转换成小写
- 管道第三部分的意思是如果前面一个部分得到了cygwin系统,则格式要转换一下。不必深究,因为cygwin基本没人用……
- 由此可见这个HOSTOS变量的值将得到负责编译的主机操作系统,大部分情况下我们得到的都是linux
# Set shell to bash if possible, otherwise fall back to sh
SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi; fi)
export HOSTARCH HOSTOS SHELL
- SHELL定义使用哪种sh解释器来解释脚本,BASH的值为/bin/sh也可能为,整个语句的意思是判断/bin/sh是否可执行
- 如果可执行将/bin/sh 赋值给变量SHELL
- 如果不可执行,进一步判断/bin/bash的可执行性
- 如果/bin/sh和/bin/bash都不可执行,则直接将sh赋值给SHELL变量
- export HOSTARCH HOSTOS SHELL
- 导出上面三个变量到全局,使其为环境变量,让其他的文件也可以使用架构和系统信息
2.实现静默编译功能
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
- 这整段是为了实现make的静默选项功能,其中,findstring一个函数,$(findstrings, $ (MAKEFLAGS))功能是从$(MAKEFLAGS)中找出字符‘s’
- $(MAKEFLAGS)是make的flag(选项),如果在控制台中输入make -s,则$(MAKEFLAGS)的值为‘s’
- 如果$(findstring $(MAKEFLAGS))没找到‘s’,这个表达式的值为空,则ifeq()为真,即make时无需静默
- 无需静默的实现方法是令变量XECHO值为关键字echo,因为makefile中每次需要打印都会使用XECHO,而不是直接使用echo本身
- 静默make的实现方法是令变量XECHO值为空,当makefile需要打印时会调用XECHO,由于其为空,故无法打印make信息
3.设置各种路径
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif
- 这里开始阐述了makefile所支持的“单独外部路径编译”,
- 它的原理和keil把.o、.l、.bin、.a等中间文件放在单独的文件夹内的原理相同,都是为了使源码更简洁明了,
- 防止被中间文件污染;以及为了同时维护多个配置编译方式
- 若要使用单独外部路径编译,有两种方法,
- 方法一:例如在控制台中输入 make O=/tmp/build ,将输出文件夹路径作为参数
- 方法二:导出环境变量,即export BUILD_DIR=/tmp/build
- 注:方法一的优先级高,会覆盖方法二。
- 而且方法一必须每次输入make时都要输入参数(不论是make clean还是make config 的时候),
- 格式如make O=/tmp/build disclean
ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)
# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
- 这句是shell语法中简写的if表达式,其作用是当输出文件夹路径不存在时就创建它
- 其中包含两个表达式,表达式1||表达式2,当表达式1为真时,表达式2不会被解释器执行,因为总结果一定为真,(尽管总的结果没有意义)。唯有表达式1为假时,解释器才会去执行表达式2,这样就能用逻辑表达式来实现if语句的功能了
- 表达式1中,方括号是固定用法,是为了突出里面表达式的作用是判断语句。-p是shell中判断路径是否存在的符号,如果路径存在则为表达式1为真;如果路径不存在,表达式1为假,解释器会执行表达式2,即创建输出文件夹路径。
# Verify if it was successful.
BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
endif # ifneq ($(BUILD_DIR),)
- 这整个一段功能是确保输出文件夹路径创建成功,&&的功能是连续执行两句语句,当cd $(BUILD_DIR)执行完后,执行/bin/pwd,即打印当前路径;
- $(if xxx,yyy,zzz)是makefile的判断函数,如果xxx为真,则执行yyy并返回值,否则执行zzz并返回值,由此可知如果未成功创建BUILD_DIR,就会输出错误打印信息;
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE := $(CURDIR)
TOPDIR := $(SRCTREE)
LNDIR := $(OBJTREE)
- 这段代码是设置并导出了很多和路径有关的环境变量
- 如果BUILD_DIR不为空,OBJTREE(放产生的.o文件)的值就为BUILD_DIR,如果为空,则OBJTREE的值就为CURDIR(即current direction,当前源码所在的目录)
- SRCTREE的值为当前源码所在目录,TOPDIR的值为SRCTREE的值即当前源码所在目录,LNDIR(应该是放链接产生的文件)的值和OBJTREE相同
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
- 将变量MKCONFIG的值设置为当前源码目录下的mkconfig文件,并将其导出为环境变量,这个shell脚本文件是正式make之前的配置脚本,十分重要
- 该mkconfig文件的分析可以参考博客《海思(Hi3521a)uboot详细分析(2)——顶层mkconfig分析》
ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD := 1
export REMOTE_BUILD
endif
- 判断OBJTREE的值和SRCTREE的值是否不相等,即判断是否使用了“单独外部路径编译”,如果使用了这个功能,则REMOTE_BUILD值为1,并将其导出至全局
# $(obj) and (src) are defined in config.mk but here in main Makefile
# we also need them before config.mk is included which is the case for
# some targets like unconfig, clean, clobber, distclean, etc.
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
export obj src
# Make sure CDPATH settings don't interfere
unexport CDPATH
- 这一段的意思非常简单,判断OBJTREE的值和SRCTREE的值是否不相等,即判断是否使用了“单独外部路径编译”功能,如果使用了这个功能, 则将obj的值赋为OBJTREE的值,将src的值赋为SRCTREE的值;反之,值都为空。最后将他们全部导出至全局
4.设置编译工具链(大部分在config.mk内)
# The "tools" are needed early, so put this first
# Don't include stuff already done in $(LIBS)
SUBDIRS = tools \
examples/standalone \
examples/api
.PHONY : $(SUBDIRS)
ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))
# Include autoconf.mk before config.mk so that the config options are available
# to all top level build files. We need the dummy all: target to prevent the
# dependency target in autoconf.mk.dep from being the default.
all:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk
# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
- config.mk这个文件其实uboot源码中是不存在的,它是由配置过程中由mkconfig这个脚本创建的,
- 执行命令:make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config
- 它会去执行根目录下mkconfig这个脚本,脚本中将创建include/config.mk并将参数填充进去。
- config.mk的内容分别定义了ARCH CPU BOARD VENDOR SOC这几个变量的值,
- 通过把这个.mk文件include进来,其内容将在本makefile中原地展开
# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
- CROSS_COMPILE是由我们外面的命令行传入的,不在这里定义。
# load other configuration
include $(TOPDIR)/config.mk
- 这里包含了一个源码目录下的config.mk文件,和之前的那个hi3521a_config生成的include/config.mk不同,
- 这个.mk文件是源码自带的,对编译属性和链接属性进行了很多设置
- 这个位于根目录的config.mk被include进来,将在原地展开,
- 但是由于代码量较大,故其注释写在了config.mk里面,请参考博客《海思(Hi3521a)uboot详细分析(3)——顶层config.mk分析》
5.设置规则
#########################################################################
# U-Boot objects....order is important (i.e. start must be first)
OBJS = $(CPUDIR)/start.o
ifeq ($(CPU),i386)
OBJS += $(CPUDIR)/start16.o
OBJS += $(CPUDIR)/resetvec.o
endif
.......
.......
.......
# Add GCC lib
ifdef USE_PRIVATE_LIBGCC
ifeq ("$(USE_PRIVATE_LIBGCC)", "yes")
PLATFORM_LIBGCC = -L $(OBJTREE)/arch/$(ARCH)/lib -lgcc
else
PLATFORM_LIBGCC = -L $(USE_PRIVATE_LIBGCC) -lgcc
endif
else
PLATFORM_LIBGCC = -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
endif
PLATFORM_LIBS += $(PLATFORM_LIBGCC)
export PLATFORM_LIBS
- 这里主要做了两件事:
- 一将众多.o文件赋给了OBJS变量,这个变量会成为后面目标u-boot的依赖
- 二将众多.a库文件赋给了LIBS变量,以及将和板子有关的.a文件赋给了LIBBOARD变量,这两个变量也会成为后面目标u-boot的依赖
LDPPFLAGS += \
-include $(TOPDIR)/include/u-boot/u-boot.lds.h \
$(shell $(LD) --version | \
sed -ne 's/GNU ld version \([0-9][0-9]*\)\.\([0-9][0-9]*\).*/-DLD_MAJOR=\1 -DLD_MINOR=\2/p')
ifeq ($(CONFIG_NAND_U_BOOT),y)
NAND_SPL = nand_spl
U_BOOT_NAND = $(obj)u-boot-nand.bin
endif
ifeq ($(CONFIG_ONENAND_U_BOOT),y)
ONENAND_IPL = onenand_ipl
U_BOOT_ONENAND = $(obj)u-boot-onenand.bin
ONENAND_BIN ?= $(obj)onenand_ipl/onenand-ipl-2k.bin
endif
__OBJS := $(subst $(obj),,$(OBJS))
__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
- 这里应该是没有使用到的
# Always append ALL so that arch config.mk's can add custom ones
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)
all: $(ALL)
$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) -O srec $< $@
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(obj)u-boot.ldr: $(obj)u-boot
$(CREATE_LDR_ENV)
$(LDR) -T $(CONFIG_BFIN_CPU) -c $@ $< $(LDR_FLAGS)
$(obj)u-boot.ldr.hex: $(obj)u-boot.ldr
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@ -I binary
$(obj)u-boot.ldr.srec: $(obj)u-boot.ldr
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@ -I binary
$(obj)u-boot.img: $(obj)u-boot.bin
$(obj)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.imx: $(obj)u-boot.bin
$(obj)tools/mkimage -n $(IMX_CONFIG) -T imximage \
-e $(TEXT_BASE) -d $< $@
$(obj)u-boot.kwb: $(obj)u-boot.bin
$(obj)tools/mkimage -n $(KWD_CONFIG) -T kwbimage \
-a $(TEXT_BASE) -e $(TEXT_BASE) -d $< $@
$(obj)u-boot.sha1: $(obj)u-boot.bin
$(obj)tools/ubsha1 $(obj)u-boot.bin
$(obj)u-boot.dis: $(obj)u-boot
$(OBJDUMP) -d $< > $@
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: ddr_training 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
endif
- 本段出现了顶层makefile的第一条规则(即目标-依赖-操作),
- 故默认情况下将以all这个变量作为最终目标。但是由于本条规则没有任何操作,
- 所以一旦把依赖(也就是$(ALL))实现了,整个makefile文件的最终目标就达成了
- 可以认为,本makefile的终极目标其实是$(ALL)所代表的那些文件
- 下面的这些都是为了产生最终文件的规则
# Auto-generate the autoconf.mk file (which is included by all makefiles)
#
# This target actually generates 2 files; autoconf.mk and autoconf.mk.dep.
# the dep file is only include in this top level makefile to determine when
# to regenerate the autoconf.mk file.
$(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h
@$(XECHO) Generating $@ ; \
set -e ; \
: Generate the dependancies ; \
$(CC) -x c -DDO_DEPS_ONLY -M $(HOSTCFLAGS) $(CPPFLAGS) \
-MQ $(obj)include/autoconf.mk include/common.h > $@
$(obj)include/autoconf.mk: $(obj)include/config.h
@$(XECHO) Generating $@ ; \
set -e ; \
: Extract the config macros ; \
$(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \
sed -n -f tools/scripts/define2mk.sed > [email protected] && \
mv [email protected] $@
- 本段的功能是根据include/config.h来生成autoconf.mk,
- config.h 是在执行配置命令make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config时生成的,它的内容为:
- #define CONFIG_BOARDDIR board/hi3520dv400
- #include <config_defaults.h>
- #include <configs/hi3521a.h>
- #include <asm/config.h>
- config.h里面的内容根据不同的配置,这里包含的头文件就不同。这主要是 为了方便uboot的移植。
- makefile利用这些autoconf.mk中的变量来指导编译过程的走向
- 这里的内容非常重要,决定Makefile的条件编译。
6.设置与cpu相关的伪目标
hi3521a_config: unconfig
@$(MKCONFIG) $(@:_config=) arm hi3521a hi3521a NULL hi3521a
hi3521d_config: unconfig
@$(MKCONFIG) $(@:_config=) arm hi3521d hi3521d NULL hi3521d
hi3520dv300_config: unconfig
@$(MKCONFIG) $(@:_config=) arm hi3521a hi3521a NULL hi3521a
- 本段代码主要负责了正式make之前的配置过程,即在控制台输入
- make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config
- 其依赖是unconfig,此变量代表了未配置的意思,通过这个方法,我们便可以多次配置。
- MKCONFIG这个变量代表的是源码目录下最关键的一个shell文件即mkconfig,这个shell文件负责了make之前的配置过程。
- 行首的@代表静默执行,这段代码在执行$(MKCONFIG)前还把$(@:_config=)、arm hi3521a、 hi3521a、 NULL、 hi3521a这6个主要参数传给了mkconfig
- 其中参数$(@:_config=)是引用了一个替换函数,将该规则中的目标(用@表示)中的_config用空替换,故$(@:_config=)的值为hi3521a
- 关于mkconfig,请参考博客《海思(Hi3521a)uboot详细分析(2)——顶层mkconfig分析》
- 注意:对于hi3521a与hi3520dv300配置的不同
- 对于hi3520dv300官方给的配置文件为:
- make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config
- make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3520dv300_config
- 它需要同时配置hi3521a和hi3520dv300这两款设备,从它们的从hi3520dv300的配置参数$(@:_config=) arm hi3521a hi3521a NULL hi3521a 可以看出,它只有第一个参数不一样,体现到最终的配置文件,hi3521a与hi320dv300的不同配置是在./u-boot-2010/include/config.h 中包含的头问价不同,hi3520dv300包含的是#include <configs/hi3520dv300.h>,其它的都相同
- 从这里可以看出,hi3520dv300的启动代码和连接文件等信息都是引用的hi3521a的启动文件和连接文件,只是配置信息使用的是hi3520dv300的配置