linux內核makefile概覽

linux內核makefile概覽

本博客參照內核官方英文文檔html

linux的內核makefile主要用於編譯整個內核源碼,按照用戶的需求生成各類目標文件,對於用戶來講,編譯內核時很是簡單的,只須要幾個指令就能夠作到,可是對於一個驅動開發者而言,瞭解內核源碼的編譯機制是很是必要的。linux

make 和 makefile

須要瞭解的是:make是linux下的一個程序軟件,makefile至關於針對make程序的配置文件,當咱們執行make命令時,make將會在當前目錄尋找Makefile文件,而後根據Makefile的配置對源文件進行編譯。git

linux內核源代碼的編譯也是使用make工具和makefile,可是它在普通的C程序編譯的基礎上對配置和編譯選項進行了擴展,這就是kbuild系統,專門針對linux的內核編譯,使得linux內核的編譯更加簡潔而高效。github

linux的內核鏡像文件

首先咱們須要認識一下linux內核鏡像的各類形式,畢竟編譯內核最主要的目的就是生成內核鏡像,它有幾種形式:vmlinux、vmlinux.bin、vmlinuz、zImage、bzImage。架構

  • vmlinux:這是編譯linux內核直接生成的原始鏡像文件,它是由靜態連接以後生成的可執行文件,可是它通常不做爲最終的鏡像使用,不能直接boot啓動,用於生成vmlinuz,能夠debug的時候使用。
  • vmlinux.bin:與vmlinux相同,但採用可啓動的原始二進制文件格式。丟棄全部符號和重定位信息。經過objcopy -O binary vmlinux vmlinux.bin從vmlinux生成。
  • vmlinuz:由vmlinux通過gzip(也能夠是bzip)壓縮而來,同時在vmlinux的基礎上進一步添加了啓動和解壓縮代碼,是能夠引導boot啓動內核的最終鏡像。vmlinuz一般被放置在/boot目錄,/boot目錄下存放的是系統引導須要的文件,同時vmlinuz文件解壓出的vmlinux不帶符號表的目標文件,因此通常/boot目錄下會帶一個符號表System.map文件。
  • zImage:這是小內核的舊格式,有指令make zImage生成,僅適用於只有640K如下內存的linux kernel文件。
  • bzImage: big zImage,須要注意的是這個bz與bzip沒有任何關係,適用於更大的linux kernel文件。現代處理器的linux鏡像都是生成bzImage文件,同時,vmlinuz和bzImage是同一類型的文件,通常狀況下這個和vmlinuz是同一個東西。

對於這一系列的生成文件能夠參考官方文檔函數

kbuild系統

各類各樣的makeifle文件

在linux中,因爲內核代碼的分層模型,以及兼容不少平臺的特性,makefile文件分佈在各個目錄中,對每一個模塊進行分離編譯,下降耦合性,使編譯方式更加靈活。
makefile主要是如下五個部分:工具

  • 頂層makefile : 在源代碼的根目錄有個頂層makefile,頂層makefile的做用就是負責生成兩個最重要的部分:編譯生成vmlinux和各類模塊。
  • .config文件 : 這個config文件主要是產生自用戶對內核模塊的配置,有三種配置方式:
    • 編譯進內核
    • 編譯成可加載模塊
    • 不進行編譯。
  • arch/$(ARCH)/Makefile : 從目錄能夠看出,這個makefile主要是根據指定的平臺對內核鏡像進行相應的配置,提供平臺信息給頂層makefile。
  • scirpts/makefile. : *這些makefile配置文件包含了構建內核的規則。
  • kbuild makefiles : 每個模塊都是單獨被編譯而後再連接的,因此這一種kbiuld makefile幾乎在每一個模塊中都存在.在這些模塊文件(子目錄)中,也可使用Kbuild文件代替Makefile,當二者同時存在時,優先選擇Kbuild文件進行編譯工做,只是用戶習慣性地使用Makefile來命名。

kbuild makefile

編譯進內核的模塊

若是須要將一個模塊配置進內核,須要在makefile中進行配置:ui

obj-y += foo.o

將foo.o編譯進內核,根據make的自動推導原則,make將會自動將foo.c編譯成foo.o。命令行

