天天看點

Linux核心建構系統之三

頂層Makefile的總體架構結構 

既然前面我們說過頂層Makefile最為重要,那麼我們就先來研究一下這個檔案。在你用VI編輯器打開這個檔案時,千萬别被它的複雜吓倒。這個檔案雖然行數頗多,但其實裡面也是有道道可尋的,我們可以抽出其中最重要的架構結構出來,列出如下(稍做整理和縮進):

​​​​

從上面的架構中可以看出,影響核心建構過程動作的有數個變量,分别是:KBUILD_SRC, KBUILD_OUTPUT, skip-makefile, mixed-targets, config-targets 和 dot-config。我們将它們分成兩組,前三為一組,後三個為一組。顯然,前一組影響着架構中最外面的兩個ifeq-endif塊,而後一組則決定了第二個ifeq-endif塊内的邏輯。

對于第一組變量,其實是為了支援"make O=1 [Targets]", 也就是為了支援将輸出檔案放到另外目錄(不同于核心源代碼目錄的其他目錄)的功能而準備的。那到底是如何支援的呢?我們說其實這是兩次調用頂層Makefile的過程。

首先,當我們輸入"make O=dir [Targets]"指令的時候,會第一次調用頂層Makefile。而此時變量KBUILD_SRC沒有定義,是以會進入到第一個ifeq-endif塊。進去之後,條件判斷ifeq ("$(origin O)", "command line")會發現指令行裡有變量O的定義,并且其值為dir。是以接下來,make會把變量O的值,也就是dir賦給變量KBUILD_OUTPUT。

既然KBUILD_OUTPUT的值為dir,是以條件判斷ifneq ($(KBUILD_OUTPUT),)就必定成立,是以make會去處理上面的A部分。處理完A部分後,會将變量skip-makefile設定為1。是以顯然,在退出第一個ifeq-endif塊後,進入第二個ifeq-endif塊的條件判斷就得不到滿足。是以我們說,其實到這裡,第一次調用Makefile的過程就提前結束了。

那第二次調用頂層Makefile的過程又是在哪裡呢?答案是在對A部分的處理上。我們列出A部分中關鍵的代碼:

PHONY += $(MAKECMDGOALS) sub-make
 
$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
        $(Q)@:
 
