uboot笔记之makefile分析

简介:    开始学习uboot,对于linux我还是个新手,在这只是对学习uboot做下笔记,文中错误之处请谅解。使用的uboot版本是2009.11。  要了解一个linux工程,一般要大致看懂它的makefile文件,我在学习uboot时也是先从其Makefile文件看起的,uboot的主Makefile就有三千多行,还有其他子文件夹中的Makefile。
    开始学习uboot,对于linux我还是个新手,在这只是对学习uboot做下笔记,文中错误之处请谅解。使用的uboot版本是2009.11。

  要了解一个linux工程,一般要大致看懂它的makefile文件,我在学习uboot时也是先从其Makefile文件看起的,uboot的主Makefile就有三千多行,还有其他子文件夹中的Makefile。如果想我一样对linux还是个新手,那么一开始接触Makefile可能会很头痛。可以先看一下于凤昌翻译的《GUN Make使用手册》,如果你英语好可以参考原版。
  首先,uboot第一次编译,make顺序是1.make mini2440_config 2.make. 这里假设已经为mini2440移植好了。在主Makefile中找到目标mini2440_config,如下:
  1. mini2440_config : unconfig
  2.          @$(MKCONFIG) $(@:_config=) arm arm920t mini2440 gc5084 s3c24x0

mini2440依赖于unconfig项,所以会去看unconfig目标,而此目标没有依赖项,所以它是最新的,unconfig一定会执行
  1. unconfig:
  2.     @rm -f $(obj)include/config.h $(obj)include/config.mk \
  3.     $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp \
  4.     $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep
这将删除所列出的这些文件,这些文件都是make mini2440_config后会产生的文件。这里有一个$(obj)变量,这个变量是在指定了输出目录时会有的,否则为空值。指定输出目录有两种方法,在README文件中有说明,第一种定义环境变量,如export BUILD_DIR=/tmp/build,然后在make mini2440_config.第二种是加在make命令中,如make O=/tmp/build/ mini2440_config (是O不是0)下面看下如何由O或者BUILD_DIR变量产生obj变量,主makefile中,大约88行,
   
  1. ifdef O
  2. ifeq ("$(origin O)", "command line")
  3. BUILD_DIR := $(O)
  4. endif
  5. endif
origin函数给出相应变量的原始类型,参考GUN Make使用手册,如果定义了O变量且类型是command line 则BUILD_DIR变量等于变量O。这顺便可以看出如果同时定义了环境变量和O变量,O变量会重写环境变量。接下来看
   
  1.  ifneq ($(BUILD_DIR),)    #如果BUILD_DIR变量不能于空
  2.  saved-output := $(BUILD_DIR) #定义saved-output变量等于BUILD_DIR变量

  3.  # Attempt to create a output directory.
  4.  $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) #[]代表test,-d是否是文件夹。如果没有BUILD_DIR文件  夹,则创建。-p为强制。

  5.  # Verify if it was successful.
  6.  BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd) #进入目录并打印出路径
  7.  $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist)) #如果不存在BUILD_DIR则    输出错误信息
  8.  endif # ifneq ($(BUILD_DIR),)

  9.  #接下来都是定义根据BULID_DIR依次定义各个变量。
  10.  OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
  11.  SRCTREE := $(CURDIR) #CURDIR是make中的标准变量 不指定-C就是当前目录。
  12.  TOPDIR := $(SRCTREE)
  13.  LNDIR := $(OBJTREE)
  14.  export TOPDIR SRCTREE OBJTREE
  15.  
  16.  MKCONFIG := $(SRCTREE)/mkconfig
  17.  export MKCONFIG
  18.  
  19.  ifneq ($(OBJTREE),$(SRCTREE))
  20.  REMOTE_BUILD := 1
  21.  export REMOTE_BUILD
  22.  endif
  23.  
  24.  # $(obj) and (src) are defined in config.mk but here in main Makefile
  25.  # we also need them before config.mk is included which is the case for
  26.  # some targets like unconfig, clean, clobber, distclean, etc.
  27.  # 上面的英文注释,说明这时还没有包含config.mk文件,但是因为要用到所以定义了obj src变量
  28.  ifneq ($(OBJTREE),$(SRCTREE)) #根据上面,如果定义了BULID_DIR,一般这个了变量就不等了
  29.  obj := $(OBJTREE)/
  30.  src := $(SRCTREE)/
  31.  else                
  32.  obj :=
  33.  src :=
  34.  endif
  35.  export obj src
