U-Boot Makefile分析(2) config.mk分析
浏览一下U-Boot各个子目录下的Makefile可以看到,几乎他们都会包含$(TOPDIR)/config.mk,那么这个文件进行了什么操作呢?简单概括:读入include/config.mk、include/autoconf.mk,指定ARCH CPU SoC Board等重要信息,并且加入各个层次上的编译选项;初始化编译处理选项、链接选项;最后有一个很重要的变量定义:
cmd_link_o_target = $(if $(strip $1),\
$(LD) $(LDFLAGS) -r -o $@ $1, \
rm -f $@; $(AR) rcs $@)
其实就是将输入的参数$1部分链接成目标,如果输入目标无效,则删除相关的目标,并且建立一个新的归档文件,与目标同名。
该文件中涉及大量编译、链接选项,篇幅较大,因此不深究它们,只是分析关键步骤。
1 ifeq ($(CURDIR),$(SRCTREE)) 2 dir := 3 else 4 dir := $(subst $(SRCTREE)/,,$(CURDIR)) 5 endif 6 7 ifneq ($(OBJTREE),$(SRCTREE)) 8 # Create object files for SPL in a separate directory 9 ifeq ($(CONFIG_SPL_BUILD),y) 10 obj := $(if $(dir),$(SPLTREE)/$(dir)/,$(SPLTREE)/) 11 else 12 obj := $(if $(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/) 13 endif 14 src := $(if $(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/) 15 16 $(shell mkdir -p $(obj)) 17 else 18 # Create object files for SPL in a separate directory 19 ifeq ($(CONFIG_SPL_BUILD),y) 20 obj := $(if $(dir),$(SPLTREE)/$(dir)/,$(SPLTREE)/) 21 22 $(shell mkdir -p $(obj)) 23 else 24 obj := 25 endif 26 src := 27 endif
CURDIR是Makefile内置变量,表示该Makefile的绝对路径,相当于执行pwd得到的输出,这里通过subst函数将CURDIR中的SRCTREE部分去掉,这样dir就表示当前路径相对于源码根目录的相对路径。
接下来是obj和src的处理,这里SRCTREE和OBJTREE相同,而且CONFIG_SPL_BUILD没定义,因此上述两个变量是空字符串。
1 CC_OPTIONS_CACHE_FILE := $(OBJTREE)/include/generated/cc_options.mk 2 CC_TEST_OFILE := $(OBJTREE)/include/generated/cc_test_file.o 3 4 -include $(CC_OPTIONS_CACHE_FILE) 5 6 cc-option-sys = $(shell mkdir -p $(dir $(CC_TEST_OFILE)); \ 7 if $(CC) $(CFLAGS) $(1) -S -xc /dev/null -o $(CC_TEST_OFILE) \ 8 > /dev/null 2>&1; then \ 9 echo 'CC_OPTIONS += $(strip $1)' >> $(CC_OPTIONS_CACHE_FILE); \ 10 echo "$(1)"; fi) 11 12 ifeq ($(CONFIG_CC_OPT_CACHE_DISABLE),y) 13 cc-option = $(strip $(if $(call cc-option-sys,$1),$1,$2)) 14 else 15 cc-option = $(strip $(if $(findstring $1,$(CC_OPTIONS)),$1,\ 16 $(if $(call cc-option-sys,$1),$1,$2))) 17 endif 18 19 # cc-version 20 # Usage gcc-ver := $(call cc-version) 21 cc-version = $(shell $(SHELL) $(SRCTREE)/tools/gcc-version.sh $(CC)) 22 binutils-version = $(shell $(SHELL) $(SRCTREE)/tools/binutils-version.sh $(AS))
这里相当于定义了两个小函数cc-option-sys,cc-option,其中后者调用前者,因此虽然前者提供了接口,但是并不使用,我不知道CONFIG_CC_OPT_CACHE_DISABLE是不是y,但是这里的核心操作就是,将要添加的CC编译选项以参数的形式传进去,然后尝试加入该选项来编译/dev/null,丢弃所有stdout/stderr输出。这里比较有意思的是,编译的源文件是/dev/null,估计作者的意思是,只要加入的这个选项存在,是编译器支持的就可以,而不是要求达到何种效果。如果选项有效,则向include/generated/cc_options.mk中写入该选项。
接着又定义了两个函数cc-version、binutils-version,两者都是通过执行shell脚本得到的,没用到,暂不分析。
1 AS = $(CROSS_COMPILE)as 2 3 # Always use GNU ld 4 LD = $(shell if $(CROSS_COMPILE)ld.bfd -v > /dev/null 2>&1; \ 5 then echo "$(CROSS_COMPILE)ld.bfd"; else echo "$(CROSS_COMPILE)ld"; fi;) 6 7 CC = $(CROSS_COMPILE)gcc 8 CPP = $(CC) -E 9 AR = $(CROSS_COMPILE)ar 10 NM = $(CROSS_COMPILE)nm 11 LDR = $(CROSS_COMPILE)ldr 12 STRIP = $(CROSS_COMPILE)strip 13 OBJCOPY = $(CROSS_COMPILE)objcopy 14 OBJDUMP = $(CROSS_COMPILE)objdump 15 RANLIB = $(CROSS_COMPILE)RANLIB 16 DTC = dtc
上面就很简单了,指明使用的各个工具,这是每个子Makefile都要包含config.mk的重要原因之一。
1 # Load generated board configuration 2 sinclude $(OBJTREE)/include/autoconf.mk 3 sinclude $(OBJTREE)/include/config.mk
这里虽然就两行,但是很重要,经过上一篇文章的分析,我们知道mkconfig s5p_goni_config的重要输出文件有两个:include/config.mk include/config.h,这里使用第一个,它指明了ARCH CPU SOC VENDOR BOARD,autoconf.mk目前还不知道是如何产生的,但是浏览以下可以知道里面的信息很多很重要,很多功能配置,这是子Makefile要包含config.mk的另一个重要原因。
1 CPUDIR=arch/$(ARCH)/cpu/$(CPU) 2 ifneq ($(SRCTREE)/$(CPUDIR),$(wildcard $(SRCTREE)/$(CPUDIR))) 3 CPUDIR=arch/$(ARCH)/cpu 4 endif 5 6 sinclude $(TOPDIR)/arch/$(ARCH)/config.mk # include architecture dependend rules 7 sinclude $(TOPDIR)/$(CPUDIR)/config.mk # include CPU specific rules 8 9 ifdef SOC 10 sinclude $(TOPDIR)/$(CPUDIR)/$(SOC)/config.mk # include SoC specific rules 11 endif 12 ifdef VENDOR 13 BOARDDIR = $(VENDOR)/$(BOARD) 14 else 15 BOARDDIR = $(BOARD) 16 endif 17 ifdef BOARD 18 sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules 19 endif
概括一下,上面的语句就是读入了几个config.mk:
sinclude arch/arm/config.mk 这是加入架构层次上的配置,比如设定CROSS_COMPILE ?= arm-linux-,比如通过上面的cc-option函数加入arm架构的编译选项:
$(call cc-option -marm)
sinclude arch/arm/cpu/armv7/config.mk 这是加入cpu层次上的配置,加入指令集上的配置选项,如$(call cc-option, -march=armv7-a, -march=armv5)
sinclude arch/arm/cpu/armv7/s5pc1xx/config.mk 这是加入soc层次上的配置,检查发现目录下没有此文件,但并不报错,sinclude和-include用法一样,当被包含的文件不
存在的时候,不报错,继续执行。
sinclude board/samsung/goni/config.mk 这是在board层次上加入配置,因为板卡资源决定了我们的内存容量和起始地址,所以这里指定了u-boot镜像的链接地址CONFIG_SYS_TEXT_BASE = 0x34800000,这个信息是board-specific。
1 ARFLAGS = $(error update your Makefile to use cmd_link_o_target and not AR) 2 RELFLAGS= $(PLATFORM_RELFLAGS) 3 DBGFLAGS= -g # -DDEBUG 4 OPTFLAGS= -Os #-fomit-frame-pointer 5 6 OBJCFLAGS += --gap-fill=0xff 7 8 gccincdir := $(shell $(CC) -print-file-name=include) 9 10 CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS) \ 11 -D__KERNEL__
注意ARFLAGS被设置成了makefile的error函数输出错误信息,这是提醒用户,在对目标文件归档的时候不要使用$(AR)了,要使用$(LD)。
接下来就是编译链接选项的设置,有太多选项了,什么SPL启动、VENDOR address等等,不细细分析了。
1 export HOSTCC HOSTCFLAGS HOSTLDFLAGS PEDCFLAGS HOSTSTRIP CROSS_COMPILE \ 2 AS LD CC CPP AR NM STRIP OBJCOPY OBJDUMP MAKE 3 export CONFIG_SYS_TEXT_BASE PLATFORM_CPPFLAGS PLATFORM_RELFLAGS CPPFLAGS CFLAGS AFLAGS
export到全局的变量如上所示。
终于到了最后!
1 # The _DEP version uses the $< file target (for dependency generation) 2 # See rules.mk 3 EXTRA_CPPFLAGS_DEP = $(CPPFLAGS_$(BCURDIR)/$(addsuffix .o,$(basename $<))) \ 4 $(CPPFLAGS_$(BCURDIR)) 5 $(obj)%.s: %.S 6 $(CPP) $(ALL_AFLAGS) -o $@ $< 7 $(obj)%.o: %.S 8 $(CC) $(ALL_AFLAGS) -o $@ $< -c 9 $(obj)%.o: %.c 10 $(CC) $(ALL_CFLAGS) -o $@ $< -c 11 $(obj)%.i: %.c 12 $(CPP) $(ALL_CFLAGS) -o $@ $< -c 13 $(obj)%.s: %.c 14 $(CC) $(ALL_CFLAGS) -o $@ $< -c -S 15 16 ######################################################################### 17 18 # If the list of objects to link is empty, just create an empty built-in.o 19 cmd_link_o_target = $(if $(strip $1),\ 20 $(LD) $(LDFLAGS) -r -o $@ $1,\ 21 rm -f $@; $(AR) rcs $@ )
这里很重要,是一些静态模式描述,分析这里的时候,着实让我纠结了一阵子。以前自己写Makefile的时候也会用静态模式,但理解是很不准确的,写依赖的时候理所当然地写$<,不知原因。举例,test.o处理依赖test.c以外,还依赖它所包含的头文件,所以该文件的完整约束描述为:
test.o: test.c \
header1path/header1.h
header2path/header2.h
...
上述依赖格式也正是CC -M的输出格式,而在编译生成test.o的时候,只需要test.c就够了,这就是$<的含义:依赖集中的第一个依赖。
最后那个函数在文章开始的时候已经分析过了。
ok.