U-Boot Makefile分析(5)主控Makefile分析

  这次分析源码根目录下的Makefile,它负责读入配置过的信息,通过OBJS、LIBS等变量设置能够参与镜像链接的目标文件,设定编译的目标等等。

 1 HOSTARCH := $(shell uname -m | \
 2     sed -e s/i.86/x86/ \
 3         -e s/sun4u/sparc64/ \
 4         -e s/arm.*/arm/ \
 5         -e s/sa110/arm/ \
 6         -e s/ppc64/powerpc/ \
 7         -e s/ppc/powerpc/ \
 8         -e s/macppc/powerpc/\
 9         -e s/sh.*/sh/)
10 
11 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
12         sed -e 's/\(cygwin\).*/cygwin/')
13 
14 # Set shell to bash if possible, otherwise fall back to sh
15 SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
16     else if [ -x /bin/bash ]; then echo /bin/bash; \
17     else echo sh; fi; fi)
18 
19 export  HOSTARCH HOSTOS SHELL

  上述代码通过shell处理字符数据,得到宿主机的架构、操作系统、shell类型等变量,Makefile中调用shell命令的方式有两种,一种是$(shell cmd_name),另一种是命令前后加上反引号``,如$(shell uname -m)或者`uname -m`。这里可以看到,sed的选项可以是多个,之前不知道还能这么用,学习了。

 1 ifdef O
 2 ifeq ("$(origin O)", "command line")
 3 BUILD_DIR := $(O)
 4 endif
 5 endif
 6 
 7 ifneq ($(BUILD_DIR),)
 8 saved-output := $(BUILD_DIR)
 9 
10 # Attempt to create a output directory.
11 $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
12 
13 # Verify if it was successful.
14 BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
15 $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
16 endif # ifneq ($(BUILD_DIR),)
17 
18 OBJTREE     := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
19 SPLTREE     := $(OBJTREE)/spl
20 SRCTREE     := $(CURDIR)
21 TOPDIR      := $(SRCTREE)
22 LNDIR       := $(OBJTREE)
23 export  TOPDIR SRCTREE OBJTREE SPLTREE
24 
25 MKCONFIG    := $(SRCTREE)/mkconfig
26 export MKCONFIG
27 
28 ifneq ($(OBJTREE),$(SRCTREE))
29 REMOTE_BUILD    := 1
30 export REMOTE_BUILD
31 endif

  BUILD_DIR可以通过命令行中O=xxx指定。BUILD_DIR和SRCTREE是相同的,代表native build,REMOTE_BUILD = 0。最后将各个变量输出到全局,TOPDIR在子Makefile中经常使用,它的内容是源码根目录的绝对路径。

  MKCONFIG代表根目录下的mkconfig脚本,这个脚本的内容在前面已经分析过了。

1 all:
2 sinclude $(obj)include/autoconf.mk.dep
3 sinclude $(obj)include/autoconf.mk

  这里出现了all,虽然它的确是我们的目标,但是这里并不是要描述它的依赖和生成命令,而是为了防止sinclude的文件中的某个目标成为默认目标,造成严重的逻辑错误。这是一个小技巧,纠正了我对于Makefile的错误认识:之前认为某个目标的依赖关系必须在一条描述中写完,要是这个目标的多个依赖分散在多处写的话,每一部分依赖都有一条命令与之对应。这是错的,其实目标的依赖关系可以分成多次写完,你只需要在某一处指明要执行的命令就行了,在U-Boot的Makefile中,由源文件生成目标文件的命令都是放在模式规则中描述的,而目标文件对于头文件的依赖是在其他地方描述的,这一点已经在分析具体子Makefile的时候说过了。

  从内容上来说,它从autoconf.mk文件中读入了很多配置信息。

 

1 include $(obj)include/config.mk
2 export  ARCH CPU BOARD VENDOR SOC

  前面分析mkconfig脚本的时候,我们知道include/config.mk文件的主要内容是指定ARCH CPU SOC VENDOR BOARD等信息,这里又输出到全局,对于后面的配置影响很大。

 1 OBJS  = $(CPUDIR)/start.o
 2 ifeq ($(CPU),x86)
 3 OBJS += $(CPUDIR)/start16.o
 4 OBJS += $(CPUDIR)/resetvec.o
 5 endif
 6 ifeq ($(CPU),ppc4xx)
 7 OBJS += $(CPUDIR)/resetvec.o
 8 endif
 9 ifeq ($(CPU),mpc85xx)
