uboot的配置编译过程的主Makefile的分析:
本文以九鼎为x210移植的uboot很目录下的Makefile为分析目标,分析uboot在配置编译阶段的工作流程。
在正式介绍之前我想先介绍一些关于Makefile的语法问题,当然我讲的是一些比较浅显的常用的东西,如果想进一步了解学习的可以看看陈皓写的《跟我一起写Makefile》
Makefile的基础知识:
1.Makefile的三类文件:目标 依赖 条件
Makefile是用来做项目管理,方便编译链接过程。自动推导是Makefile的法则。
Makefile的目标:最终要生成的东西。顶格写,后面是依赖;
Makefile的依赖:用来生成目标的原材料;
Makefile的命令:也就是程序的具体实现,加工方式。
一个Makefile可以有多个目标,可以通过make+目标名实现目标,在默认的情况下实现的是第一个名为ALL的目标。 %是Makefile的通配符,例如%.x就代表.x的所有文件。Makefile定义变量和使用变量和shell很像,是一个弱类型语言,没有变量的类型,直接定义使用,引用时用$xxx。 伪目标直接执行目标之后的命令,没有依赖。一般用.PHONY声明。
2.Makefile引用其他的Makefile:
用include引用,应用的效果是原地展开,类似于C语言。静默执行:在执行命令前加上@可以使得命令输出时仅仅只是输出命令结果而不是将命令行本身也输出。
3.Makefile的赋值:
= 赋值时以最后一个值为变量最终的值。
?= 如果前面没有赋值就执行赋值语句,反则不执行。
+= 给一个已经赋值的变量接续赋值,把这次的值加到原来的值后面(将变量当做字符串来处理)
:=赋值时就地直接解析,只用往前看。也就是一般的赋值。
4.Makefile的环境变量:
在Makefile中用export导出的就是环境变量,用大写来表示,和普通变量不同,他是整个工程中所有Makefile可以共享的全局变量。而实际make时给Makefile传进去的参数优先级高于Makefile内定义的变量,可以覆盖Makefile的赋值
5.Makefile的通配符:
1. * 表示若干个任意的随意字符。
2. ?表示一个与依赖内相同的字符。
3. []表示括号内与依赖内相同的字符。
4. %表示任意多个字符,但是一般只用于规则描述之中,叫做规则通配符。
6.Makefile的自动变量:预定义的特殊意义符号,类似于C语言编译器中预定义的宏。
1. $@ 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
2. $< 依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
3. $^ 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
4.$+这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标
7.makefile的两种编译方法:
1.原地编译,编译前的.c文件内同时存放着编译之后的.o文件。会导致污染原文件夹
2.单独的专用文件夹存放编译之后的文件。 指定的位置输出文件:①.make O =输出目录;'make O=/tmp/build all' ②export BULLD DIR=输出目录 注:CURDIR //源码目录 SRCTREE //顶层目录 OBJTREE:编译出的.o文件存放的目录的根目录。在make o=xx的编译方式
下面将对Makefile进行具体的分析:
make xxx 配置阶段完成之后生成的文件:
(1)uboot/include/config.mk 存放着ARCH CPU BOARD VENDOR SOC 参数
(2)uboot/include/autoconf.mk 指导编译阶段的宏文件
(3)uboot/board/samsung/x210/config.mk 用来存放链接地址的文件
1.确定uboot的版本号和主机信息:
VERSION = 1
PATCHLEVEL = 3
SUBLEVEL = 4
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
编译之后,创建了一个头文件uboot/include/version_autogenerated.h,可以直观的展示出版本号。 而这个版本号就是VERSION_FILE = $(obj)include/version_autogenerated.h
2.环境变量的设置
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/)
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
export HOSTARCH HOSTOS
uname -m 得到当前电脑的版本号,通过管道将其输出作为后面的输入,最后统一的将不同的主机统一命名以上的几种架构,并导出我们的这几个环境变量。
3.实现静默编译:编译时make -s,然后-s就会作为参数传给makefile
# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
静默编译不是完全没有信息打印,实现静默编译仅仅将Makefile内执行的代码不展示在我们的终端上面,但是一些函数固有的调试信息还是会打印
4.通过make -o的方式指定我们的.o文件的输出位置:
# 1) Add O= to the make command line
# '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
# 'export BUILD_DIR=/tmp/build'
# './MAKEALL'
但是在这里将其注释,未进行其操作。 在编译时有时为了避免污染uboot的根目录,所以将.o文件和最终的.bin镜像文件输出至一个固定的文件。 在查阅readme后得知指定目录的编译方式为:
Add O= to the make command line invocations:
make O=/tmp/build distclean
make O=/tmp/build NAME_config
make O=/tmp/build all
在配置阶段就要指定我们的输出路径,然后在编译时指定相同的路径。
5.三个重要的参数:OBJTREE,SRCTREE,TOPDIR
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE := $(CURDIR)
TOPDIR := $(SRCTREE)
LNDIR := $(OBJTREE)
export TOPDIR SRCTREE OBJTREE
OBJTREE:OBJTREE是我们设置的输出目录,编译之后.o文件的存放位置。在默认编辑的情况下OBJTREE等于当前目录,在o=xx编译下等于指定的xxx目录;
SRCTREE:源码目录,也就是uboot的根目录目录,当前目录。
TOPDIR:被赋值于SRCTREE,是uboot的根目录目录,当前目录。
最后导出这三个文件路径参数,在之后的很多编译文件的寻找中都是通过这三个路径进行寻找的。
6.引出uboot根目录下的mkconfig大侠,并导出mkconfig,这也是根目录下的mkconfig的开始工作的地方:
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
7.引用uboot/include/config.mk文件,导入其内ARCH CPU BOARD VENDOR SOC 参数,并将其导出为环境变量:
include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
而这几个变量归根结底来源于配置阶段:
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
执行 make x210_sd_config之后会调用执行mkconfig,然后将传入的五个参数写入源码根目录/include/config.mk中。而对于/include下的config.mk的生成在详见uboot的源目录下的mkconfig的分析。
注:由于我们通常情况下在配置时不会指定.O文件的存放位置,默认会存放在源码根目录下,所以这个config.mk就在根目录的/inlclude下存放,而不是我们分析uboot时主要分析的那个config.mk。这个文件和我们配置过程有关,是由配置过程根据我们的配置自动生成的
7.设置交叉编译工具链:
ifeq ($(ARCH),arm)
#CROSS_COMPILE = arm-linux-
#CROSS_COMPILE = /usr/local/arm/4.4.1-eabi-cortex-a8/usr/bin/arm-linux-
#CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux-
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
ARCH:在之前导出,指定我们编译目标的CPU架构。
CROSS_COMPILE:交叉编译工具链的前缀,由于不同CPU架构的交叉编译工具链之间只是前缀的不同,将前缀用环境变量单独设置,保证了uboot的可移植性。 CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi- =后面就是交叉编译工具链的储存位置。
也可以通过make CROSS_COMPILE=xx的方式直接进行设置,因为在Makefile内 先会判断是否make是是否传入CROSS_COMPILE:ifndef CROSS_COMPILE
8.引用uboot根目录下的config.mk文件,config.mk大侠正式登场:
include $(TOPDIR)/config.mk
引用了根目录下一个config.mk文件夹,这个文件是uboot的四个主要文件之一,对于此文件内内容的详尽分析请看配置编译之config.mk 。其中主要定义了一些交叉编译工具链,定义了uboot链接时指定的链接地址TEXT_BASE,并引用了我们编译配置过程中一个很重要的宏文件:autoconf.mk,它用来指导整个uboot的编译过程,并且在其中引出了我们的链接脚本文件:u-boot.lds。
9.之后根据之前我们make配置阶段传入的硬件信息进行硬件初始化文件的指定,然后就指定一些链接路径
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),mpc85xx)
OBJS += cpu/$(CPU)/resetvec.o
endif
OBJS := $(addprefix $(obj),$(OBJS))
LIBS = lib_generic/libgeneric.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
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/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/nand_legacy/libnand_legacy.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/sk98lin/libsk98lin.a
LIBS += drivers/pci/libpci.a
LIBS += drivers/pcmcia/libpcmcia.a
LIBS += drivers/spi/libspi.a
ifeq ($(CPU),mpc83xx)
LIBS += drivers/qe/qe.a
endif
ifeq ($(CPU),mpc85xx)
LIBS += drivers/qe/qe.a
endif
LIBS += drivers/rtc/librtc.a
LIBS += drivers/serial/libserial.a
LIBS += drivers/usb/libusb.a
LIBS += drivers/video/libvideo.a
LIBS += common/libcommon.a
LIBS += libfdt/libfdt.a
LIBS += api/libapi.a
LIBS += post/libpost.a
LIBS := $(addprefix $(obj),$(LIBS))
.PHONY : $(LIBS) $(VERSION_FILE)
LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a
LIBBOARD := $(addprefix $(obj),$(LIBBOARD))
10.与我们最终目标的设置有关,也就是与镜像文件相关的一些设置:
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_bl1
U_BOOT_ONENAND = $(obj)u-boot-onenand.bin
endif
这里根据我么启动方式的不同,确定不同的依赖
11.Makefile内的第一个目标,是编译阶段执行的目标,但不是第一个执行的目标。
ALL确定了我们最终镜像文件依赖的文件,以及整个编译过程的具体的过程
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND) $(obj)u-boot.dis
ifeq ($(ARCH),blackfin)
ALL += $(obj)u-boot.ldr
endif
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.ldr: $(obj)u-boot
$(LDR) -T $(CONFIG_BFIN_CPU) -f -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
./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.sha1: $(obj)u-boot.bin
$(obj)tools/ubsha1 $(obj)u-boot.bin
$(obj)u-boot.dis: $(obj)u-boot
$(OBJDUMP) -d $< > $@
$(obj)u-boot: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT)
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
$(OBJS): depend $(obj)include/autoconf.mk
$(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
$(LIBS): depend $(obj)include/autoconf.mk
$(MAKE) -C $(dir $(subst $(obj),,$@))
$(LIBBOARD): depend $(LIBS) $(obj)include/autoconf.mk
$(MAKE) -C $(dir $(subst $(obj),,$@))
$(SUBDIRS): depend $(obj)include/autoconf.mk
$(MAKE) -C $@ all
$(LDSCRIPT): depend $(obj)include/autoconf.mk
$(MAKE) -C $(dir $@) $(notdir $@)
$(NAND_SPL): $(VERSION_FILE) $(obj)include/autoconf.mk
$(MAKE) -C nand_spl/board/$(BOARDDIR) all
$(U_BOOT_NAND): $(NAND_SPL) $(obj)u-boot.bin $(obj)include/autoconf.mk
cat $(obj)nand_spl/u-boot-spl-16k.bin $(obj)u-boot.bin > $(obj)u-boot-nand.bin
#$(ONENAND_IPL): $(VERSION_FILE) $(obj)include/autoconf.mk
# $(MAKE) -C onenand_ipl/board/$(BOARDDIR) all
#
#$(U_BOOT_ONENAND): $(ONENAND_IPL) $(obj)u-boot.bin $(obj)include/autoconf.mk
# cat $(obj)onenand_ipl/onenand-ipl-2k.bin $(obj)u-boot.bin > $(obj)u-boot-onenand.bin
# cat $(obj)onenand_ipl/onenand-ipl-4k.bin $(obj)u-boot.bin > $(obj)u-boot-flexonenand.bin
$(ONENAND_IPL): $(VERSION_FILE) $(obj)include/autoconf.mk
$(MAKE) -C $(obj)onenand_bl1/$(BOARD) all
$(U_BOOT_ONENAND): $(ONENAND_IPL) $(obj)u-boot.bin $(obj)include/autoconf.mk
cat $(obj)onenand_bl1/$(BOARD)/BL1.bin.padding $(obj)u-boot.bin > $(U_BOOT_ONENAND)
$(VERSION_FILE):
@( printf '#define U_BOOT_VERSION "U-Boot %s%s"\n' "$(U_BOOT_VERSION)" \
'$(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion $(TOPDIR))' \
) > $@.tmp
@cmp -s $@ $@.tmp && rm -f $@.tmp || mv -f $@.tmp $@
gdbtools:
$(MAKE) -C tools/gdb all || exit 1
updater:
$(MAKE) -C tools/updater all || exit 1
env:
$(MAKE) -C tools/env all MTD_VERSION=${MTD_VERSION} || exit 1
depend dep: $(VERSION_FILE)
for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
12.整个Makefile中实际第一个执行的目标,·,用来生成一些指导编译过程的文件。
x210_sd_config : unconfig
@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk
这里是将x210_sd_config 后面的_config替换为空,然后x210_sd作为执行MKCONFIG的参数和后面的arm s5pc11x x210 samsung s5pc110一起传给MKCONFIG。
END..................................