根据上面详细的分析,已经解释清楚了 obj src变量的来源。
接着回到这句
  1. mini2440_config : unconfig
  2.             @$(MKCONFIG) $(@:_config=) arm arm920t mini2440 gc5084 s3c24x0
@起始的行将不回显命令,MKCONFIG变量代表mkconfig脚本文件
$(@:_config=) 的结果是mini2440。它来源于$(var:xx=yy)此句表示将变量var中以xx结尾的部分替换成yy。$@代表目标文件mini2440_config.注意在$()的括号中的变量是不需要再加$的。

再看mkconfig脚本,简要说明一下。
一开始的while循环是处理带-参数的,一般不会有。检查参数等。
 if [ "$SRCTREE" != "$OBJTREE" ] ; then
这段是如果源文件和目标文件不是在一起和在一起,分别用不同的方法,进入include文件夹,删除旧的asm连接文件夹,并创建新的asm连接到asm-$2文件夹,$2变量在这是arm。
然后删除asm-$2的连接,然后在新建连接。
再在include文件夹下创建一个config.mk文件,这个文件内容按照本例应该为ARCH = arm CPU = arm920t BOARD = mini2440 VENDOR = gc5084 SOC = s3c24x0。最后创建一个config.h,这个文件也只有两个,分别为#include 和#include 。
这样所有make mini2440_config所做的事情就都做完了。接着回看主makefiel.
在主Makefle中,有一个条件语句很重要,大约在148行有如下一句,
 ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))