10 OBJS += $(CPUDIR)/resetvec.o
11 endif
12 
13 OBJS := $(addprefix $(obj),$(OBJS))

  重要变量OBJS登场,它表示U-Boot必须要编译链接的那些目标文件,而后面可选的驱动、文件系统、网络配置都放在LIBS变量中描述。OBJS变量中,一定要注意目标文件的顺序,这关系到处理器能否正常工作,倘若你把启动配置cpu的代码放在了镜像文件的后面,而非开始位置,那么一定会出错。

 1 HAVE_VENDOR_COMMON_LIB = $(if $(wildcard board/$(VENDOR)/common/Makefile),y,n)
 2 
 3 LIBS-y += lib/libgeneric.o
 4 LIBS-y += lib/lzma/liblzma.o
 5 LIBS-y += lib/lzo/liblzo.o
 6 LIBS-y += lib/zlib/libz.o
 7 LIBS-$(CONFIG_TIZEN) += lib/tizen/libtizen.o
 8 LIBS-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/lib$(VENDOR).o
 9 LIBS-y += $(CPUDIR)/lib$(CPU).o
10 ifdef SOC
11 LIBS-y += $(CPUDIR)/$(SOC)/lib$(SOC).o
12 endif
13 ifeq ($(CPU),ixp)
14 LIBS-y += drivers/net/npe/libnpe.o
15 endif
16 LIBS-$(CONFIG_OF_EMBED) += dts/libdts.o
17 LIBS-y += arch/$(ARCH)/lib/lib$(ARCH).o
18 LIBS-y += fs/cramfs/libcramfs.o \
19     fs/ext4/libext4fs.o \
20     fs/fat/libfat.o \
21     fs/fdos/libfdos.o \
22     fs/jffs2/libjffs2.o \
23     fs/reiserfs/libreiserfs.o \
24     fs/ubifs/libubifs.o \
25     fs/yaffs2/libyaffs2.o \
26     fs/zfs/libzfs.o

  首先说一下Makefile中的wildcard函数,它的本意是通配符,但是这里的用途是判断 一个路径是否存在,如果你写的路径不合法,不存在,那么返回空字符串。

  可以看到LIBS-y变量开始收割了,它包含了lib fs arch net drivers等各个目录下的目标文件,因为这些功能都是可选的,因此以lib的形式出现。那么浏览一下它包含了哪些目标文件吧!

  1. lib目录下的通用库文件

  2. arch/arm/cpu/armv7/libarmv7.o        实现armv7的cache等部件的管理。

  3. arch/arm/cpu/armv7/s5pc1xx/libs5pc1xx.o   实现了reset clock等部件的管理。

  4. arch/arm/lib/libarm.o              架构相关的底层库。

  5. fs/下的诸多文件系统

  6. net目录下的libnet.o             网络协议栈相关的库。

  7. drivers/ :block dma fpga gpio i2c input misc mmc mtd net  power pci spi等文件下的libxx.o

1 ifeq ($(SOC),s5pc1xx)
2 LIBS-y += $(CPUDIR)/s5p-common/libs5p-common.o
3 endif

  包含s5p系列soc的公用库,比如timer srom watchdog等。

1 LIBS := $(addprefix $(obj),$(sort $(LIBS-y)))
2 .PHONY : $(LIBS)
3 
4 LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).o
5 LIBBOARD := $(addprefix $(obj),$(LIBBOARD))

  LIBS-y经过排序、清理掉重复目标文件之后,放在新的变量LIBS中,并将它声明为伪目标。

  LIBBOARD := board/samsung/goni/libgoni.o,这也是一个重要的变量。

 1 # Add GCC lib
 2 ifdef USE_PRIVATE_LIBGCC
 3 ifeq ("$(USE_PRIVATE_LIBGCC)", "yes")
 4 PLATFORM_LIBGCC = $(OBJTREE)/arch/$(ARCH)/lib/libgcc.o
 5 else
 6 PLATFORM_LIBGCC = -L $(USE_PRIVATE_LIBGCC) -lgcc
 7 endif
 8 else
 9 PLATFORM_LIBGCC := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
10 endif
11 PLATFORM_LIBS += $(PLATFORM_LIBGCC)
12 export PLATFORM_LIBS

  这里添加LIBGCC,看不明白,暂不研究。

