什么是配置?
还是从编译uboot的第二个命令开始我们的旅程,如下命令:
[email protected]:~/work/MYiR-iMX-Uboot$ make mys_imx6ull_14x14_nand_defconfig
这个命令会打印一些信息,我们看到最后有个打印信息:
#
# configuration written to .config
#
这个英语简单不,一目了然,就是配置被写入了.config。这个.config是个啥,我们不妨打开这个文件看一看:
#
# Automatically generated file; DO NOT EDIT.
# U-Boot 2016.03 Configuration
#
CONFIG_CREATE_ARCH_SYMLINK=y
CONFIG_HAVE_GENERIC_BOARD=y
CONFIG_SYS_GENERIC_BOARD=y
# CONFIG_ARC is not set
CONFIG_ARM=y
# CONFIG_AVR32 is not set
# CONFIG_BLACKFIN is not set
# CONFIG_M68K is not set
# CONFIG_MICROBLAZE is not set
# CONFIG_MIPS is not set
# CONFIG_NDS32 is not set
# CONFIG_NIOS2 is not set
# CONFIG_OPENRISC is not set
# CONFIG_PPC is not set
# CONFIG_SANDBOX is not set
# CONFIG_SH is not set
# CONFIG_SPARC is not set
# CONFIG_X86 is not set
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="mx6"
CONFIG_SYS_VENDOR="myir"
CONFIG_SYS_BOARD="mys_imx6ull"
CONFIG_SYS_CONFIG_NAME="mys_imx6ull"
......
看起来好像挺规范的,注释也是符合脚本化语言的习惯,去掉注释符号#的每一行,格式都是:
CONFIG_xxx=value
这个value的值,我们看到有'y',也有字符串。没错,这个就是kbuild框架的配置文件格式,这个配置赋值将会被Makefile里面使用到,用以选择特定的编译行为。这里我们举个例子,比如我们打开"arch/arm/cpu/armv7/Makefile",有如下语句:
obj-$(CONFIG_SYS_ARCH_TIMER) += arch_timer.o
我们看这里的,有一个"CONFIG_SYS_ARCH_TIMER"变量,如果这个变量取值为'y',那么以上赋值就变成:
obj-y += arch_timer.o
这样,我们就可以把一些目标追加到"obj-y"这个变量里面,如果CONFIG_SYS_ARCH_TIMER没有值,则这个变量追加到"obj-"变量里面,这就根据配置文件进行了区分。后面我们可以对obj-y指定的目标进行编译,obj-指定的目标不做处理即可。
配置是如何出场的?
既然我们了解了什么是配置,那么我们自然会想到一个问题,配置是怎么被引入make过程的?就是说上面那个文件"arch/arm/cpu/armv7/Makefile"里面是如何识别到保存在.config里面的"CONFIG_SYS_ARCH_TIMER"变量的?因为我们根本没有发现"arch/arm/cpu/armv7/Makefile"里面有引入.config的任何语句,是不是很奇怪,用到的地方没有引入,难道是天上掉下来的吗?
做软件,有个思路,大家不妨了解下有没有道理,就是任何软件上的技术点,都是有来头的,也都是有人去实现的,没有天上掉下来那么简单。你想象一下,多少万年前,人类还是蛮荒动物的年代,是不是连一根针都是没有的?不要说针,就是金属都没有,有的只有石头和木棍。程序也是一个思路,所有的东西都是有人去实现才有的。好了,说了一大堆,我就是想说,这个配置一定是有在某个地方被引入,我们要做的,就是找到这个地方,哪里引入了这个配置.config。
于是,我们先在根目录Makefile里面找,哪里引入了.config。但是幼小的心灵再一次受到打击,我们没有发现有地方直观include这个.config。但是,我们发现了一丁点线索,根目录主Makefile第249行有这么一句:
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
将.config赋值给了KCONFIG_CONFIG,注意,这个KCONFIG_CONFIG如果已经有定义,.config是不会覆盖它的。然后导出这个变量,使得各个子Makefile都能看到这个变量。但是这个线索,貌似没有太大帮助,因为并没有找到哪里有诸如"include $(KCONFIG_CONFIG)"之类的语句,但我们找到如下的一段代码:
# If .config is newer than include/config/auto.conf, someone tinkered
# with it and forgot to run make oldconfig.
# if auto.conf.cmd is missing then we are probably in a cleaned tree so
# we execute the config step to be sure to catch updated Kconfig files
include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
@# If the following part fails, include/config/auto.conf should be
@# deleted so "make silentoldconfig" will be re-run on the next build.
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
{ rm -f include/config/auto.conf; false; }
@# include/config.h has been updated after "make silentoldconfig".
@# We need to touch include/config/auto.conf so it gets newer
@# than include/config.h.
@# Otherwise, 'make silentoldconfig' would be invoked twice.
$(Q)touch include/config/auto.conf
这段代码里面,KCONFIG_CONFIG作为目标的依赖。也就是说,要生成目标,就要先生成$(KCONFIG_CONFIG),不出意外的话,就是要生成.config。但是这个.config我们在运行make *defconfig的时候,就已经生成了,所以,这里貌似也并没有什么卵用。于是,追踪陷入了僵局,进行不下去了,我们是不是可以宣告失败了?
路是人走出来的,我们要坚信
且慢,越是到关键时刻,就越是需要有耐心,也许,成功离我们并不太遥远了。我们再静下来心来想一想,我们编译uboot的三条命令,是不是第三个命令我们只要敲入"make",最后就会生成u-boot.imx?然后这里面的make,就已经用到了我们之前的那些配置,也就是.config?那么我猜想,我们敲入make命令,在执行make命令的过程中,一定有哪里引入了这个.config配置,否则是如何生成u-boot.imx的呢?这么一想,我感觉信心又来了,但是路途依然不容乐观,我们需要耐着性子过一遍make的执行流程。
学过make的一定都知道,make执行过程第一步是建立内部变量和规则视图,第二步是寻找终极目标,去努力构建这个最终的目标。主Makefile第129行、第196行,有如下代码:
# That's our default target when none is given on the command line
PHONY := _all
_all:
# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif
看到没,这个"_all"就是最终要构建的目标,根据KBUILD_EXTMOD的不同值,依赖不同的目标,没有设置KBUILD_EXTMOD的时候,_all依赖all,我们再找下这个all,我们找到:
all: $(ALL-y)
ifneq ($(CONFIG_SYS_GENERIC_BOARD),y)
@echo "===================== WARNING ======================"
@echo "Please convert this board to generic board."
@echo "Otherwise it will be removed by the end of 2014."
@echo "See doc/README.generic-board for further information"
@echo "===================================================="
endif
ifeq ($(CONFIG_DM_I2C_COMPAT),y)
@echo "===================== WARNING ======================"
@echo "This board uses CONFIG_DM_I2C_COMPAT. Please remove"
@echo "(possibly in a subsequent patch in your series)"
@echo "before sending patches to the mailing list."
@echo "===================================================="
endif
这个all依赖"ALL-y",然后我们再找下,这个ALL-y是个什么鬼。我们找到如下一堆赋值:
# Always append ALL so that arch config.mk's can add custom ones
ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
ifeq ($(CONFIG_SPL_FSL_PBL),y)
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
else
ifneq ($(CONFIG_SECURE_BOOT), y)
# For Secure Boot The Image needs to be signed and Header must also
# be included. So The image has to be built explicitly
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
endif
......
第一项追加比较明显,向ALL-y里面追加了:
u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
后面的追加都是根据配置进行,这里的配置,貌似是.config里面的格式嘛?怎么我们在寻找哪里引入的路上,又发现了使用例子了?那也就是说,到了这里,应该是已经识别了.config里面的内容才对吧?
我们这里着重追踪一下u-boot.bin吧,详细过程大家可以自己看下,很容易的,就是找找字符串而已,我们直接说结果,追踪这个u-boot.bin的过程中,我们追踪到了u-boot。这个u-boot目标的规则如下:
u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
$(call if_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
$(call cmd,smap)
$(call cmd,u-boot__) common/system_map.o
endif
u-boot依赖$(u-boot-init) $(u-boot-main)这些,我们在地下不远处,发现,这个$(u-boot-init) $(u-boot-main)又依赖如下:
# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ;
# Handle descending into subdirectories listed in $(vmlinux-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language
PHONY += $(u-boot-dirs)
$(u-boot-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
看到没,依赖$(u-boot-dirs),这个$(u-boot-dirs)依赖prepare和scripts,看字面就能知道,这个prepare是准备的意思,就是说在构建这一整条链的时候,需要先构建prepare。然后我们找这个prepare目标,非常有意思的是,这个prepare依赖一长串下级prepare,如下:
PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3
# prepare3 is used to check if we are building in a separate output directory,
# and if so do:
# 1) Check that make has not been executed in the kernel src $(srctree)
prepare3: include/config/uboot.release
ifneq ($(KBUILD_SRC),)
@$(kecho) ' Using $(srctree) as source for U-Boot'
$(Q)if [ -f $(srctree)/.config -o -d $(srctree)/include/config ]; then \
echo >&2 " $(srctree) is not clean, please run 'make mrproper'"; \
echo >&2 " in the '$(srctree)' directory.";\
/bin/false; \
fi;
endif
# prepare2 creates a makefile if using a separate output directory
prepare2: prepare3 outputmakefile
prepare1: prepare2 $(version_h) $(timestamp_h) \
include/config/auto.conf
ifeq ($(CONFIG_HAVE_GENERIC_BOARD),)
ifeq ($(CONFIG_SYS_GENERIC_BOARD),y)
@echo >&2 " Your architecture does not support generic board."
@echo >&2 " Please undefine CONFIG_SYS_GENERIC_BOARD in your board config file."
@/bin/false
endif
endif
ifeq ($(wildcard $(LDSCRIPT)),)
@echo >&2 " Could not find linker script."
@/bin/false
endif
archprepare: prepare1 scripts_basic
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
# All the preparing..
prepare: prepare0
依赖链是prepare->prepare0->archprepare->prepare1->prepare2--------------------->prepare3
---------------------->outputmakefile
->$(version_h)
->$(timestamp_h)
->include/config/auto.conf
这里我们看到,prepare1除了依赖prepare2,还依赖其它三项,其中第三项是include/config/auto.conf,这个刚好符合我们以上提到的一个模式规则目标"include/config/%.conf"。于是,我们发现,这个模式规则的命令是会被执行的,而且是在prepare的早期执行,我们来看下命令:
include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
{ rm -f include/config/auto.conf; false; }
$(Q)touch include/config/auto.conf
第一条以主Makefile为规则,执行silentoldconfig目标。这个我们在《make menuconfig命令的过程追踪》里面都讲述过,config结尾的目标,都有一个模式规则入口,如下:
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
这个规则的命令展开为:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig silentoldconfig
这里依然会进一步包含scripts/kconfig/Makefile,这里面有对应silentoldconfig的规则:
silentoldconfig: $(obj)/conf
$(Q)mkdir -p include/config include/generated
$< $(silent) --$@ $(Kconfig)
除了脚本,咱也得看看C代码
这个规则首先建立include/config和include/generated目录,然后执行conf程序,以--silentoldconfig为选项,Kconfig为输入。到了这里,如果对细节还是感兴趣,那么建议阅读以下conf这个程序的源代码。这里也可以简单分析以下源码,conf.c第511行,将会获得silentoldconfig选项,执行代码为:
case silentoldconfig:
sync_kconfig = 1;
break;
这里把sync_kconfig标志置为1,那么第566行的if语句,将会执行:
if (sync_kconfig) {
name = conf_get_configname();
if (stat(name, &tmpstat)) {
fprintf(stderr, _("***\n"
"*** Configuration file \"%s\" not found!\n"
"***\n"
"*** Please run some configurator (e.g. \"make oldconfig\" or\n"
"*** \"make menuconfig\" or \"make xconfig\").\n"
"***\n"), name);
exit(1);
}
}
这里调用了conf_get_configname()函数,这个函数用于获得"KCONFIG_CONFIG"环境变量的值,如果该环境变量不存在,则默认值为".config"。如果".config"不存在,将会打印出错信息并退出,所有在此之前,一定得有".config"文件。怎么样,隐藏得深不深?这里居然用到了".config"。到main函数的最后面,一下代码分支也将得到执行:
if (sync_kconfig) {
/* silentoldconfig is used during the build so we shall update autoconf.
* All other commands are only used to generate a config.
*/
if (conf_get_changed() && conf_write(NULL)) {
fprintf(stderr, _("\n*** Error during writing of the configuration.\n\n"));
exit(1);
}
if (conf_write_autoconf()) {
fprintf(stderr, _("\n*** Error during update of the configuration.\n\n"));
return 1;
}
}
这里用到了conf_write_autoconf()函数,这个函数有兴趣可以展开看一下,我大概了解了一下,里面会生成include/config/auto.conf文件,这里面包含之前从.config里面读取的各种配置变量,差不多跟.config是一样的,只是去掉了那些#号开头的语句。我们可以简单理解为,.confg经过处理,写到了include/config/auto.conf里面。这个auto.conf引用的地方貌似就比较多了。比如主Makefile的第486行,就include了这个文件,不过前面加了一个'-'号,意思大概就是如果该文件不存在,include不出错。也就是说,这里include的时候,这个auto.conf有可能还不存在,确实,我们分析了发现,我们在输入make命令的时候,解析到Makefile第486行时,是没有这个auto.conf文件的。
但是,我们在编译目标的时候,一般会引入scripts/Makefile.build,这个里面也包含auto.conf,第47行:
-include include/config/auto.conf
到了这里的时候,其实auto.conf文件已经有了,所以配置文件应该是能够被引入了。这里大家可以自行跟踪看看。接下来因为auto.conf已经被包含了,那些CONFIG_XXX变量就已经能够识别了,你的配置就和make过程关联了起来。最后,还有一个细节,我们发现auto.conf里面的配置变量赋值使用的是'='号,而不是':='。这里可能是有讲究的,'='是递归变量赋值,这样,变量不管在哪里定义,是前还是后,都能被整个make识别,否则我们在后面包含auto.conf,前面的语句岂不是用不了auto.conf里面的值了,这个应该还是有讲究的。