天天看點

Linux核心建構系統之四

回到我們的主架構上面來,讨論完配置目标的處理後,就輪到架構中 "ifeq ($(config-targets),1)-endif" 塊的 else 部分了。這部分是為了處理那些建構目标以及和.config無關的目标,其對這些目标處理的代碼都位于架構中的E部分中。在E部分之前,有一個不小的 "ifeq ($(dot-config),1)-endif" 塊,我們暫先不去理會,且看這個 ifeq-endif 塊之前有一小段注釋:

# ===========================================================================
# Build targets only - this includes vmlinux, arch specific targets, clean
# targets and others. In general all targets except *config targets.      

這段注釋字面上的意思是說 "ifeq ($(config-targets),1)-endif" 塊的 else 部分處理的都是 Build targets,也就是除了配置目标之外的其他目标。注意他這裡對 Build targets 分類方法,其實和我們之前的分類方法是有差異的,他這裡所謂的 Build targets ,除了包括我們之前分類中所說的建構目标外,還包括之前我們說的和 .config 檔案無關的那些目标。這其實是對同個東西的兩種不同分類罷了,不影響我們的分析。其實不管哪種分類,都改變不了在本 else 部分既處理真正的建構目标,又處理那些和 .config 檔案無關目标的事實。

好,鑒于我們已有這樣的事實認同。那接下來了解前面說的那個不小的 "ifeq ($(dot-config),1)-endif" 塊就比較容易了。很顯然這個時候如果變量 dot-config 等于 1,那說明針對的是那些真正的建構目标,因為他們需要檔案 .config 來完成真正的建構。而如果這個變量不為1,那麼針對的就是那些和 .config 完全無關的目标了。

當 dot-config 等于 1 時,建構系統首先會嘗試性的包含 include/config/auto.conf 檔案。為什麼說是嘗試性的?這是 GNU make 做 "-include" 的特性。其意思和是否存在所包含的檔案以及是否能根據所存在的規則去重新建立所包含檔案有關系。

GNU Make 是這樣一個大緻的讀取Makefile的流程:首先它讀入主Makefile,在讀的過程中,如果碰到 "include" 或 "-include",它就會包含對應的檔案。如果對應的檔案不存在,則暫時跳過做包含的地方,繼續讀入。待所有makefie都讀完後。GNU Make會考慮将每個makefile作為目标,在全局範圍内查找是否有能生成這些目标的規則,如果發現有一個makefile可以被一條規則生成,那麼GNU Make就會先生成這個makefile。生成後,GNU Make又會從零開始讀入主Makefile以及所有被包含的makefile,然後再檢查是否有makefile可以被remade….這樣一次又一次,直到所有的makefile都不需要再次生成了,它才處理依賴規則鍊。它之這樣做,是為了保證所有 makefile 都是 update-to-date 的。

那如果你的子makefile是被 "include" 所包含的,但是這個makefile本身不存在,且無法用一條規則去Remake出來,那麼 GNU Make就會報錯并退出。相反,如果你用的是 "-include",那麼 GNU Make就什麼都不做,就好象什麼也沒發生過那樣繼續處理後面的事情。是以,我們說這裡是嘗試性的,通俗點就是”有則包含,沒有也罷了“:)。

接下來回到主架構,假如你的 make 指令是 "make ARCH=arm CROSS_COMPILE=arm-linux- zImage",那麼dot-config 等于 1,并且變量 KBUILD_EXTMOD 會等于空。建構系統又會先嘗試性的包含檔案 include/config/auto.conf.cmd,然後繼續處理主架構中的 G1部分。我們先看看G1部分的代碼:

# To avoid any implicit rule to kick in, define an empty command
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;
 
# 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/auto.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
        $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig      

:),你看到這裡也許會露出些許微笑,因為你還記得那個 "-include include/config/auto.conf"。沒錯,針對你的make指令,GNU Make在處理任何目标之前,它一定會努力的為我們生成檔案 include/cofig/auto.conf ,用的就是這裡的這條對應規則。注意這裡的細節,GNU Make同樣也會努力重新用上面這條規則去生成 include/config/auto.conf.cmd 檔案,可是無奈上面這條規則既沒有依賴,又沒有指令,是以GNU Make 是怎麼也沒辦法生成 auto.conf.cmd。那麼 auto.conf.cmd 檔案又是從什麼地方生成呢?答案是在生成 include/config/auto.conf 的時候。稍後,我們會看到,它同樣在這個時候生成了另外一個檔案 include/linux/autoconf.h。

既然已經說到這幾個檔案了,那我就預先來回答一下上面提出來的問題。什麼問題?就是配置過程最後産生了用于記載配置結果的 .config ,那麼其中的配置結果由誰使用,又是如何使用的問題。我們說在整個Linux核心系統中,有兩方面的使用者需要關注所記載的配置結果。一個自然是核心建構系統,它需要根據配置結果産生具有指定功能的核心映像;另外一個就是大部分代碼為C語言代碼的Linux核心本身,它也需要使用者的配置結果,主要用來預處理C代碼。前者使用配置結果,并不是直接通過 .config 檔案來的,而是将其轉換成兩個檔案:include/config/auto.conf 和 include/config/auto.conf.cmd。後者也沒辦法直接通過 .config 檔案來使用配置結果,它需要将其轉換成C語言頭檔案的形式使用,在這裡就是檔案 include/linux/autoconf.h 。關于這三個檔案的内容,我們稍後叙述。

還是回到上面處理 auto.conf 的規則中來,變量 KCONFIG_CONFIG 指代的就是配置檔案 .config 。目标 auto.conf 依賴于 .config 和 auto.conf.cmd 。當GNU Make使用這條規則來remake auto.conf的時候,這兩個檔案即使不存在也沒有關系。因為這樣的話,GNU Make 會因為有上面這條沒有依賴也沒有指令的規則而認為這兩個依賴是最新的,是以此時auto.conf規則的指令總是會被得到執行。

這是有意思的地方,因為這是不是意味着我可以在一個剛剛解壓出來的Linux核心目錄中直接用指令 "make ARCH=arm CROSS_COMPILE=arm-linux- zImage"來完成建構呢?咱們試一下:

​​​​

從結果看前面都沒問題,直到用conf進行最後一步的配置時出現了錯誤。至于錯誤原因呢,是因為找不到檔案 .config 。我們可以找出出現錯誤的代碼,就在檔案scripts/kconfig/conf.c 的main函數中,如下所示的代碼片段:

if (sync_kconfig) {
                name = conf_get_configname();
                if (stat(name, &tmpstat)) {
                        fprintf(stderr, _("***\n"
                                "*** You have not yet configured your kernel!\n"
                                "*** (missing kernel config file \"%s\")\n"
                                "***\n"
                                "*** Please run some configurator (e.g. \"make oldconfig\" or\n"
                                "*** \"make menuconfig\" or \"make xconfig\").\n"
                                "***\n"), name);
                        exit(1);
                }
        }      

就像前面講的,這個時候GNU Make會執行auto.conf規則的對應指令"$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig"。而針對這個指令,配置程式 conf 最後會在main函數上面這個代碼片段的前面,将上面這個代碼片段中的 sync_kconfig 設定為1。接下來取得配置檔案的名稱放在 name 變量裡面,再使用 stat 函數查詢這個檔案的狀态。結果該函數傳回非0值,使得GNU Make認為配置檔案.config不存在而報錯。

回到處理 auto.conf 的那條規則上來。考慮一下如果這個時候 auto.conf 檔案和 .config 檔案都存在,但是 .config 比較新的話會怎麼樣呢?想想,這會是一個怎麼樣的 Scenario呢?如果你剛剛編譯過核心并已生成映像檔案,這個時候你從你朋友那裡取來一個 .config 檔案,并且将其放到了你的核心源碼樹下面,這個時候就産生了這種情況。這個時候你需要根據你的核心的版本來做處理。如果你的版本和你的朋友一樣,那沒問題,你大可以直接 "make ... zImage"。但是,如果你們的版本不一樣,你最好做下 "make ... oldconfig"。這個指令的作用是針對原來版本核心的配置檔案設定來配置新版本的核心。新版核心中沒變的配置選項可以設定成原來的值,但是新添加的配置項就需要你自己手動設定了。

再次回到處理 auto.conf 的那條規則上來,我們看到它的指令 "$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig",這個指令最終會導緻GNU Make執行檔案 scripts/kconfig/Makefile 中針對目标 silentoldconfig 的指令:

$(obj)/conf -s arch/arm/Kconfig

conf配置程式在前面已經有所提及,其對應的代碼都在目錄 scripts/kconfig/ 中。conf 的主函數main即定義在 conf.c 檔案中。其實,目标silentoldconfig 和 目标oldconfig類似,隻不過它多了生成auto.conf、auto.conf.cmd以及autoconf.h等三個檔案的任務。這是怎麼做到的?答案就在conf.c檔案中 main 函數最後的一段代碼:

int main(int ac, char **av)
{
        int opt;
        const char *name;
        struct stat tmpstat;
 
        .....
        .....
        
        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 kernel configuration.\n\n"));
                        exit(1);
                }
                if (conf_write_autoconf()) {
                        fprintf(stderr, _("\n*** Error during update of the kernel configuration.\n\n"));
                        return 1;
                }
        } else {
                if (conf_write(NULL)) {
                        fprintf(stderr, _("\n*** Error during writing of the kernel configuration.\n\n"));
                        exit(1);
                }
        }
        return 0;
}      

前面我們已經介紹過,當用 conf 處理 silentoldconfig 時,變量sync_kconfig會被設定為1。實際上,也隻有處理此目标時,它才會被設定成1,其他的目标都不會。對于oldconfig、menuconfig等目标來說,conf程式最後會直接調用函數 conf_write 将配置結果寫到配置檔案 .config 中去。該函數的定義在同目錄的另外一個檔案 confdata.c 中,這裡我們不再細究下去了,你可以自己探究。而對于 silentoldconfig 目标來說,conf 程式除了調用 conf_write 來寫 .config 檔案外,它還會調用 conf_write_autoconf 函數來完成 auto.conf、auto.conf.cmd 和 autoconf.h 三個檔案的生成。我們且來看看同樣定義在 confdata.c 檔案中的 conf_write_autoconf 函數:

​​​​

該函數一開始就寫 include/config/auto.conf.cmd 檔案,然後将對應的内容寫入到兩個臨時檔案 .tmpconfig 和 .tmpconfig.h 中,并在最後将這兩個檔案分别重新命名為include/config/auto.conf 和 include/linux/autoconf.h。注意上面代碼中對 conf_split_config 函數的調用,其目的是在目錄 include/config 中産生一系列的頭檔案,至于這些頭檔案如何産生的、以及它們的作用,我們這裡先留下一個伏筆,回頭再來探究。

知道了這幾個檔案是如何産生的。我們再來注意一下它們的内容。檔案 auto.conf.cmd 裡面記錄的是 auto.conf 目标的相關依賴,而檔案 auto.conf 和檔案 autoconf.h 的内容和 .config 檔案的内容直接相關。舉例來說,如果你找到有個定義在 .config 檔案裡的變量形式:CONFIG_MMU=y 表示要将虛拟記憶體管理的功能編譯進核心,那麼在 auto.conf 裡面 也會有完全相同的定義形式:CONFIG_MMU=y。而在檔案 autoconf.h 檔案中,則會有一個這樣形式的宏定義:#define CONFIG_MMU 1。假如.config中的形式是 CONFIG_IKCONFIG=m,那麼在 auto.conf 中的形式也會是:CONFIG_IKCONFIG=m ,而在檔案 autoconf.h 中的定義形式則變成:#define CONFIG_IKCONFIG_MODULE 1 。

前面說的是當 KBUILD_EXTMOD 為空的時候,那麼當這個變量取值不為空(也就是我們嘗試在用 "make ... -M=..." 之類的指令來編譯外部子產品)時,GNU Make 隻是簡單的處理上面架構中的G2部分。編譯外部子產品時并不需要關心 auto.conf/autoconf.h 是不是最新的。它隻是檢查他們是否存在,如果不存在,它就報錯并退出。

之前我們說過建構目标以及和.config無關的目标是混在一塊處理的。那當 dot-config 等于 1 時,處理的是建構目标,而在 dot-config 等于 0 時,建構系統處理的是那些和檔案 .config沒有關系的目标。注意看我們的架構,處理和.config無關目标的時候,建構系統隻是簡單的針對 auto.conf 目标定義了一個既沒有依賴又沒有指令的規則:

# Dummy target needed, because used as prerequisite
include/config/auto.conf: ;      

這樣做的意思是因為 auto.conf 在後面會為多個其他目标所依賴。我們在這裡隻是登記一下說:“嘿,兄弟我(auto.conf)已經是最新的了,你們别再管我了,隻管繼續做你們自己的事情就好。”

繼續閱讀