1 __OBJS := $(subst $(obj),,$(OBJS))
2 #__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
3 
4 __LIBS := $(subst $(obj),,$(LIBBOARD)) $(subst $(obj),,$(LIBS))

  重量级变量登场!__OBJS是除去了obj前缀的OBJS,就是start.o。__LIBS则由两部分组成,LIBBOARD在前,LIBS在后,其实官方源码中两者的顺序是相反的。为什么要修改呢?这就跟S5PV210的启动流程有关系了。移植的时候,我们选择修改goni的代码,其中要修改的文件之一是board/samsung/goni/lowlevel_init.S,根据路径可以知道这里的文件都会被链接进libgoni.o中,也就是LIBBOARD变量里,如果它放在LIBS后面,那么lowlevel_init.S中的代码将会在镜像靠后的位置,绝对在16KB以后,而S5PV210在启动的时候会将SD卡的前16KB搬移到iRAM中,这将无法运行我们修改的代码,所以修改。

 1 BOARD_SIZE_CHECK = \
 2     @actual=`wc -c $@ | awk '{print $$1}'`; \
 3     limit=$(CONFIG_BOARD_SIZE_LIMIT); \
 4     if test $$actual -gt $$limit; then \
 5         echo "$@ exceeds file size limit:"; \
 6         echo "  limit:  $$limit bytes"; \
 7         echo "  actual: $$actual bytes"; \
 8         echo "  excess: $$((actual - limit)) bytes"; \
 9         exit 1; \
10     fi

  这里就是一个检查镜像大小的函数,如果超出了用户设定的大小就报错,理解起来很简单,不多说,一般可以不用这个功能。

 1 ALL-y += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map
 2 
 3 ALL-$(CONFIG_NAND_U_BOOT) += $(obj)u-boot-nand.bin
 4 ALL-$(CONFIG_ONENAND_U_BOOT) += $(obj)u-boot-onenand.bin
 5 ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin
 6 ALL-$(CONFIG_OF_SEPARATE) += $(obj)u-boot.dtb $(obj)u-boot-dtb.bin
 7 
 8 # enable combined SPL/u-boot/dtb rules for tegra
 9 ifeq ($(SOC),tegra20)
10 ifeq ($(CONFIG_OF_SEPARATE),y)
11 ALL-y += $(obj)u-boot-dtb-tegra.bin
12 else
13 ALL-y += $(obj)u-boot-nodtb-tegra.bin
14 endif
15 endif
16 
17 all:        $(ALL-y) $(SUBDIR_EXAMPLES)

  终于开始描述all的依赖了,all的依赖是ALL-y,可以看到这个变量的内容是u-boot.srec u-boot.bin System.map,另外,我们还可以通过宏来给它增添新的内容:u-boot-nand.bin u-boot-onenand.bin u-boot-spl.bin u-boot.dtb u-boot-dtb.bin。

  上面这段代码的最后就写清楚了all的所有依赖:ALL-y $(SUBDIR_EXAMPLES),看一看这两个变量里面的内容吧。

  ALL-y其实指定的是u-boot镜像的生成格式。u-boot是包含各种调试信息、符号表等元素的镜像文件,可以用于阅读分析,但是不适合在嵌入式平台上烧写,因为文件容量大。u-boot.bin是不包含地址信息的二进制指令数据流,文件容量小,我们一般使用这个进行烧写。u-boot.srec,这是一种S-Record格式的文件,是由Motorola公司推出的一种用于flash烧写的镜像文件格式,每一行都由S字母开头,后面有类型字段、地址字段、数据字段以及校验和等等。u-boot.hex,这也是一种烧写flash的镜像文件格式,它每行都由0x3a这个字节开始,后面有一字节的行长度字段、两字节的地址字段、一字节的数据类型字段、数据以及校验和。u-boot里还支持生成其他格式的镜像文件,但是我暂时用不着,先不分析。

  SUBDIR_EXAMPLES指向了examples目录下的文件,主要生成一些测试文件,暂不深究。

  我们关注的是u-boot.bin文件,因此看一下它的依赖:

