uboot構建框架5-配置文件和make過程是如何聯繫起來的

什麼是配置?

還是從編譯uboot的第二個命令開始我們的旅程,如下命令:

sunke@droresrv:~/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裏面的值了,這個應該還是有講究的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章