【原文:http://blog.sina.com.cn/s/blog_71d491570100xcio.html】
U-Boot开头有一些跟主机软硬件环境相关的代码,在每次执行make命令时这些代码都被执行一次。
U-Boot Makefile分析
(1)定义主机系统架构
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/ppc64/ppc/ /
-e s/macppc/ppc/)
“sed –e”表示后面跟的是一串命令脚本,
表达式“s/abc/def/”表示要从标准输入中,查找到内容为“abc”的,然后替换成“def”。其中“abc”表达式用可以使用“.”作为通配符。
命令“uname –m”将输出主机CPU的体系架构类型。作者的电脑使用Intel Core2系列的CPU,因此“uname –m”输出“i686”。 “i686”可以匹配命令“sed -e s/i.86/i386/”中的“i.86”,因此在作者的机器上执行Makefile,HOSTARCH将被设置成“i386” 。
(2)定义主机操作系统类型
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | /
sed -e 's//(cygwin/).*/cygwin/')
“uname –s”输出主机内核名字,作者使用Linux发行版Ubuntu9.10,因此“uname –s”结果是“Linux”。
“tr '[:upper:]' '[:lower:]'”作用是将标准输入中的所有大写字母转换为响应的小写字母。因此执行结果是将HOSTOS 设置为“linux”。
(补充:$ sed -e '1,5d' -e 's/test/check/' example-----(-e)选项允许在同一行里执行多条命令。如例子所示,第一条命令删除1至5行,第二条命令用check替换test。命令的执行顺序对结果有影响。如果两个命令都是替换命令,那么第一个替换命令将影响第二个替换命令的结果。)
(3)定义执行shell脚本的shell
# 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)
"$$BASH"的作用实质上是生成了字符串“$BASH”(前一个$号的作用是指明第二个$是普通的字符)。若执行当前Makefile的shell中定义了“$BASH”环境变量,且文件“$BASH”是可执行文件,则SHELL的值为“$BASH”。否则,若“/bin/bash”是可执行文件,则SHELL值为“/bin/bash”。若以上两条都不成立,则将“sh”赋值给SHELL变量。
由于作者的机器安装了bash shell,且shell默认环境变量中定义了“$BASH”,因此SHELL 被设置为$BASH 。
(4)设定编译输出目录
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif
函数$( origin, variable) 输出的结果是一个字符串,输出结果由变量variable定义的方式决定,若variable在命令行中定义过,则origin函数返回值为"command line"。假若在命令行中执行了“export BUILD_DIR=/tmp/build”的命令,则“$(origin O)”值为“command line”,而BUILD_DIR被设置为“/tmp/build”。
ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)
# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
-d选项判断${BUILD_DIR}这个目录存在与否。
若${BUILD_DIR}表示的目录没有定义,则创建目录。
# 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命令,进入到新建的目录里,然后执行pwd命令来得到当前目录的真实位置。为什么需要这样做呢,因为前面的创建目录工作可能不成功,所以导致后面的cd命令也没有进去,所以需要后面的pwd命令来确认一下。
if(a,b,c)这样的形式,执行步骤为,先判断a的真假,如果为真,则执行b,如果为假,则执行c。所以这里的意思就是判断目录建成没有建成,如果建成,则什么也不干,没建成,就使用error,输出错误信息且退出。
若$(BUILD_DIR)为空,则将其赋值为当前目录路径(源代码目录)。并检查$(BUILD_DIR)目录是否存在。
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE := $(CURDIR)
TOPDIR := $(SRCTREE)
LNDIR := $(OBJTREE)
… …
MKCONFIG := $(SRCTREE)/mkconfig
… …
ifneq ($(OBJTREE),$(SRCTREE))
obj := $(OBJTREE)/
src := $(SRCTREE)/
else
obj :=
src :=
endif
CURDIR变量指示Make当前的工作目录,由于当前Make在U-Boot顶层目录执行Makefile,因此CURDIR此时就是U-Boot顶层目录。
OBJTREE和LNDIR为存放生成文件的目录,TOPDIR与SRCTREE为源码所在目录。
变量MKCONFIG:这个变量指向一个脚本,即顶层目录的mkconfig。
执行完上面的代码后, SRCTREE,src变量就是U-Boot代码顶层目录,而OBJTREE,obj变量就是输出目录,若没有定义BUILD_DIR环境变量,则SRCTREE,src变量与OBJTREE,obj变量都是U-Boot源代码目录。而MKCONFIG则表示U-Boot根目录下的mkconfig脚本。
#########################################################################
ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))
wildcard是Makefile中的关键字,它的作用是让通配符在变量中展开。
这句的封闭语句为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
首先,它判断$(OBJTREE)/include/config.mk与$(wildcard $(OBJTREE)/include/config.mk)得到的值是否一样。$(wildcard PATTERN)的使用就是查找到与PATTERN相符合的,并且是存在的,以空格分开的文件列表。说白了,这句话就是判断$(OBJTREE)/include/config.mk文件是否存在。如果存在,则执行咱们忽略的内容,如果不存在,则执行咱们没有忽略的内容。
先看不存在的情况。不存在的时候,就把所有目标,都指向到一条依赖上了
@echo "System not configured - see README" >&2
@ exit 1
然后不管执行make xxx命令,都会显示System not configured - see README,然后它把这个输出输到标准错误输出文件里了,退出了执行。
所以这里就明说了,如果$(OBJTREE)/include/config.mk文件不存在,则编译不能进行下去了。
如果直接运行make的话,u-boot是编译不过去的,因为缺少$(OBJTREE)/include/config.mk这个文件。然后出错提示让去看README,那咱们就去看看这个README里面写的是什么东西。
这个README文件里介绍了一下u-boot这个工程,然后说明了一下好些个参数是做什么用的,其中有一句话是包含跟咱们编译有关的信息的
make <board_name>_config
也就是说,编译的时候,make后面是需要跟参数的,那么根据make的语法,这个<board_name>_config肯定是在Makefile里定义了的一个target了。
然后再看一下Makefile文件,果然以_config结尾的target很多。因为我的板子是仿制三星的S3C2440的,所以与我最接近的,让我在移植的时候代码改动量最小的,就是smdk2410_config这个target了。如果是新手,对自己没有什么信心,那就可以直接使用这个target来编译,直接修改与这个target相关的源码就可以了。其实我个人倒是建议另外给自己的板子取一个名字,然后在Makefile里给自己的板子按着相应的规则给加一条target。然后把自己需要的与现在板子的环境最接近的板子的相关的文件都拷到相应的位置,然后慢慢修改它们就可以了。
# load ARCH, BOARD, and CPU configuration
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
上面为加入编译环境,根据CPU类型来为交叉编译工具加上相应的前缀
开头一句中的include,它是把后面跟的文件内容给加到它出现的地方,这里就是把$(OBJTREE)/include/config.mk文件的内容加入到了这里,所以就得到了ARCH,BOARD与CPU的值,如果VENDOR与SOC也定义了,也会得到它们的值。
下面为U-boot需要的目标文件。顺序很重要,start.o必须放第一位。
#########################################################################
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*$$//' > $@
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))
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)
# 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)
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))
以上为UBOOT需要的库文件。
根据上面的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/configs/smdk2410.h
下面是最终生成的各种镜像文件:
这里就是定义了各种目标的target。我们最终想要的目标就是$(obj)u-boot,然后看后面的,它都信赖了好多其它的目标,然后这些个目标,也是在此处定义的,然后有相应的$(MAKE)来执行相应的操作,所以这样就实现了一个Makefile文件,套很多个其它的Makefile文件来编译整个工程的情况。
#########################################################################
#########################################################################
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
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
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
首先我们看到,这个target首先需要执行一个unconfig操作,然后再执行后面的操作。然后我们查到unconfig操作的具体内容如下:
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk /
$(obj)board*/config.tmp
看得出来,它是删掉了包括$(obj)include/config.mk文件在内的,几个在编译的时候生成的配置文件,这个$(obj)include/config.mk与$(OBJTREE)/include/config.mk是同一个文件。这样做是为了保证重新生成文件的纯粹性。
执行完unconfig后,就执行:
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
这里的$(MKCONFIG)就会被替换成上文中所说的$(SRCTREE)/mkconfig文件路径,相关于是把这个文件的内容,当可执行程序执行了,其实,它就是一个shell脚本。
一定要注意这句$(@:_config=),其实这里是用了一个比较高级的Makefile语法,$(a:patternA=patternB),这样的语法表示把a变量里的形式为patternA的换成为patternB,然后输出。那么在这个例子里,a变量换成了@,它的意思与shell里@的意思是一样的,就是输入进来的所有的参数,但是中间不用环境变量IFS所定义的分隔符分开。patternA就是_config,patternB就是空。
然后再看看咱们执行这个Makefile的时候输入的语句:
make smdk2410_config
那么在这里的话,传到$(SRCTREE)/mkconfig这个脚本里的参数实际上就是 smdk2410 arm arm920t smdk2410 NULL s3c24x0