使用kbuild構建bzImage內核映像

這裏通過以下三個最經典的步驟來分析下,一個bzImage內核映像是如何配置編譯,並最終安裝使用的。當然在整個內核構建過程中,還支持許多的特性,相信這個經典過程明確之後,分析其他的情況就不會太難了,這裏以x86體系來做分析。分析版本2.6.34.1

(1) make menuconfig
研究代碼最好的方法就是手眼並用,看代碼的同時多多動手調試,我們先貼出了它實際運行的過程:
[root@www linux-2.6.34.1]# make menuconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/basic/docproc
  HOSTCC  scripts/basic/hash
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/kxgettext.o
  HOSTCC  scripts/kconfig/lxdialog/checklist.o
  HOSTCC  scripts/kconfig/lxdialog/inputbox.o
  HOSTCC  scripts/kconfig/lxdialog/menubox.o
  HOSTCC  scripts/kconfig/lxdialog/textbox.o
  HOSTCC  scripts/kconfig/lxdialog/util.o
  HOSTCC  scripts/kconfig/lxdialog/yesno.o
  HOSTCC  scripts/kconfig/mconf.o
  SHIPPED scripts/kconfig/zconf.tab.c
  SHIPPED scripts/kconfig/lex.zconf.c
  SHIPPED scripts/kconfig/zconf.hash.c
  HOSTCC  scripts/kconfig/zconf.tab.o
  HOSTLD  scripts/kconfig/mconf
scripts/kconfig/mconf arch/x86/Kconfig
當我們在內核頂層目錄執行make menuconfig(假設你知道menuconfig的作用,這裏就不多講了)時,就指定了當前make的對象是menuconfig,那麼我們到Makefile中去找到這個目標。注意的是,在衆多kbuild系統分析的博客中,都會交代都諸多的細節,我寫這個系列的目的主要是爲了理出一條清晰的思路,而不去糾纏太多細枝末節的條件判斷,當然在最後我會貼出一些比較好的,並且包含多細節的博客分享給大家。

由於我們的目標是menuconfig,在Makefile裏面屬於一種叫做config-targets的構建對象,在Makefile中會將“include $(srctree)/arch/$(SRCARCH)/Makefile”,即arch/x86/Makefile包含進來,include的動作是相當於把arch/x86/Makefile的內容直接複製到上述語句的所在的地方,成爲了當前的Makefile的一部分,自然了,裏面的變量也就不需要export出來了。
接下來有一塊內容:
%config: scripts_basic outputmakefile FORCE
     $(Q)mkdir -p include/linux include/config
     $(Q)$(MAKE) $(build)=scripts/kconfig $@
這裏%config是Makefile裏面的通配表示,匹配以config結尾的所以字符,自然我們的menuconfig也是它匹配的類型。它的依賴包括script_basic, outputmakefile,剩下的FORCE,它的作用一句話概括,只要依賴條件中含有FORCE,目標總會被構建。接下來它的構建指令需要重點關注的是第二個(第一條就是創建兩個目錄),關於變量$(@),這裏有詳細的介紹。
這句指令中涉及到的$(build)在script/kbuild.include中被定義,它真實的樣子是:
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
所以通過這個build的定義,上面的那句話可以翻譯爲:make -f script/Makefile.build obj=script/kconfig menuconfig
實際的執行過程可以到script下的Makefile.build看到:
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
這三句話就把script/kconfig/目錄下的Makefile包含了進來,在這個Makefile中,定義了menuconfig的構建過程:
menuconfig: $(obj)/mconf
     $< $(Kconfig)
我們的討論環境裏,$(Kconfig)指向的是arch/x86/Kconfig文件,這個文件裏有啥?很明瞭,$(obj)/mconf,即script/kconfig/目錄下生成的mconf程序會讀取arch/x86/Kconfig文件中的內容來生成我們熟悉的配置界面,配置結束之後,在頂層目錄下會生成一個名爲.config的隱藏文件,這是後續構建過程的重要依據和參考。

關於mconf的生成,前面貼的運行過程已經很清楚了,想了解跟多細節的同學,這裏推薦一篇文章:走一走makefile menuconfig流程

依賴條件script_basic和outputmakefile又有怎樣的前世今生呢?這裏粗略的分析一下。
outputmakefile在我們make時通過”O=“指定了make輸出目錄纔會執行有意義的動作,這裏不是我們討論的情況,先跳過。
script_basic我們可以在主Makefile中找到它:
scripts_basic:
     $(Q)$(MAKE) $(build)=scripts/basic
     $(Q)rm -f .tmp_quiet_recordmcount