上述方式基本上用於開發時的模塊單獨編譯,當須要一次編譯整個內核時,一般是在top makefile中這樣寫:debug

obj-$(CONFIG_FOO) += foo.o

在.config文件中將CONFIG_FOO變量配置成y,當須要修改模塊的編譯行爲時,就能夠統一在配置文件中修改,而不用到makefile中去找。

kbuild編譯全部的obj-y的文件,而後調用$(AR) rcSTP將全部被編譯的目標文件進行打包,打包成build-in.o文件,須要注意的是這僅僅是一份壓縮版的存檔,這個目標文件裏面並不包含符號表,既然沒有符號表,它就不能被連接。

緊接着調用scripts/link-vmlinux.sh,將上面產生的不帶符號表的目標文件添加符號表和索引,做爲生成vmlinux鏡像的輸入文件,連接生成vmlinux。

對於這些被編譯進內核的模塊,模塊排列的順序是有意義的,容許一個模塊被重複配置,系統將會取用第一個出現的配置項,而忽略隨後出現的配置項,並不會出現後項覆蓋前項的現象。

連接的順序同時也是有意義的,由於編譯進內核的模塊一般由xxx_initcall()來描述,內核對這些模塊分了相應的初始化優先級,相同優先級的模塊初始化函數將會被依次放置在同一個段中,而這些模塊執行的順序就取決於放置的前後順序,由連接順序所決定。

linux的initcall機制能夠參考另外一篇博客:linux的initcall機制

編譯可加載的模塊

全部在配置文件中標記爲-m的模塊將被編譯成可加載模塊.ko文件。

若是須要將一個模塊配置爲可加載模塊,須要在makefile中進行配置:

obj-m += foo.o

一樣的,一般能夠寫成這樣的形式:

obj-$(CONFIG_FOO) += foo.o

在.config文件中將CONFIG_FOO變量配置成m,在配置文件中統一控制,編譯完成時將會在當前文件夾中生成foo.ko文件,在內核運行時使用insmod或者是modprobe指令加載到內核。

模塊編譯依賴多個文件

一般的,驅動開發者也會將單獨編譯本身開發的驅動模塊,當一個驅動模塊依賴多個源文件時,須要經過如下方式來指定依賴的文件:

obj-m += foo.o
foo-y := a.o b.o c.o

foo.o 由a.o,b.o,c.o生成,而後調用$(LD) -r 將a.o,b.o,c.o連接成foo.o文件。

一樣地,makefile支持以變量的形式來指定是否生成foo.o,咱們能夠這樣:

obj-$(CONFIG_FOO) += foo.o
foo-$(CONFIG_FOO_XATTR) += a.o b.o c.o

根據CONFIG_FOO_XATTR的配置屬性來決定是否生成foo.o,而後根據CONFIG_FOO屬性來決定將foo.o模塊編入內核仍是做爲模塊。

makefile目錄層次關係的處理

須要理解的一個原則就是:一個makefile只負責處理本目錄中的編譯關係,天然地,其餘目錄中的文件編譯由其餘目錄的makefile負責,整個linux內核的makefile組成一個樹狀結構,對於上層makefile的子目錄而言,只須要讓kbuild知道它應該怎樣進行遞歸地進入目錄便可。

kbuild利用目錄指定的方式來進行目錄指定操做,舉個例子:

obj-$(CONFIG_FOO) += foo/

當CONFIG_FOO被配置成y或者m時,kbuild就會進入到foo/目錄中,可是須要注意的是,這個信息僅僅是告訴kbuild應該進入到哪一個目錄,而不對其目錄中的編譯作任何指導。

編譯選項

*** 須要注意的是,在以前的版本中,編譯的選項由EXTRA_CFLAGS, EXTRA_AFLAGS和 EXTRA_LDFLAGS修改爲了ccflags-y asflags-y和ldflags-y. ***

ccflags-y asflags-y和ldflags-y

ccflags-y asflags-y和ldflags-y這三個變量的值分別對應編譯、彙編、連接時的參數。

同時,全部的ccflags-y asflags-y和ldflags-y這三個變量只對有定義的makefile中使用,簡而言之,這些flag在makefile樹中不會有繼承效果,makefile之間相互獨立。

