轉載自mz_linux的ChinaUnix博客 :http://blog.chinaunix.net/uid-26806098-id-3141136.html
眾所周知,Linux內核是使用make命令來配置並編譯的,那必然少不了Makefile。在內核目錄樹中我們可以看到內核編譯系統的頂層Makefile文件。但是如此復雜、龐大的內核源碼絕不可能使用一個或幾個Makefile文件來完成配置編譯,而是需要一套同樣復雜、龐大,且為Linux內核定制的Makefile系統。她可以說是內核的一個子系統,是內核中比較特殊的一部分,幾乎都是應用層的程序和腳本,但又和生成的內核二進制文件息息相關。編譯不僅涉及本地編譯,還涉及各個平台之間的交叉編譯以及二進制文件格式處理等等。她是對Makefile 在功能上的擴充,使其在配置編譯Linux內核的時候更加靈活、高效和簡潔。
盡管她是一個復雜的系統,但對絕大部分內核開發者來說只需要知道如何使用,而無需了解其中的細節。她對絕大部分內核開發者基本上是透明的,隱藏了大部分實現細節,有效地降低了開發者的負擔,能使其能專注於內核開發,而不至於花費時間和精力在編譯過程上。
以下我們就來簡要的了解一下內核Makefile體系。
一、 內核Makefile體系概述
其實內核Makefile體系的包含了Kconfig和Kbuild兩個系統。她曾經的維護人是Sam Ravnborg <sam@ravnborg.org>,現在的暫時沒有查到。參考資料:
kbuild 更換維護者 作者:王聰 (西郵神人,崇拜下)
Kconfig 對應的是內核配置階段,如你使用命令:make menuconfig,就是在使用Kconfig系統。Kconfig由以下三部分組成:
scripts/kconfig/* |
Kconfig文件解析程序 |
kconfig |
各個內核源代碼目錄中的kconfig文件 |
arch/$(ARCH)/configs/*_defconfig |
各個平台的缺省配置文件 |
當Kconfig系統生成.config后,Kbuild 會依據.config編譯指定的目標。后面我會簡單地對make %config的流程進行情景分析,這里不必贅述。
Kbuild 是內核Makefile體系重點,對應內核編譯階段,由5個部分組成:
頂層Makefile |
根據不同的平台,對各類target分類並調用相應的規則Makefile生成目標 |
.config |
內核配置文件 |
arch/$(ARCH)/Makefile |
具體平台相關的Makefile |
scripts/Makefile.* |
通用規則文件,面向所有的Kbuild Makefiles,所起的作用可以從后綴名中得知。 |
各子目錄下的Makefile 文件 |
由其上層目錄的Makefile調用,執行其上層傳遞下來的命令 |
而其中scripts目錄下的編譯規則文件和其目錄下的C程序在整個編譯過程起着重要的作用。列舉如下:
文件名 |
作用 |
Kbuild.include |
共用的定義文件,被許多獨立的Makefile.*規則文件和頂層Makefile包含 |
Makefile.build |
提供編譯built-in.o, lib.a等的規則 |
Makefile.lib |
負責歸類分析obj-y、obj-m和其中的目錄subdir-ym所使用的規則 |
Makefile.host |
本機編譯工具(hostprog-y)的編譯規則 |
Makefile.clean |
內核源碼目錄清理規則 |
Makefile.headerinst |
內核頭文件安裝時使用的規則 |
Makefile.modinst |
內核模塊安裝規則 |
Makefile.modpost |
模塊編譯的第二階段,由.o和.mod生成.ko時使用的規則 |
頂層Makefile 主要是負責完成vmlinux(內核文件)與*.ko(內核模塊文件) 的編譯。頂層 Makefile 讀取.config 文件,並根據.config 文件確定訪問哪些子目錄,並通過遞歸向下訪問子目錄的形式完成。頂層Makefile同時根據.config 文件原封不動的包含一個具體架構的Makefile,其名字類似於 arch/$(ARCH)/Makefile。該架構Makefile 向頂層Makefile 提供其架構的特別信息。
每一個子目錄都有一個Makefile 文件,用來執行從其上層目錄傳遞下來的命令。子目錄的 Makefile 也從.config 文件中提取信息,生成內核編譯所需的文件列表。
二、 內核Makefile導讀與情景分析
1、概述
上面簡要介紹了內核Makefile的總體結構,但當我們打開頂層Makefile文件時還是因為她的復雜而覺得無從下手。但是內核Makefile就是Makefile,和最簡單的Makefile遵循着同樣的規則。所以只要我們靜下心來分析,還是可以理解的。當然,在閱讀內核的Makefile前,你必須對Makefile和shell腳本有一定的基礎。
- 推薦參考資料:
- 《GNU make中文手冊》 翻譯整理:徐海兵 PDF文檔
- 《高級Bash腳本編程指南》翻譯:楊春敏 黃毅
根據Makefile的執行規則,在分析Makefile時,首先必須確定一個目標 ,然后才能確定所有的依賴關系 ,最后根據更新情況決定是否執行相應的命令。所以要看懂內核Makefile的大致框架,我們首先要了解她里面所定義的目標。而內核Makefile所定義的目標基本上可以通過 make help打印出來(因為help本身就是頂層Makefile的一個目標,里面是打印幫助信息的“echo”命令)。
這些目標可以分為以下幾個大類:
目標 |
常用目標舉例 |
作用 |
|
配置 |
%config |
config |
啟動Kconfig,以不同界面來配置內核。 |
menuconfig |
|||
xconfig |
|||
編譯 |
all |
編譯vmlinux內核映像和內核模塊 |
|
vmlinux |
編譯vmlinux內核映像 |
||
modules |
編譯內核模塊 |
||
安裝 |
headers_install |
安裝內核頭文件/模塊 |
|
modules_install |
|||
源碼瀏覽 |
tags |
生成代碼瀏覽工具所需要的文件 |
|
TAGS |
|||
cscope |
|||
靜態分析 |
checkstack |
檢查並分析內核代碼 |
|
namespacecheck |
|||
headers_check |
|||
內核打包 |
%pkg |
以不同的安裝格式編譯內核 |
|
文檔轉換 |
%doc |
把kernel文檔轉成不同格式 |
|
構架相關 (以arm為例) |
zImage |
生成壓縮的內核映像 |
|
uImage |
生成壓縮的u-boot可引導的內核映像 |
||
install |
安裝內核映像 |
其中的構架相關目標在頂層Makefile上並未出現,而是被包含在平台相關的Makefile(arch/$(ARCH)/Makefile)中。
2、情景分析
以下我們就來分析一個簡單的目標(menuconfig),作為情景分析范例來演示一下內核Makefile的分析方法。
首先當我們在內核源碼的根目錄下執行make menuconfig命令時,根據規則,make程序讀取頂層Makefile 文件及其包含的Makefile文件,內建所有的變量、明確規則和隱含規則,並建立所有目標和依賴之間的依賴關系結構鏈表。make程序最終會調用規則:
config %config: scripts_basic outputmakefile FORCE $(Q)mkdir -p include/linux include/config $(Q)$(MAKE) $(build)=scripts/kconfig $@ |
調用的原因是我們指定的目標“menuconfig”匹配了“%config”。
她的依賴目標是scripts_basic 和outputmakefile,以及FORCE。也就是說在完成了這3個依賴目標后,下面的兩個命令才會執行以完成我們指定的目標“menuconfig”。
所以我們來看看這三個依賴目標實現的簡要過程:
(1)scripts_basic
make程序會調用規則:
scripts_basic: $(Q)$(MAKE) $(build)=scripts/basic |
他沒有依賴目標,所以直接執行了以下的指令,只要將指令展開,我們就知道make做了什么操作。其中比較不好展開的是$(build),她的定義在scripts/Kbuild.include中:
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj |
所以展開后是:
make -f scripts/Makefile.build obj= scripts/basic |
也就是make 解析執行scripts/Makefile.build文件,且參數obj= scripts/basic。而在解析執行scripts/Makefile.build文件的時候,scripts/Makefile.build又會通過解析傳入參數來包含對應文件夾下的Makefile文件(scripts/basic/Makefile),從中獲得需要編譯的目標。
在確定這個目標以后,通過目標的類別來繼續包含一些scripts/Makefile.*文件。例如scripts/basic/Makefile中內容如下:
hostprogs-y := fixdep docproc hash always := $(hostprogs-y)
# fixdep is needed to compile other host programs $(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep |
所以scripts/Makefile.build會包含scripts/Makefile.host。相應的語句如下:
# Do not include host rules unless needed ifneq ($(hostprogs-y)$(hostprogs-m),) include scripts/Makefile.host endif |
此外scripts/Makefile.build會包含include scripts/Makefile.lib等必須的規則定義文件,在這些文件的共同作用下完成對scripts/basic/Makefile中指定的程序編譯。
由於Makefile.build的解析執行牽涉了多個Makefile.*文件,過程較為復雜,礙於篇幅無法一條一條指令的分析,興趣的讀者可以自行分析。
- 推薦兩篇經典的分析文檔:
- 《kbuild實現分析》 作者:x.yin@hotmail.com
- 《Kbuild系統原理分析》 作者未知,網上有PDF文檔
(2)outputmakefile
make程序會調用規則:
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 |
從這里我們可以看出:outputmakefile是當KBUILD_SRC不為空(指定O=dir,編譯輸出目錄和源代碼目錄分開)時,在輸出目錄建立Makefile時才執行命令的,
如果我們在源碼根目錄下執行make menuconfig命令時,這個目標是空的,什么都不做。
如果我們指定了O=dir時,就會執行源碼目錄下的scripts/mkmakefile,用於在指定的目錄下產生一個Makefile,並可以在指定的目錄下開始編譯。
(3)FORCE
這是一個在內核Makefile中隨處可見的偽目標,她的定義在頂層Makefile的最后:
PHONY += FORCE FORCE:
|
是個完全的空目標,但是為什么要定義一個這樣的空目標,並讓許多目標將其作為依賴目標呢?原因如下:
正因為FORCE 是一個沒有命令或者依賴目標,不可能生成相應文件的偽目標。當make執行此規則時,總會認為FORCE不存在,必須完成這個目標,所以她是一個強制目標。也就是說:規則一旦被執行,make 就認為它的目標已經被執行並更新過了。當她作為一個規則的依賴時,由於依賴總被認為被更新過的,因此作為依賴所在的規則中定義的命令總會被執行。所以可以這么說:只要執行依賴包含FORCE的目標,其目標下的命令必被執行。
在make完成了以上3個目標之后,就開始執行下面的命令的,首先是
$(Q)mkdir -p include/linux include/config |
這個很好理解,就是建立兩個必須的文件夾。然后
$(Q)$(MAKE) $(build)=scripts/kconfig $@ |
這和我們上面分析的$(Q)$(MAKE) $(build)=結構相同,將其展開得到:
make -f scripts/Makefile.build obj=scripts/kconfigmenuconfig |
所以這個指令的效果是使make 解析執行scripts/Makefile.build文件,且參數obj=scripts/kconfig menuconfig。這樣Makefile.build會包含對應文件夾下的Makefile文件(scripts/kconfig /Makefile),並完成scripts/kconfig /Makefile下的目標:
menuconfig: $(obj)/mconf $< $(Kconfig) |
這個目標的依賴條件是$(obj)/mconf,通過分析可知她其實是對應以下規則:
mconf-objs := mconf.o zconf.tab.o $(lxdialog) …… ifeq ($(MAKECMDGOALS),menuconfig) hostprogs-y += mconf endif |
也就是編譯生成本機使用的mconf程序。完成依賴目標后,通過scripts/kconfig /Makefile中對Kconfig的定義可知,最后執行:
mconf arch/$(SRCARCH)/Kconfig |
而對於conf和xconf等都有類似的過程,所以總結起來:當make %config 時,內核根目錄的頂層Makefile會臨時編譯出scripts/kconfig 中的工具程序conf/mconf/qconf 等負責對arch/$(SRCARCH)/Kconfig 文件進行解析。這個Kconfig 又通過source標記調用各個目錄下的Kconfig文件構建出一個Kconfig樹,使得工具程序構建出整個內核的配置界面。在配置結束后,工具程序就會生成我們常見的.config文件。
三、在內核中添加自己的模塊
雖然內核Makefile體系很是復雜,但是這種復雜帶來的確是開發時的便利。其實內核Makefile體系之所以復雜,其中的一個原因就是為了方便擴展。對於一個開發者來要在內核中添加自己的一個驅動代碼是非常簡單的事情。
一般來說,對於一個新驅動代碼的添加,驅動工程師只需要在內核源碼的drivers目錄的相應子目錄下添加新設備驅動源碼,並增加或修改該目錄下的Kconfig和Makefile文件即可。
比如你已經寫好了一個針對TI 的AM33XX芯片的 LED的驅動程序,名為am33xx_led.c。
(1) 將驅動源碼am33xx_led.c等文件復制到linux-X.Y.Z/drivers/char目錄。
(2) 在該目錄下的Kconfig文件中添加LED驅動的配置選項:
config AM33XX_LED bool "Support for am33xx led drivers" depends on SOC_OMAPAM33XX default n ---help--- Say Y here if you want to support for AM33XX LED drivers. |
(3) 在該目錄下的Makefile文件中添加對LED驅動的編譯:
obj-$(CONFIG_AM33XX_LED) += am33xx_led.o |
這樣你就可以在make menuconfig的時候看到這個配置選項,並進行配置了。
當然,上面的例子只是一個意思,對於Kconfig文件和Makefile的詳細語法,請參考內核文檔:Documentation/kbuild/makefile.txt
四 、在內核Makefile上對讀者的建議
這個復雜的Makefile體系體現了很多優秀程序共有的設計思想,對於我們今后的程序設計上有很多值得借鑒的地方。比如:模塊化設計、簡化編程接口,使得自行添加模塊更加的簡潔。閱讀分析這樣復雜的Makefile對於學習和編寫Makefile和shell腳本有很好的參考價值。如果你正在學習Makefile的編寫和閱讀,那你可以耐心的分析一下內核的Makefile體系,只要你認真分析了一兩個目標的實現,你會發現當你在閱讀一些小軟件的Makefile時已經是輕車熟路了。特別是現在很多芯片的開發包都是以SDK包的形式發布的,而這些軟件包都是通過Makefile體系來實現自動編譯和配置的,所以熟悉Makefile是每個Linux開發者都需要做到的。