此句判断有没有生成include/config.mk文件,有这个文件则makefile认为是配置过了make mini2440_config。所以再make时就会包含其下面的部分,这部分很大,为了方便描述称这部分为A部分,否则则包含else部分(称为B部分),else大约在483行,else后只是简单报错退出。
下面就继续看,配置后,make所包含的这个A部分。
在这个条件语句后紧接着,包含include/autoconf.mk.dep include/autoconf include/config.mk
这两句也很重要,它是生成autoconf.mk和autoconf.mk.dep的起因。makefile在包含其他makefiel时即有(s)include语句时,会尝试更新它,即以被包含的makefile作为一个目标去make。这样只要是运行过make mini2440_config。在make任何目标,都会进入A部分。A部分中有这两个.mk文件为目标的规则,如下:
  1. $(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h
  2.          @$(XECHO) Generating $@ ; \
  3.          set -e ; \
  4.          : Generate the dependancies ; \
  5.          $(CC) -x c -DDO_DEPS_ONLY -M $(HOSTCFLAGS) $(CPPFLAGS) \
  6.                  -MQ $(obj)include/autoconf.mk include/common.h > $@
  7.  
  8.  $(obj)include/autoconf.mk: $(obj)include/config.h
  9.          @$(XECHO) Generating $@ ; \
  10.          set -e ; \
  11.          : Extract the config macros ; \
  12.          $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \
  13.                  sed -n -f tools/scripts/define2mk.sed > $@.tmp && \
  14.          mv $@.tmp $@
这个规则看起来稍微有点复杂,但是主要就是根据include/common.h其中包含了config.h等文件,利用define2mk.sed脚本。处理这些头文件中的宏部分,生成一个整体的可被此主Makefil识别的autoconfi.mk文件。autoconfig.mk.dep文件内容是autoconfig.mk文件产生时所依赖的有那些文件。这部分详细可以参考gcc手册和编写sed脚本的资料。
介绍完autoconfig.mk文件部分。继续看A部分中的其他部分。

 然后接着一般要添加一句CROSS_COMPILE := arm-linux-,定义你已经安装好的交叉编译工具,这里是arm-linux-。在包含顶层config.mk。这个文件中有配置了很多编译相关的变量,此mk文件内容大致如下:
 
  1. ifneq ($(OBJTREE),$(SRCTREE)) #是否有定义输出目标,分别生成obj和src变量的值。
  2.  ifeq ($(CURDIR),$(SRCTREE)) #当前文件夹是否和源文件文件夹相同。一般是相同的
  3.  dir :=
  4.  else
  5.  dir := $(subst $(SRCTREE)/,,$(CURDIR))
  6.  endif
  7.  
  8.  obj := $(if $(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/)
  9.  src := $(if $(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/)
  10.  
  11.  $(shell mkdir -p $(obj))
  12.  else            #未定义输出目标文件夹,则为空。
  13.  obj :=
  14.  src :=
  15.  endif
然后是主机编译器HOSTCC等。
 
  1. cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \
  2. > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)
上面这句需要看看,之后很多call函数会调用它,~~
再声明一些编译工具变量
  1.  AS = $(CROSS_COMPILE)as
  2.  LD = $(CROSS_COMPILE)ld
  3.  CC = $(CROSS_COMPILE)gcc
  4.  CPP = $(CC) -E
  5.  AR = $(CROSS_COMPILE)ar
  6.  NM = $(CROSS_COMPILE)nm
  7.  LDR = $(CROSS_COMPILE)ldr
  8.  STRIP = $(CROSS_COMPILE)strip
  9.  OBJCOPY = $(CROSS_COMPILE)objcopy
  10.  OBJDUMP = $(CROSS_COMPILE)objdump
  11.  RANLIB = $(CROSS_COMPILE)RANLIB
然后根据配置项包含各个文件夹下的config.mk文件。
  1. ifdef ARCH    #此.mk文件内容主要是指定一些和体系结构特定的编译选项,最后的LDSCRIPT定义了uboot镜像的地址分配文件
  2.  sinclude $(TOPDIR)/lib_$(ARCH)/config.mk # include architecture dependend rules
  3.  endif
  4. #CPU 和 SOC 的.mk文件定义了浮点数等编译选项,~~
  5.  ifdef CPU    
  6.  sinclude $(TOPDIR)/cpu/$(CPU)/config.mk # include CPU specific rules
  7.  endif
  8.  ifdef SOC
  9.  sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk # include SoC specific rules
  10.  endif
  11.  ifdef VENDOR
  12.  BOARDDIR = $(VENDOR)/$(BOARD)
  13.  else
  14.  BOARDDIR = $(BOARD)
  15.  endif
  16.  ifdef BOARD    #此.mk文件仅仅定义了TEXT_BASE,镜像加载到内存的起始地址。
  17.  sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules
  18.  endif
然后配置了CPPFLAGS,CFLAGS等编译选项。编译选项部分可参考其他资料。~~
最后有指定编译规则,如下
  1. BCURDIR := $(notdir $(CURDIR)) #notdir是去掉路径部分。
  2. #以下都是编译各种类型文件时的规则,主要是第一个变量定义的规则。(将以下文件类型注明!)
  3.  $(obj)%.s: %.S
  4.          $(CPP) $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $
  5.  $(obj)%.o: %.S
  6.          $(CC) $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $ -c
  7.  $(obj)%.o: %.c
  8.          $(CC) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $ -c
  9.  $(obj)%.i: %.c
  10.          $(CPP) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $ -c
  11.  $(obj)%.s: %.c
  12.          $(CC) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $ -c -S
再回到主Makefile中。
定义OBJS和LIBS变量,OBJS代表目标文件,本例中只会包含start.o。LIBS是库文件,主要包含以下文件夹lib_generic,cpu,fs,driver,commom等,下的一些.a库文件。
然后会添加gcc的库。
下面两句是定义all目标的规则,all目标是make的第一个目标即默认目标。它依赖变量ALL,ALL为各种格式的镜像。
  1. ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)
  2. all: $(ALL)
在下面是一堆各种镜像生成的规则,这里看一下其中uboot.bin的规则。
  1. $(obj)u-boot.bin: $(obj)u-boot
  2.                  $(OBJCOPY) ${OBJCFLAGS} -O binary $ $@ #OBJCOPY是在
顶层config.mk文件中定义的转换工具.
在看它的依赖项$(obj)uboot的生成规则。
  1. $(obj)u-boot: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
  2.                  $(GEN_UBOOT)
依次看其依赖项,
*****目标depend*****
  1. depend dep: $(TIMESTAMP_FILE) $(VERSION_FILE) $(obj)include/autoconf.mk
  2.                  for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
  其中依赖项$(TIMESTAMP)是一个时间标志文件,$(VERSION_FILE)是个版本标志文件,并且被声明为假想目标所以一定会被执行,执行的结果是更新这两个文件,autoconf.mk上面已经说过。这个规则中的命令是依次循环进入$(SUBDIRS)目录中执行make _depend,子目录中的makefile稍后分析。$(SUBDIRS)包含tools example/standalone example/api.
******假想目标$(SUBDIRS)******
  1. $(SUBDIRS): depend
  2.                  $(MAKE) -C $@ all
$@代表规则中的目标,所以这条命令会在$(SUBDIRS)下执行的make all,$(SUBDIRS)的值上面已经说过。依赖目标depend参考上面。即会在tool,example等目录下make。
******目标$(OBJS)******
  1. $(OBJS): depend
  2.                  $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
make这个目录cpu/arm920t

******目标$(LIBS)******
  1. $(LIBS): depend $(SUBDIRS)
  2.                  $(MAKE) -C $(dir $(subst $(obj),,$@))
这个LIBS变量中的文件比较多,依次进入这些目录make。这样可以产生相应的库文件

******目标LDSCRIPT******
  1. $(LDSCRIPT): depend
  2.                   $(MAKE) -C $(dir $@) $(notdir $@)
这个目标的变量是一个连接的脚本,上文有提到。make它。

现在看以下各个子目录中Makefile文件,以cpu/arm920t目录为例.
 
  1.  include $(TOPDIR)/config.mk    #包含顶层config.mk,这个文件内容上面有提及。
  2.  
  3.  LIB = $(obj)lib$(CPU).a
  4.  
  5.  START = start.o
  6.  
  7.  COBJS-y += cpu.o
  8.  COBJS-$(CONFIG_USE_IRQ) += interrupts.o
  9.  
  10.  SRCS := $(START:.o=.S) $(SOBJS:.o=.S) $(COBJS-y:.o=.c) #所有源文件变量
  11.  OBJS := $(addprefix $(obj),$(COBJS-y) $(SOBJS))
  12.  START := $(addprefix $(obj),$(START))

  13.   #make的目标,这个规则是靠隐含规则执行的。依赖项.depend,此文件包含本目录所有源文件所依赖的文件。稍后分析如何产生这个文件。
  14.  all: $(obj).depend $(START) $(LIB)
  15.  
  16.  $(LIB): $(OBJS)        #库文件靠目标文件通过AR转换而来
  17.          $(AR) $(ARFLAGS) $@ $(OBJS)
  18.  
  19.  #########################################################################
  20.  
  21.  # defines $(obj).depend target
  22.  include $(SRCTREE)/rules.mk    #产生.depend文件的makefile
  23.  
  24.  sinclude $(obj).depend        #包含.depend文件
再贴出rules.mk
  1. _depend: $(obj).depend
  2.  
  3.  $(obj).depend: $(src)Makefile $(TOPDIR)/config.mk $(SRCS)
  4.                  @rm -f $@
  5.                  @for f in $(SRCS); do \
  6.                          g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \
  7.                          $(CC) -M $(HOSTCFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \
  8.                  done
整体看一下make的流程,make 找到默认目标all 它首先依赖.depend文件。而这个目标在此Makefile最后用sinclude包含,这个目标会利用编译器的功能列出$(SRCS)所有源文件的依赖。则all的依赖.depend ,$(START)和 $(LIB) 组成所有依赖的文件,然后隐含规则编译所有文件。

然后我们回到主makefile,uboot的makefile主要的内容也就差不多完了,剩下的很多行就是各个_config项,如mini2440_config,最后有clean等清除作用的目标。

  





目录
相关文章
|
3月前
|
Shell Linux C++
Makefile编译实战
Makefile编译实战
54 0
|
8月前
|
NoSQL IDE 小程序
Linux第七章之gdb与makefile使用
3. 如果hello文件不存在,或是hello所依赖的后面的hello.o文件的文件修改时间要比hello这个文件新(可以用touch 测试),那么,他就会执行后面所定义的命令来生成hello这个文件。5. 当然,你的C文件和H文件是存在的啦,于是make会生成 hello.o 文件,然后再用 hello.o 文件声明make的终极任务,也就是执行文件hello了。2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“hello”这个文件,并把这个文件作为最终的目标文件。
64 0
|
12月前
|
Linux 编译器 C语言
『Linux从入门到精通』第 ⑦ 期 - Linux编译器——gcc/g++(预处理、编译、汇编、链接)
『Linux从入门到精通』第 ⑦ 期 - Linux编译器——gcc/g++(预处理、编译、汇编、链接)
86 0
|
存储 Ubuntu Unix
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解(一)
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解
280 0
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解(一)
|
NoSQL 编译器 Linux
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解(三)
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解
239 0
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解(三)
|
自然语言处理 编译器 Linux
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解(二)
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解
468 0
【三、深入浅出GCC编译器】一个源文件到可执行文件是如何生成的:GCC编译工具链及编译参数详解(二)
|
NoSQL Linux 项目管理
linux系统编程 (四) gdb调试与makefile
linux系统编程 (四) gdb调试与makefile
142 0
linux系统编程 (四) gdb调试与makefile