主要的動作翻譯過來就是:make -f script/Makefile.build obj=script/kconfig,由於沒有指定構建對象(即$(build)=script/basic後面爲空),所以make的目標就是針對script/Makefile.build的默認目標,很顯然它就是“__build”。
這裏我跳到重點的片段:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
     $(subdir-ym) $(always)
     @:
這裏需要說明的一點就是,在當前的討論情景下只有always是含有實際對象的,其它的都爲空,這裏用到的的always是script/basic/Makefile中定義的,在開始時看到執行過程,則是通過Makefile.host產生了一些構建必要的工具,同時將這些工具生成指令寫到一個.cmd結尾的文件裏,如.docproc.cmd,.fixdep.cmd,.hash.cmd等。
關於@:,在這裏有詳細的說明。
這篇文章詳細的描述了menuconfig的構建過程,上面我所寫的這些,貌似重複和多餘,但是對我個人而言,關於menuconfig,腦海裏保存這些信息就足夠了,細節就像是手冊中的幫助文檔,需要的時候查閱就可以了。
(2) make
直接make,那麼就會執行makefile中的默認對象,這裏就是vmlinux。
這裏我們要提一點,在主Makefile中,include $(srctree)/arch/$(SRCARCH)/Makefile,也就是說將當前架構文件夾下的Makefile包含進來了。在該makfile中bzImage最終還是要以vmlinux爲依賴,所以vmlinux是重點。那麼vmlinux需要哪些依賴呢?我們列出來看:
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o)

vmlinux-dirs   := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
               $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
               $(net-y) $(net-m) $(libs-y) $(libs-m)))
vmlinux.o: $(modpost-init) $(vmlinux-main)
以上兩個是他們各自的依賴,而下面的這幾個則是賦值。
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
kallsyms.o := .tmp_kallsyms$(last_kallsyms).o #last_kallsyms是1,2,3等數值
注意這裏vmlinux-dirs,後面出現了它的依賴:
$(vmlinux-dirs): prepare scripts 
到這裏說句題外話,有很多前輩的工作,極大的縮短了我這樣剛剛開始研究內核的人的學習曲線,想寫點東西,前輩都寫的很清楚了,這裏就不想重複了。
這裏給出雲鬆大牛瘋狂內核中的關於內核映像構建相關鏈接,很前面也很詳細。這裏主要的內容算是自己的一種總結和理解。
整理一下思路:
1. 各個目錄下.o文件的編譯生成
$(vmlinux-dirs): prepare scripts
     $(Q)$(MAKE) $(build)=$@
該命令的執行啓動了.o文件的編譯,由於vmlinux-dirs實際上就是各個目錄的替身,在makefile.build中我們看到:
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
這裏的操作就是會依次包含進各個目錄下的Makefile,來編譯該目錄下的對象,編譯的實際過程則是:
$(obj)/%.o: $(src)/%.c FORCE
     $(call cmd,force_checksrc)
     $(call if_changed_rule,cc_o_c)
if_changed_rule和cc_o_c是什麼?看這裏
2. 目錄下的.o文件生成built-in.o
下面的命令啓動.o連接成built-in.o
$(builtin-target): $(obj-y) FORCE
     $(call if_changed,link_o_target)
這裏$(obj-y)是啥?你隨便點開一個目錄下的Makefile,看看就知道了,其實就是各個.o的集合。
3. 生成vmlinux.o
直接看這裏吧,呵呵。
4. vmlinux.lds的生成
.lds文件稱爲鏈接腳本,它的語法和作用,參考這裏。它在最終鏈接生成vmlinux起着重要的作用。它的構建規則在Makefile.build中定義:
$(obj)/%.lds: $(src)/%.lds.S FORCE
     $(call if_changed_dep,cpp_lds_S)
5. 鏈接生成vmlinux
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
     $(call if_changed_rule,vmlinux__)
這裏關於kallsyms.o,可以參考這裏。當目前位置,各個目錄下的.o都已構建完畢,通過$(call if_changed_rule,vmlinux__)
就可以在頂層目錄下生成vmlinux了。
6. 生成bzImage
生成bzImage的工作集中在x86/boot目錄和x86/compressed目錄中,其中boot目錄下的makefile會使用tools/build這個工具將當前目錄中生成的setup.bin vmlinux.bin和啓動設備號信息(即ROOT_DEV)“打包”成bzImage。
其中boot目錄下的setup.ld會將目錄下的所有的.o文件連接成setup.elf,然後使用objcopy處理得到setup.bin。
在compressed目錄下的makefile會將misc.o,piggy.o,head_32.o或者head_64.o鏈接成vmlinux,這個vmlinux會在上層boot目錄中被objcopy處理生成vmlinux.bin。這裏有個奇怪的東西,就是piggy.o,這個東西實際上就是將頂層目錄下生成的vmlinux進行了objcopy+gzip處理之後的最終結果。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章