subdir-ccflags-y, subdir-asflags-y

這兩個編譯選項與ccflags-y和asflags-y效果是一致的,只是添加了subdir-前綴,意味着這兩個編譯選項對本目錄和全部的子目錄都有效。

CFLAGS_$@, AFLAGS_$@

使用CFLAGS_或者AFLAGS_前綴描述的模塊能夠爲模塊的編譯單獨提供參數,舉個例子:

CFLAGS_foo.o = -DAUTOCONF

在編譯foo.o時,添加了-DAUTOCONF編譯選項。

kbuild中的變量

頂層makefile中定義瞭如下變量:

KERNELRELEASE

這是一個字符串,用於構建安裝目錄的名字(通常使用版本號來區分)或者顯示當前的版本號。

ARCH

定義當前的目標架構平臺,好比:"X86","ARM",默認狀況下,ARCH的值爲當前編譯的主機架構,可是在交叉編譯環境中,須要在頂層makefile或者是命令行中指定架構:

make ARCH=arm ...

INSTALL_PATH

指定安裝目錄,安裝目錄主要是爲了放置須要安裝的鏡像和map(符號表)文件,系統的啓動須要這些文件的參與。

INSTALL_MOD_PATH, MODLIB

INSTALL_MOD_PATH:爲模塊指定安裝的前綴目錄,這個變量在頂層makefile中並無被定義,用戶可使用,MODLIB爲模塊指定安裝目錄.

默認狀況下,模塊會被安裝到$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)中,默認INSTALL_MOD_PATH不會被指定,因此會被安裝到/lib/modules/$(KERNELRELEASE)中。

INSTALL_MOD_STRIP

若是這個變量被指定,模塊就會將一些額外的、運行時非必要的信息剝離出來以縮減模塊的大小,當INSTALL_MOD_STRIP爲1時,--strip-debug選項就會被使用,模塊的調試信息將被刪除,不然就執行默認的參數,模塊編譯時會添加一些輔助信息。

這些全局變量一旦在頂層makefile中被定義就全局有效,可是有一點須要注意,在驅動開發時,通常編譯單一的模塊,執行make調用的是當前目錄下的Makefile.

在這種狀況下這些變量是沒有被定義的,只有先調用了頂層makefile以後,這些變量在子目錄中的makefile才被賦值。

生成header文件

vmlinux中打包了全部模塊編譯生成的目標文件,在驅動開發者眼中,在內核啓動完成以後,它的做用至關於一個動態庫,既然是一個庫,若是其餘開發者須要使用裏面的接口,就須要相應的頭文件。

天然地,build也會生成相應的header文件供開發者使用,一個最簡單的方式就是用下面這個指令:

make headers_install ARCH=arm INSTALL_HDR_PATH=/DIR

ARCH:指定CPU的體系架構,默認是當前主機的架構,可使用如下命令查看當前源碼支持哪些架構:

ls -d include/asm-* | sed 's/.*-//'

INSTALL_HDR_PATH:指定頭文件的放置目錄,默認是./usr。

至此,build工具將在指定的DIR目錄生成基於arm架構的頭文件,開發者在開發時就能夠引用這些頭文件。

小結

爲了清晰地瞭解kbuild的執行,有必要對kbuild的執行過程作一下梳理:

  • 根據用戶(內核)的配置生成相應的.config文件
  • 將內核的版本號存入include/linux/version.h
  • 創建指向 include/asm-$(ARCH) 的符號連接,選定平臺
  • 更新全部編譯所需的文件。
  • 從頂層makefile開始,遞歸地訪問各個子目錄,對相應的模塊編譯生成目標文件
  • 連接過程,在源代碼的頂層目錄連接生成vmlinux
  • 根據具體架構提供的信息添加相應符號,生成最終的啓動鏡像,每每不一樣架構之間的啓動方式不一致。
    • 這一部分包含啓動指令
    • 準備initrd鏡像等平臺相關的部分。

好了,關於linux內核編譯build系統的討論就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言

關於linux可加載模塊編譯makefile介紹可參考另外一篇博客:linux內核可加載模塊makefile簡述

原創博客,轉載請註明出處!

祝各位早日實現項目叢中過,bug不沾身.

相關文章
相關標籤/搜索