概述
本文基於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
- 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
- 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中有唯一的規則匹配目標:
- %config: scripts_basic outputmakefile FORCE
- $(</span>Q)<span class="hljs-variable">$(MAKE) $(</span>build)=scripts/kconfig <span class="hljs-variable">$@
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
對於目標,rpi_3_32b_defconfig
,展開則有:
- rpi_3_32b_defconfig: scripts_basic outputmakefile FORCE
- $(</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
- 1
i. 依賴scripts_basic
依賴scripts_basic:
- # Basic helpers built in scripts/
- PHONY += scripts_basic
- scripts_basic:
- $(</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
- 1
- 2
- 3
- 4
- 5
- 6
可見scripts_basic沒有進一步的依賴,展開後規則如下:
- scripts_basic:
- $(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
- 1
- 2
- 3
ii. 依賴outputmakefile
依賴outputmakefile:
- 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 ($(</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 \
- $(</span>srctree) <span class="hljs-variable">$(objtree) $(</span>VERSION) <span class="hljs-variable">$(PATCHLEVEL)
- endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
outputmakefile也沒有進一步的依賴。
如果執行如下命令:
make rpi_3_32b_defconfig O=out
- 1
- 1
- 1
那麼所有生成的目標都將放到out目錄,此時會通過outputmakefile導出一個makefile到out目錄進行編譯。
由於在當前目錄下編譯,$(KBUILD_SRC)
爲空,不需要導出makefile文件,outputmakefile爲空目標。
iii. 依賴FORCE
依賴FORCE:
- PHONY += FORCE
- FORCE:
- 1
- 2
- 3
- 1
- 2
- 3
- 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
- 1
總是能夠被執行。
以上是rpi_3_32b_defconfig
的所有依賴,分析完依賴後再分析命令。
2. 頂層make defconfig的命令
i. 依賴scripts_basic的命令
目標rpi_3_32b_defconfig
的三個依賴scripts_basic
,outputmakefile
和FORCE
中,只有scripts_basic
需要執行命令,如下
- scripts_basic:
- $(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
- 1
- 2
- 3
然後Make命令會轉到文件scripts/Makefile.build
去執行。
第一次調用scripts/Makefile.build進行編譯
文件script/Makefile.build
的開頭會根據傳入的obj=scripts/basic
參數設置src=scripts/basic
:
- prefix := tpl
- 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))
- ifeq ($(</span>obj),<span class="hljs-variable">$(src))
- prefix := .
- endif
- endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
然後搜尋$(srctree)/$(src)
子目錄下的makefile,幷包含進來:
- # The filename Kbuild has precedence over Makefile
- 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))
- 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
- 1
- 2
- 3
- 4
這裏展開替換後相當於:
include ./scripts/basic/Makefile
- 1
- 1
- 1
文件scripts/basic/Makefile
中定義了編譯在主機上執行的工具fixdep:
- hostprogs-y := fixdep
- 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
- 1
- 2
- 3
- 4
- 5
工具fixdep用於更新每一個生成目標的依賴文件*.cmd
。
上面定義的這個$(always)
在scripts/Makefile.build
裏會被添加到targets中:
targets += $(extra-y)</span> <span class="hljs-variable">$(MAKECMDGOALS) $(always)
- 1
- 1
- 1
關於如何編譯主機上可執行的程序,會在另外的文章中分析。
簡而言之,scripts_basic
規則
- scripts_basic:
- $(Q) make -f ./scripts/Makefile.build obj=scripts/basic
- 1
- 2
- 1
- 2
- 1
- 2
的最終結果就是編譯scripts/basic/fixdep.c
生成主機上的可執行文件fixdep。至於爲什麼要編譯fixdep和如何使用fixdep,會在另外的文章中分析。
ii. 頂層rpi_3_32b_defconfig的命令
完成對依賴scripts_basic
的更新後,接下來就是執行頂層目標的命令完成對rpi_3_32b_defconfig
的更新,展開後的規則如下:
- rpi_3_32b_defconfig: scripts_basic outputmakefile FORCE
- make -f ./scripts/Makefile.build obj= scripts/kconfig rpi_3_32b_defconfig
- 1
- 2
- 1
- 2
- 1
- 2
其中$(build)
在kbuild.include
中定義:
- ###
- # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
- # Usage:
- # $(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
- 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了:
- # The filename Kbuild has precedence over Makefile
- 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))
- 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
- 1
- 2
- 3
- 4
這裏替換展開後相當於:
include ./scripts/kconfig/Makefile
- 1
- 1
- 1
文件scripts/kconfig/Makefile
中定義了所有匹配%config
的目標:
- PHONY += xconfig gconfig menuconfig config silentoldconfig update-po-config \
- localmodconfig localyesconfig
-
- PHONY += oldnoconfig savedefconfig defconfig
-
- PHONY += kvmconfig
-
- PHONY += tinyconfig
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
對於這裏傳入的rpi_3_32b_defconfig
,匹配的目標是:
- %_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)$<</span> <span class="hljs-variable">$(silent) --defconfig=arch/$(</span>SRCARCH)/configs/<span class="hljs-variable">$@ $(Kconfig)
- 1
- 2
- 1
- 2
- 1
- 2
展開爲:
- rpi_3_32b_defconfig: scripts/kconfig/conf
- $(Q)scripts/kconfig/conf --defconfig=arch/../configs/rpi_3_32b_defconfig Kconfig
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
此處目標rpi_3_32b_defconfig
依賴於scripts/kconfig/conf
,接下來檢查並生成依賴。
hostprogs-y := conf nconf mconf kxgettext qconf gconf
- 1
- 1
- 1
hostprogs-y
指出conf被定義爲主機上執行的程序,其依賴於另外兩個文件:
conf-objs := conf.o zconf.tab.o
- 1
- 1
- 1
通過編譯conf.c
和zconf.tab.c
生成conf-objs
,並鏈接爲scripts/kconfig/conf
。
生成依賴後就是執行目標的命令了:
$(Q)scripts/kconfig/conf --defconfig=arch/../configs/rpi_3_32b_defconfig Kconfig
- 1
- 1
- 1
工具scripts/kconfig/conf
的操作會在單獨的文章中分析,此處只做簡要的說明:
conf
工具從根目錄下開始樹狀讀取默認的Kconfig
文件,分析其配置並保存在內存中。分析完默認的Kconfig
後再讀取指定文件(即arch/../configs/rpi_3_32b_defconfig
)更新得到最終的符號表,並輸出到.config
文件中。
至此完成了make rpi_3_32b_defconfig
執行配置涉及的所有依賴和命令的分析。
make defconfig配置流程簡圖
整個配置流程闡述得比較囉嗦,可以用一個簡單的依賴圖表示,如下:
(可以將圖片拖到瀏覽器的其他窗口看大圖)