天天看点

uboot构建框架5-配置文件和make过程是如何联系起来的

什么是配置?

还是从编译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里面的值了,这个应该还是有讲究的。