1 $(obj)u-boot:   depend \
2         $(SUBDIR_TOOLS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
3         $(GEN_UBOOT)

  u-boot的依赖有7个:depend SUBDIR_TOOLS OBJS LIBBOARD LIBS LDSCRIPT u-boot.lds,生成的命令是$(GEN_UBOOT),下面逐个看一下。

1 # Explicitly make _depend in subdirs containing multiple targets to prevent
2 # parallel sub-makes creating .depend files simultaneously.
3 depend dep: $(TIMESTAMP_FILE) $(VERSION_FILE) \
4         $(obj)include/autoconf.mk \
5         $(obj)include/generated/generic-asm-offsets.h \
6         $(obj)include/generated/asm-offsets.h
7         for dir in $(SUBDIRS) $(CPUDIR) $(LDSCRIPT_MAKEFILE_DIR) ; do \
8             $(MAKE) -C $$dir _depend ; done

  depend、dep两个目标拥有同样的依赖关系,执行相同的命令。不难发现depend的命令中并没有生成depend这个文件,因此它的作用相当于一个伪目标,每次执行make命令,都会发现depend目标不存在,接着就开始检查depend的各个依赖。更新depend目标时,也是更换到其他路径下执行make _depend命令,结合前面对于rules.mk的分析知道,_depend也是一个伪目标,用于生成某个文件夹下的所有文件依赖关系,这里很绕,慢慢体会。下面看一看depend的依赖。

  include/autoconf.mk这个文件中有很多重要的配置信息,它的依赖关系如下所示。

1 $(obj)include/autoconf.mk: $(obj)include/config.h
2     @$(XECHO) Generating $@ ; \
3     set -e ; \
4     : Extract the config macros ; \
5     $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \
6         sed -n -f tools/scripts/define2mk.sed > $@.tmp && \
7     mv $@.tmp $@

  include/autoconf.mk文件的依赖是include/config.h,config.h是执行make s5p_goni_config的时mkconfig脚本自动生成的文件,所以说,变更配置才会更新autoconf.mk文件,这也进一步说明autoconf.mk文件确实是用来描述当前配置选项的。

  分析一下上面用到的各条命令,第一句就是echo Generating include/autoconf.mk。第二句,set -e的作用是监测当前进程中命令的执行情况,当有命令执行出错时直接退出,这里的关键字是“当前进程”,如果你以 “./example.sh”方式执行某个脚本时出现的错误并不能被捕捉,因为这种方式的调用是以子进程的方式执行的,所以应该使用“source example.sh”或者“ .  example.sh”的方式。第三句,cpp -dM include/common.h | sed -n -f tools/scripts/define2mk.sed > include/autoconf.mk.tmp && mv include/autoconf.mk.tmp include/autoconf.mk,cpp是gcc的预处理器,-dM选项会输出很多预处理信息,比如宏定义,类型定义等等,而且它会递归地解析你包含的头文件,比如说这里的include/common.h文件中有一句#include <config.h>,那么cpp -dM include/common.h不但解析common.h文件中的内容并输出,而且会对config.h文件做相同的处理,显然,这条命令执行之后,就输出了全部的配置信息。接着通过管道操作,将这些信息用sed命令处理成特定的格式,最终输出到autoconf.mk文件中。

  TIMESTAMP_FILE和VERSION_FILE

 

1 $(TIMESTAMP_FILE):
2         @mkdir -p $(dir $(TIMESTAMP_FILE))
3         @LC_ALL=C date +'#define U_BOOT_DATE "%b %d %C%y"' > $@.tmp
4         @LC_ALL=C date +'#define U_BOOT_TIME "%T"' >> $@.tmp
5         @cmp -s $@ $@.tmp && rm -f $@.tmp || mv -f $@.tmp $@
 1 $(VERSION_FILE):
 2         @mkdir -p $(dir $(VERSION_FILE))
 3         @( localvers='$(shell $(TOPDIR)/tools/setlocalversion $(TOPDIR))' ; \
 4            printf '#define PLAIN_VERSION "%s%s"\n' \
 5             "$(U_BOOT_VERSION)" "$${localvers}" ; \
 6            printf '#define U_BOOT_VERSION "U-Boot %s%s"\n' \
 7             "$(U_BOOT_VERSION)" "$${localvers}" ; \
 8         ) > $@.tmp
 9         @( printf '#define CC_VERSION_STRING "%s"\n' \
10          '$(shell $(CC) --version | head -n 1)' )>>  $@.tmp
11         @( printf '#define LD_VERSION_STRING "%s"\n' \
12          '$(shell $(LD) -v | head -n 1)' )>>  $@.tmp
13         @cmp -s $@ $@.tmp && rm -f $@.tmp || mv -f $@.tmp $@
1 TIMESTAMP_FILE = $(obj)include/generated/timestamp_autogenerated.h
2 VERSION_FILE = $(obj)include/generated/version_autogenerated.h

  上述两个变量其实是include/genetated下的两个文件。

  timestamp生成的过程:提取TIMESTAMP_FILE路径,建立路径对应的文件夹,将date命令的输出信息重定位到表示时间戳的、后缀为.tmp的文件中,接着比较原有的时间戳文件和这个tmp文件的内容,如果相同,那么删除刚刚生成的文件,否则以tmp文件更换之前的时间戳文件。

  version生成的过程:提取VERSION_FILE路径,建立路径对应文件夹,将主Makefile开始定义的U-Boot version信息、gcc版本信息、ld版本信息都写到tmp结尾的文件中,接着比较version源文件和之前version文件,相同则删除tmp,否则覆盖原文件。

  其实Makefile中将以上两个变量声明为伪目标,因此每次执行make命令,都会在更新depend的时候更新时间戳文件和版本文件。

  depend的另外两个依赖generic-asm-offsets.h asm-offsets.h不是特别重要,暂不分析。

 

1 SUBDIR_TOOLS = tools
1 tools: $(VERSION_FILE) $(TIMESTAMP_FILE)
2     $(MAKE) -C $@ all

  SUBDIR_TOOLS是u-boot的第二个依赖,它的内容是tools这个文件夹,依赖是时间戳和版本文件,执行的命令是到tools文件夹下执行make,显然意思是到编译tools下面的各个文件,生成各种工具。

 1 $(OBJS):    depend
 2         $(MAKE) -C $(CPUDIR) $(if $(REMOTE_BUILD),$@,$(notdir $@))
 3 
 4 $(LIBS):    depend $(SUBDIR_TOOLS)
 5         $(MAKE) -C $(dir $(subst $(obj),,$@))
 6 
 7 $(LIBBOARD):    depend $(LIBS)
 8         $(MAKE) -C $(dir $(subst $(obj),,$@))
 9 
10 $(SUBDIRS): depend
11         $(MAKE) -C $@ all
12 
13 $(SUBDIR_EXAMPLES): $(obj)u-boot
14 
15 $(LDSCRIPT):    depend
16         $(MAKE) -C $(dir $@) $(notdir $@)
17 
18 $(obj)u-boot.lds: $(LDSCRIPT)
19         $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@

  OBJS就是arch/arm/cpu/armv7/start.o,要生成它,就需要到CPUDIR下面执行make,这很容易理解。说一下makefile的函数notdir吧,它的意思是去除所有的地址信息,仅留下最后一个/符号后面的文件名字,也就是取文件名操作。与之对应的dir函数,则实现去除文件名,取路径名的作用。

  LIBS包含arch cpu soc board等各个层次上的公用库,文件系统库,还有各种驱动库。这其实是一个多目标的依赖关系描述,不过各目标的依赖相同,生成规则统一,都是到目标变量所在路径下去执行make。详细点,就是make -C drivers/bios_emulator,make -C drivers/block... ...

  LIBBOARD指的是board/samsung/goni/libgoni.o,生成的命令很简单,到BOARD_DIR目录下执行make即可。

  LDSCRIPT内容是arch/arm/cpu/u-boot.lds,此条依赖对应的命令是make -C arch/arm/cpu u-boot.lds,在arch/arm/cpu/目录下的确能看到链接文件,但是并没有makefile,这一点不能理解。

 

  u-boot的依赖文件就分析到这里,下面看一下u-boot的生成过程:

 1 ifeq ($(CONFIG_SANDBOX),y)
 2 GEN_UBOOT = \
 3         cd $(LNDIR) && $(CC) $(SYMS) -T $(obj)u-boot.lds \
 4             -Wl,--start-group $(__LIBS) -Wl,--end-group \
 5             $(PLATFORM_LIBS) -Wl,-Map -Wl,u-boot.map -o u-boot
 6 else
 7 GEN_UBOOT = \
 8         UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
 9         sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
10         cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) $$UNDEF_SYM $(__OBJS) \
11             --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
12             -Map u-boot.map -o u-boot
13 endif

  这里只看核心部分:$(LD) $(LDFLAGS) $(__OBJS) --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) -Map u-boot.map -o u-boot,简而言之,就是将__OBJS __LIBS __PLATFORM_LIBS链接成u-boot,写到这里,就很容易理解了。

  现在回到u-boot.bin。

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

  u-boot.bin依赖u-boot,生成的命令是arm-linux-objcopy,接着还检查了镜像文件的大小,很简单的过程,不多解释

  顺带提一句u-boot.hex,其实就是使用arm-linux-objcopy的时候改变一下控制参数而已,将binary改成ihex。u-boot.srec的生成就是将控制参数替换成srec。

 

  对U-Boot Makefile的初步探索终于到头了,这个过程虽然很粗略,但确实纠正了一些makefile不准确的认识,学到了不少makefile以及shell的知识,并对u-boot镜像文件的建立有了全局的了解。

转载于:https://www.cnblogs.com/tech-lqh/p/8411052.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值