sub-make: FORCE
        $(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \
        KBUILD_SRC=$(CURDIR) \
        KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile \
        $(filter-out _all sub-make,$(MAKECMDGOALS))      

從這裡,我們可以看出,不管指令"make O=dir [Targets]"中的Targets個數有多少個,它們都是要依賴于 sub-make。是以,對A部分的處理,其實就是執行sub-make規則的指令。在這個指令中,KBUILD_OUTPUT值為dir,CURDIR值為目前的核心源碼目錄。因為make指令中沒有設定M變量或者SUBDIRS變量,是以KBUILD_EXTMOD值為空。是以這個指令就簡化為:

make -C dir KBUILD_SRC=`pwd` KBUILD_EXTMOD="" -f `pwd`/Makefile [Targets]

這樣一個指令的執行就和執行沒帶O=dir的"make [Targets]"指令差不多了。其差别隻在于将輸出檔案存到dir目錄,而非核心源碼樹目錄而已。

從上面的架構中看出來,如果隻是簡單的"make [Targets]",那麼就不會進去到第一個ifeq-endif塊,而直接進到第二個ifeq-endif塊中去處理了。這就要涉及到上面說到的第二組變量了。我們說,設定第二組變量的目的,就是為了适應make指令後面所跟Targets數目和類型上的多樣性而已。

在詳細分析第二個ifeq-endif塊之前,讓我們看看第二組變量的含義以及它們的初始值設定情況。由于我們之前已經對幾乎所有Targets的作用及分類做了說明,是以了解它們不應該很難。如上面架構圖中所示的那樣,讓我們先抽出B2部分中和這個相關的代碼如下:

no-dot-config-targets := clean mrproper distclean \
                         cscope TAGS tags help %docs check% \
                         include/linux/version.h headers_% \
                         kernelrelease kernelversion
 
config-targets := 0
mixed-targets  := 0
dot-config     := 1
 
ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
        ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
                dot-config := 0
        endif
endif
 
ifeq ($(KBUILD_EXTMOD),)
        ifneq ($(filter config %config,$(MAKECMDGOALS)),)
                config-targets := 1
                ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
                        mixed-targets := 1
                endif
        endif
endif      

上面的代碼一開始就設定了變量no-dot-config-targets,它指代的是那些和.config沒有關系的目标,這已經在前面對目标進行分類的時候說過了。接下來,它将三個變量分别賦初值0,0和1。再接下來,它會判斷make指令中的[Targets]部分是否有且僅有變量no-dot-config-targets所指代的那些目标,如果是的話它就将變量dot-config的值設定為0。這表明本次make指令所make的目标都是和.config檔案沒有關聯的,既不是那些會産生.config的配置目标,也不是那些需要使用.config檔案内容的建構目标。

好,上面的條件判斷ifeq ($(KBUILD_EXTMOD),)訓示了如果我們不是編譯外部子產品的話就進去到這最後一個ifeq-endif塊裡面。進去後,建構系統如果發現make指令中[Targets]部分包含有config,或者有%config目标的話,它就将變量config-targets設定為1,這表明本次make需要處理配置目标。在此基礎之上,如果它還發現有其他目标的話,它就将mixed-targets變量設定為1。注意mixed-targets變量的确切含義,其确切含義是指配置目标和其他目标相混合,而不是僅僅指配置目标和建構目标相混合。換句話講,對于"make s3c2410_defconfig kernelversion"這樣的指令來說,配置目标s3c2410_defconfig和與.config檔案不相關的目标kernelversion相混合,此時變量mixed-targets也會被設定為1。

讨論完B2部分對三個變量的初始化,讓我們再回到主架構結構上面來。架構結構中接下來就是針對三變量不同的取值搭配進行處理了。

首先,如果mixed-targets取值為1,則表明是混合目标的情況,建構系統要處理架構中的C部分。我們取出其中代碼如下:

# ===========================================================================
# We're called with mixed targets (*config and build targets).
# Handle them one by one.
 
%:: FORCE
        $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@      

從代碼中可以看出,這裡使用了一個雙冒号的模式比對規則。百分号代表任何目标都使用這個規則,其中$(srctree)為核心代碼樹所在目錄,KBUILD_SRC定義為空。是以如果make指令為:make s3c2410_defconfig all,那麼建構系統就會分别執行下面兩條指令:

make -C $(srctree) KBUILD_SRC= s3c2410_defconfig
  make -C $(srctree) KBUILD_SRC= all      

這其實和簡單的用手動的輸入兩條連續指令(make s3c2410_defconfig 和 make all)是一樣效果的。

回到主架構,如果make指令的[Targets]部分不是混合目标,而是單個目标。那麼建構系統會先用ifeq ($(config-targets),1)判斷是否是配置目标,如果是,則處理架構中的D部分,我們也抽出D部分的代碼如下:

# ===========================================================================
# *config targets only - make sure prerequisites are updated, and descend
# in scripts/kconfig to make the *config target
 
# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed.
# KBUILD_DEFCONFIG may point out an alternative default configuration
# used for 'make defconfig'
include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG
 
config: scripts_basic outputmakefile FORCE
        $(Q)mkdir -p include/linux include/config
        $(Q)$(MAKE) $(build)=scripts/kconfig $@
 
%config: scripts_basic outputmakefile FORCE
        $(Q)mkdir -p include/linux include/config
        $(Q)$(MAKE) $(build)=scripts/kconfig $@      

觀察上面D部分的代碼,無論配置目标是config,還是%config形式的,都依賴于 script_basic 和 outputmakefile兩個目标。我們可以找到這兩個依賴是定義在架構B1部分中的,抽出代碼如下:

# ===========================================================================
# Rules shared between *config targets and build targets
 
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
        $(Q)$(MAKE) $(build)=scripts/basic
 
# To avoid any implicit rule to kick in, define an empty command.
scripts/basic/%: scripts_basic ;
 
PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
        $(Q)ln -fsn $(srctree) source
        $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
            $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif      

正如上面代碼中的注釋所說的一樣,這兩個目标對應的規則其實是Kconfig和Kbuild都要用到的。scripts_basic規則的指令 $(Q)$(MAKE) $(build)=scripts/basic 做的工作就是:make -f scripts/Makefile.build obj=scripts/basic ,為什麼?回過頭去看看我們前面說的建構系統内各makefile的關聯就知道了。

make -f scripts/Makefile.build obj=scripts/basic 指令由于沒有指定目标,是以會在 script/Makefile.build 中處理預設目标__build,如下:

__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
         $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
         $(subdir-ym) $(always)
        @:      

同時,别忘記在scripts/Makefile.build中會包含進 scripts/basic 目錄下的 Kbuild/Makefile,是以該make指令的實際效果是去編譯出 scripts/basic 目錄下的三個 host program,也就是 fixdep docproc和hash。什麼是host program?一般認為是和核心無關,但是要在編譯過程中使用的工具程式。關于這些程式的編譯,請參見 scripts/Makefile.host 檔案,以及Documentation/kbuild/makefile.txt 檔案中關于 host program的這一節。請你留意這裡的變量$(always)。

對于目标outputmakefile,其實隻在 make 指令帶有O 變量時才有用,因為這個時候變量 KBUILD_SRC 不會為空。這個時候中間檔案不會存在核心源碼樹目錄中,而是存在另外的一個目錄。這個沒什麼困難的,是以我們這裡不再做過多說明。

好,回到我們對配置目标的處理上面來。在處理完兩個依賴後,建構系統将會建立兩個目錄: include/linux 和 include/config。接着執行 $(Q)$(MAKE) $(build)=scripts/kconfig $@ 。如果我們的make 指令是 "make s3c2410_defconfig" 的話,這個時候執行的就是:

make -f scripts/Makefile.build obj=scripts/kconfig s3c2410_defconfig

又牽涉到檔案 scripts/Makefile.build 了,我們先搜尋一下這個檔案裡面有沒有 s3c2410_defconfig 或 類似于 %config 之類的目标。沒有,怎麼辦,該不會是弄錯了吧?呵呵,你忘記了麼?檔案 scripts/Makefile.build 會包含obj變量所指代目錄内的 Makefile的,在這裡就是 script/kconfig/Makefile。打開這個檔案,果然能找到相關的規則:

​​​​

在這裡,s3c2410_defconfig 需要依賴于同目錄下的conf程式。這其實就是Linux核心進行Kconfig操作的主程式之一了,類似的還有mconf,qconf和gconf等。他們其實都是host program。關于它們是如何被編譯出來的,還請參見 scripts/kconfig/Makefile 檔案,主要是借助于bison,flex和gperf三個工具來生成c源程式檔案,之後再編譯出來的。由于這部分和我們Linux核心的建構主題關系不大,是以我在這裡不再贅述。

由于變量 KBUILD_KCONFIG 在arm架構Makefile中沒有被定義,是以 Kconfig 被定義成 arch/arm/kconfig,是以這個目标的規則就簡化成:

$(obj)/conf -D arch/arm/configs/s3c2410_defconfig arch/arm/Kconfig

這個指令就是讀取并解析以 arch/arm/Kconfig 為首的核心功能選項配置檔案,并将檔案 arch/arm/configs/s3c2410_defconfig 所設定的預設值配置設定給對應的所有選項,最終生成隐藏配置檔案 .config 。你可以打開看一下 .config 檔案的内容,都是這樣的格式:

#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.31
# Tue Nov 30 21:03:12 2010
#
CONFIG_ARM=y
CONFIG_HAVE_PWM=y
CONFIG_SYS_SUPPORTS_APM_EMULATION=y
CONFIG_GENERIC_GPIO=y
CONFIG_MMU=y
CONFIG_NO_IOPORT=y
CONFIG_GENERIC_HARDIRQS=y
CONFIG_STACKTRACE_SUPPORT=y
CONFIG_HAVE_LATENCYTOP_SUPPORT=y
CONFIG_LOCKDEP_SUPPORT=y
.....      

裡面貌似都是一些變量的定義。稍後我們會看到在核心開始真正編譯之前,建構系統會以 .config 檔案為藍本生成 include/config/auto.conf 檔案,這個檔案的格式和 .config類似,這個檔案會在頂層 以及 scripts/Makefile.build 檔案中被直接包含進來,是以這些變量其實就成了 GNU Make 的變量。而核心各子目錄中的 Kbuild/Makefile 就可以使用這些變量的定義,來決定是否将該目錄下對應的代碼功能直接編譯到核心裡面(這些變量取值為"y")、編譯成子產品(取值為"m")或者幹脆不進行編譯(取值為"空")。可以想見,如果選擇不編譯,那出來的Linux核心就不會有對應的功能。

前面之是以說是以 arch/arm/Kconfig 為首的,那就說明功能配置選項檔案可能有多個。的确如此,你如果打開檔案 arch/arm/Kconfig 就會看到它通過 source 來包含其他的選項配置檔案:

​​​​

不光是 arm 架構這樣,其他所有的架構也都這樣。在配置的時候,配置工具首先會解析架構平台目錄下的 Kconfig,這就是所謂和平台相關的主Kconfig。主Kconfig檔案會包含其他目錄的Kcofnig檔案,而其他目錄的Kconfig又會包含其他各子目錄的 Kconfig。如此形成一個樹型結構。

另外需要說一下的,如果你的make 指令是 "make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig",那用到的配置工具将會是 mconf,它同樣會讀取那些配置選項檔案,隻不過不同的是,它會show除一個菜單界面,并将這些可設定的選項一一呈現出來,如此我們就可以手動進行設定,而不是讓mconf去讀取 arch/arm/configs/s3c2410_defconfig 之類的檔案進行預設設定了。關于gconf等其他配置工具以及Kconfig檔案中具體的選項配置文法,我這裡就不再詳細叙述了,你可以參考目錄 Documentation/kbuild/ 下的檔案 kconfig.txt以及 kconfig-language.txt 。關于配置目标,除了這裡的 menuconfig 和 s3c2410_defconfig,我們後面還會說到另外一個很重要的配置目标: silentoldconfig 。

繼續閱讀