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

发布者:悠然自在最新更新时间:2025-01-24 来源: cnblogs关键字:U-Boot  主控 手机看文章 扫描二维码
随时随地手机看文章

  这次分析源码根目录下的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)

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))

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 ,那么cpp -dM include/common.h不但解析common.h文件中的内容并输出,而且会对config.h文件做相同的处理,显然,这条命令执行之后,就输出了全部的配置信息。接着通过管道操作,将这些信息用sed命令处理成特定的格式,最终输出到autoconf.mk文件中。

[1] [2]
关键字:U-Boot  主控 引用地址:U-Boot Makefile分析(5)主控Makefile分析

上一篇:U-Boot Makefile分析(4)具体子Makefile的分析
下一篇:U-Boot bootargs简析

小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved