u-boot-2016 make配置過程分析

概述

本文基於u-boot樹莓派3代配置過程進行分析,環境如下:
編譯環境:Ubuntu 14.04 LTS
編譯工具:arm-Linux-gnueabi-gcc
代碼版本:u-boot v2016.09
配置文件:rpi_3_32b_defconfig

u-boot自v2014.10版本開始引入KBuild系統,Makefile的管理和組織跟以前版本的代碼有了很大的不同,其Makefile更加複雜。整個Makefile中,嵌套了很多其它不同用途的Makefile,各種目標和依賴也很多,make分析很容易陷進去,讓人摸不着頭腦。
本文涉及的配置命令:

make rpi_3_32b_defconfig
  • 1
  • 1
save_snippets_01.png
  • 1

實例執行配置命令

u-boot的編譯跟kernel編譯一樣,分兩步執行:
- 第一步:配置,執行make xxx_defconfig進行各項配置,生成.config文件
- 第二部:編譯,執行make進行編譯,生成可執行的二進制文件u-boot.bin或u-boot.elf

先從簡單的make defconfig配置過程着手吧。
命令行輸入:

make rpi_3_32b_defconfig V=1
  • 1
  • 1
save_snippets_01.png
  • 1

編譯輸出如下:
make rpi_3_32b_defconfig V=1的輸出

配置命令參數說明:
- rpi_3_32b_defconfig 是樹莓派3代32位編譯的配置文件
- V=1 指示編譯顯示詳細的輸出。默認V=0,編譯僅顯示必要的簡略信息

從輸出的log看,make rpi_3_32b_defconfig的執行主要分爲3個部分,見圖上的標示:
- 1. 執行make -f ./scripts/Makefile.build obj=scripts/basic,編譯生成scripts/basic/fixdep工具
- 2. 執行make -f ./scripts/Makefile.build obj=scripts/kconfig rpi_3_32b_defconfig編譯生成scripts/kconfig/conf工具
- 3. 執行scripts/kconfig/conf --defconfig=arch/../configs/rpi_3_32b_defconfig Kconfig生成最終的.config配置文件

跟原始的代碼相比,執行make rpi_3_32b_defconfig後文件夾內容的變化如下:
配置前後文件夾內容變化

被後續編譯用到的文件是.config。

詳細配置流程分析

言歸正傳,整個配置流程的目的就是爲了生成.config文件,下面詳細分析.config文件是如何一步一步生成的。

Makefile的核心是依賴和命令。對於每個目標,首先會檢查依賴,如果依賴存在,則執行命令更新目標;如果依賴不存在,則會以依賴爲目標,先生成依賴,待依賴生成後,再執行命令生成目標。

1. 頂層make defconfig規則

執行make xxx_defconfig命令時,u-boot根目錄下的Makefile中有唯一的規則匹配目標:

  1. %config: scripts_basic outputmakefile FORCE
  2. $(</span>Q)<span class="hljs-variable">$(MAKE) $(</span>build)=scripts/kconfig <span class="hljs-variable">$@
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
save_snippets_01.png
  • 1
  • 2
  • 3

對於目標,rpi_3_32b_defconfig,展開則有:

  1. rpi_3_32b_defconfig: scripts_basic outputmakefile FORCE
  2. $(</span><span class="hljs-constant">Q</span>)<span class="hljs-variable">$(MAKE) $(</span>build)=scripts/kconfig rpi_3_32b_defconfig</div></div></li></ol></code><ul class="pre-numbering" style="opacity: 0;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li></ul><div class="hljs-button" data-title="複製"></div></pre><ul class="pre-numbering" style="opacity:0;"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li></ul><div class="save_code tracking-ad" style="display:none;"><a target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets_01.png" alt="save_snippets_01.png"></a></div><ul class="pre-numbering"><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li></ul><p>其中<code>$(build)在kbuild.include中定義:
    build := -f $(srctree)/scripts/Makefile.build obj
    • 1
    • 1
    save_snippets_01.png
    • 1

    i. 依賴scripts_basic

    依賴scripts_basic:

    1. # Basic helpers built in scripts/
    2. PHONY += scripts_basic
    3. scripts_basic:
    4. $(</span><span class="hljs-constant">Q</span>)<span class="hljs-variable">$(MAKE) $(</span>build)=scripts/basic</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-variable">$(Q)rm -f .tmp_quiet_recordmcount
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    save_snippets.png
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可見scripts_basic沒有進一步的依賴,展開後規則如下:

    1. scripts_basic:
    2. $(Q) make -f ./scripts/Makefile<span class="hljs-preprocessor">.build</span> obj=scripts/basic</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> $(Q) rm -f .tmp_quiet_recordmcount
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    save_snippets.png
    • 1
    • 2
    • 3

    ii. 依賴outputmakefile

    依賴outputmakefile:

    1. PHONY += outputmakefile
    2. # outputmakefile generates a Makefile in the output directory, if using a
    3. # separate output directory. This allows convenient use of make in the
    4. # output directory.
    5. outputmakefile:
    6. ifneq ($(</span>KBUILD_SRC),)</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="7"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-variable">$(Q)ln -fsn $(</span>srctree) <span class="hljs-keyword">source</span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="8"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-variable">$(Q)$(</span>CONFIG_SHELL) <span class="hljs-variable">$(srctree)/scripts/mkmakefile \
    7. $(</span>srctree) <span class="hljs-variable">$(objtree) $(</span>VERSION) <span class="hljs-variable">$(PATCHLEVEL)
    8. endif
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    save_snippets.png
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    outputmakefile也沒有進一步的依賴。
    如果執行如下命令:

    make rpi_3_32b_defconfig O=out
    • 1
    • 1
    save_snippets.png
    • 1

    那麼所有生成的目標都將放到out目錄,此時會通過outputmakefile導出一個makefile到out目錄進行編譯。

    由於在當前目錄下編譯,$(KBUILD_SRC)爲空,不需要導出makefile文件,outputmakefile爲空目標。

    iii. 依賴FORCE

    依賴FORCE:

    1. PHONY += FORCE
    2. FORCE:
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    save_snippets_01.png
    • 1
    • 2
    • 3

    FORCE被定義爲一個空目標。
    如果一個目標添加FORCE依賴,每次編譯都會去先去執行FORCE(實際上什麼都不做),然後運行命令更新目標,這樣就能確保目標每次都會被更新。在這裏也就保證目標rpi_3_32b_defconfig的命令:

    $(</span><span class="hljs-constant">Q</span>)<span class="hljs-variable">$(MAKE) $(build)=scripts/kconfig rpi_3_32b_defconfig
    • 1
    • 1
    save_snippets.png
    • 1

    總是能夠被執行。

    以上是rpi_3_32b_defconfig的所有依賴,分析完依賴後再分析命令。

    2. 頂層make defconfig的命令

    i. 依賴scripts_basic的命令

    目標rpi_3_32b_defconfig的三個依賴scripts_basicoutputmakefileFORCE中,只有scripts_basic需要執行命令,如下

    1. scripts_basic:
    2. $(Q) make -f ./scripts/Makefile<span class="hljs-preprocessor">.build</span> obj=scripts/basic</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> $(Q) rm -f .tmp_quiet_recordmcount
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    save_snippets_01.png
    • 1
    • 2
    • 3

    然後Make命令會轉到文件scripts/Makefile.build去執行。

    第一次調用scripts/Makefile.build進行編譯
    文件script/Makefile.build的開頭會根據傳入的obj=scripts/basic參數設置src=scripts/basic:

    1. prefix := tpl
    2. src := $(</span>patsubst <span class="hljs-variable">$(prefix)/%,%,$(</span>obj))</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">ifeq (<span class="hljs-variable">$(obj),$(</span>src))</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">prefix := spl</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">src := <span class="hljs-variable">$(patsubst $(</span>prefix)/<span class="hljs-variable">%,</span><span class="hljs-variable">%,</span><span class="hljs-variable">$(obj))
    3. ifeq ($(</span>obj),<span class="hljs-variable">$(src))
    4. prefix := .
    5. endif
    6. endif
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    save_snippets.png
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然後搜尋$(srctree)/$(src)子目錄下的makefile,幷包含進來:

    1. # The filename Kbuild has precedence over Makefile
    2. kbuild-dir := $(</span><span class="hljs-keyword"><span class="hljs-keyword">if</span></span> <span class="hljs-variable">$(filter /%,$(</span>src)),<span class="hljs-variable">$(src),$(</span>srctree)/<span class="hljs-variable">$(src))
    3. kbuild-file := $(</span><span class="hljs-keyword"><span class="hljs-keyword">if</span></span> <span class="hljs-variable">$(wildcard $(</span>kbuild-dir)/Kbuild),<span class="hljs-variable">$(kbuild-dir)/Kbuild,$(</span>kbuild-dir)/Makefile)</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-keyword">include</span> <span class="hljs-variable">$(kbuild-file)
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4
    save_snippets.png
    • 1
    • 2
    • 3
    • 4

    這裏展開替換後相當於:

    include ./scripts/basic/Makefile
    • 1
    • 1
    save_snippets.png
    • 1

    文件scripts/basic/Makefile中定義了編譯在主機上執行的工具fixdep:

    1. hostprogs-y := fixdep
    2. always := $(</span>hostprogs-y)</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="3"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> </div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-comment"><span class="hljs-comment"># fixdep is needed to compile other host programs</span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-variable">$(addprefix $(</span>obj)/,<span class="hljs-variable">$(filter-out fixdep,$(</span>always)))<span class="hljs-symbol">:</span> <span class="hljs-variable">$(obj)/fixdep
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5
    save_snippets.png
    • 1
    • 2
    • 3
    • 4
    • 5

    工具fixdep用於更新每一個生成目標的依賴文件*.cmd

    上面定義的這個$(always)scripts/Makefile.build裏會被添加到targets中:

    targets += $(extra-y)</span> <span class="hljs-variable">$(MAKECMDGOALS) $(always)
    • 1
    • 1
    save_snippets.png
    • 1

    關於如何編譯主機上可執行的程序,會在另外的文章中分析。

    簡而言之,scripts_basic規則

    1. scripts_basic:
    2. $(Q) make -f ./scripts/Makefile.build obj=scripts/basic
    • 1
    • 2
    • 1
    • 2
    save_snippets.png
    • 1
    • 2

    的最終結果就是編譯scripts/basic/fixdep.c生成主機上的可執行文件fixdep。至於爲什麼要編譯fixdep和如何使用fixdep,會在另外的文章中分析。

    ii. 頂層rpi_3_32b_defconfig的命令

    完成對依賴scripts_basic的更新後,接下來就是執行頂層目標的命令完成對rpi_3_32b_defconfig的更新,展開後的規則如下:

    1. rpi_3_32b_defconfig: scripts_basic outputmakefile FORCE
    2. make -f ./scripts/Makefile.build obj= scripts/kconfig rpi_3_32b_defconfig
    • 1
    • 2
    • 1
    • 2
    save_snippets.png
    • 1
    • 2

    其中$(build)kbuild.include中定義:

    1. ###
    2. # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
    3. # Usage:
    4. # $(Q)$(MAKE) $(build)=dir</span></span></div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="5"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line">build <span class="hljs-symbol">:</span>= -f $(srctree)/scripts/Makefile.build obj
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5
    save_snippets.png
    • 1
    • 2
    • 3
    • 4
    • 5

    這個make命令會第二次轉到scripts/Makefile.build去執行。

    第二次調用scripts/Makefile.build進行編譯
    文件script/Makefile.build的開頭會根據傳入的obj=scripts/kconfig參數設置src=scripts/kconfig。然後搜尋$(srctree)/$(src)子目錄下的makefile,由於src=scripts/kconfig參數不同於第一次調用的參數(src=scripts/basic),此處包含的makefile也不同於第一次的makefile了:

    1. # The filename Kbuild has precedence over Makefile
    2. kbuild-dir := $(</span><span class="hljs-keyword"><span class="hljs-keyword">if</span></span> <span class="hljs-variable">$(filter /%,$(</span>src)),<span class="hljs-variable">$(src),$(</span>srctree)/<span class="hljs-variable">$(src))
    3. kbuild-file := $(</span><span class="hljs-keyword"><span class="hljs-keyword">if</span></span> <span class="hljs-variable">$(wildcard $(</span>kbuild-dir)/Kbuild),<span class="hljs-variable">$(kbuild-dir)/Kbuild,$(</span>kbuild-dir)/Makefile)</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="4"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"><span class="hljs-keyword">include</span> <span class="hljs-variable">$(kbuild-file)
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4
    save_snippets.png
    • 1
    • 2
    • 3
    • 4

    這裏替換展開後相當於:

    include ./scripts/kconfig/Makefile
    • 1
    • 1
    save_snippets.png
    • 1

    文件scripts/kconfig/Makefile中定義了所有匹配%config的目標:

    1. PHONY += xconfig gconfig menuconfig config silentoldconfig update-po-config \
    2. localmodconfig localyesconfig
    3. PHONY += oldnoconfig savedefconfig defconfig
    4. PHONY += kvmconfig
    5. PHONY += tinyconfig
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    save_snippets.png
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    對於這裏傳入的rpi_3_32b_defconfig,匹配的目標是:

    1. %_defconfig: $(</span>obj)/conf</div></div></li><li><div class="hljs-ln-numbers"><div class="hljs-ln-line hljs-ln-n" data-line-number="2"></div></div><div class="hljs-ln-code"><div class="hljs-ln-line"> <span class="hljs-variable">$(Q)$&lt;</span> <span class="hljs-variable">$(silent) --defconfig=arch/$(</span>SRCARCH)/configs/<span class="hljs-variable">$@ $(Kconfig)
    • 1
    • 2
    • 1
    • 2
    save_snippets.png
    • 1
    • 2

    展開爲:

    1. rpi_3_32b_defconfig: scripts/kconfig/conf
    2. $(Q)scripts/kconfig/conf --defconfig=arch/../configs/rpi_3_32b_defconfig Kconfig
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    save_snippets.png
    • 1
    • 2
    • 3

    此處目標rpi_3_32b_defconfig依賴於scripts/kconfig/conf,接下來檢查並生成依賴。

    hostprogs-y := conf nconf mconf kxgettext qconf gconf
    • 1
    • 1
    save_snippets.png
    • 1

    hostprogs-y指出conf被定義爲主機上執行的程序,其依賴於另外兩個文件:

    conf-objs   := conf.o  zconf.tab.o
    • 1
    • 1
    save_snippets.png
    • 1

    通過編譯conf.czconf.tab.c生成conf-objs,並鏈接爲scripts/kconfig/conf

    生成依賴後就是執行目標的命令了:

    $(Q)scripts/kconfig/conf  --defconfig=arch/../configs/rpi_3_32b_defconfig Kconfig
    • 1
    • 1
    save_snippets.png
    • 1

    工具scripts/kconfig/conf的操作會在單獨的文章中分析,此處只做簡要的說明:

    conf工具從根目錄下開始樹狀讀取默認的Kconfig文件,分析其配置並保存在內存中。分析完默認的Kconfig後再讀取指定文件(即arch/../configs/rpi_3_32b_defconfig)更新得到最終的符號表,並輸出到.config文件中。

    至此完成了make rpi_3_32b_defconfig執行配置涉及的所有依賴和命令的分析。

    make defconfig配置流程簡圖

    整個配置流程闡述得比較囉嗦,可以用一個簡單的依賴圖表示,如下:
    make defconfig配置中的依賴和命令
    (可以將圖片拖到瀏覽器的其他窗口看大圖)

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