前言
Linux Kernel中是透過Kbuild Makefile(Kernel build, 基於makefile的compile tool)來對kernel code編譯,然後最終產生vmlinux file。所以了解Kbuild是很重要的,在看一個kernel code的時候可以透過Makefile file來幫助了解編譯的架構構成。
要了解Kbuild Makefile就要知道什麼是Makefile,但是Makefile細節太多了,講不完齁
詳細完整的教學網路上很多了,所以這篇文章不講瑣碎的原理跟細節,而是試著從實際看code會看到的內容來介紹:
- 快速介紹Makefile的概念,我們只講重點
- 快速介紹Kbuild規則,我們只講重點和常會用到的部分
- 搭配kernel code來實際看一下例子
從0開始的make快速上手教學
先科普一下一個程式到產生執行檔的過程: 由c/c++等語言邊寫的程式碼檔案,會先經過編譯器(compiler)產生object file,然後再透過連結器(linker)將一個或多個object file組成執行檔executable。
今天如果需要編譯一個由c寫的程式碼我們可以直接下gcc指令來編譯,但下列一些狀況就很適合使用Makefile:
- 例如包含多個.c(object file)檔案,彼此檔案之間的相關性很複雜的時候,人工來維護這些相關性比較麻煩
- 有時候要編譯的檔案很多(例如linux kernel),但我們只改了一部分的code,那跟這部分無關的其實可以都不用重新編譯以節省時間
此時我們可以透過一些工具來幫我們做檢查並自動編譯,Makefile就是一個好工具。夠過指令make
去找資料夾下的Makefile(還有其他名稱會被查找,這裡有個查找的順序)文件,然後會做這些檢查:
- 如果目標(target)的相依性項目(dependencies)中需要的的source code file沒有被編譯過,則編譯相關檔案然後生成我們需要的檔案
- 如果目標的相依檔案有一個source code file被更動過,則他將會被重新編譯
- 如果有目標的相依項目有header file被修改過,則所有使用到該header file的都會被重新編譯
目標跟相依檔案是什麼意思?考慮一個gcc command作為例子:0
gcc -o hello_make main.o
這段指令是說透過連結main.o產生一個hello_make的執行檔,但我們沒有main.o呀?
所以我們需要先編譯main.c來產生main.o,透過先下這個指令:0
gcc -c main.c
上述的兩個步驟隱含了hello_make、main.o和main.c的關係:
- 所以為了產生hello_make(target),我們需要main.o(dependency)
- 而因為需要main.o(target),我們需要main.c(dependency)
這種目標跟相依性的概念就是Makefile的核心!
上面這個指令在等價於Makefile的這種寫法0
1
2
3
4all: main.o
gcc -o hello_make main.o
main.o: main.c
gcc -c main.c
這樣有沒有比較有概念了呢?
比較了解Makefile是什麼後,來正式看一下他的格式0
1
2
3
4# comment in Makefile
<target>: <dependiency1> <dependiency2> <dependiency3>...
# Note that makefile use tab!!! not space!!!
<rule1>
<rule2>
- 一個Makefile可以有很多個target,在下
make
時預設會找第一個target作為最終目標- 如果要執行特定的target可以下
make <target>
command
- 如果要執行特定的target可以下
- 如果要執行的target的相依性項目不存在,則繼續往下看其他的target來產生所需的檔案
最後,一些Makefile常用小知識:
- Makefile是使用Tab而不是空格,所以如果編譯器有將Tab自動轉成4個空格的,打美蝶斯!
- $是變數的意思,所以可以透過變數設定來控制我們的rule
- 由於$是變數,所以如果要執行shell command會使用兩個錢錢符號
- %是萬用匹配符號,例如
%.o: %.c
就是將所有.o檔對應的.c file作為dependency - 以及其他許多噁心的符號:
- $@: rule當前的target name
- $<: rule當前的(第一個)dependency name
- $*: rule當前的dependency name,但不含副檔名
- $?: rule當前比targets還新的dependencies
瞭解了這些就可以開始去找網路上的Makefile練功打怪了,詳細的教學不是本篇的重點,主要是讓大家對Makefile有個基本概念,有興趣的也可以去看References的相關文章繼續學習唷。
番外篇: 要如何遞迴make所有子目錄?
Makefile其實是可以進入某個目錄然後去執行該目錄下的make的,使用“-C”參數即可,例如:0
make -C subdirectory
他就會先進到subdirectory這個資料夾中去執行make
,然後再回來繼續執行這一層的make
。
可是如果這些資料夾名稱我都要一個一個手動填也是很麻煩,要如何自動取得底下所有資料夾,然後讓他一個一個自己進去make呢?搭配shell command來達成:
0 | SUBDIRS = $(shell ls -d */) |
搞定!
Linux Kernel的Makefile: Kbuild Makefile
Kbuild是Kernel build的縮寫,為了方便對kernel進行編譯而提供了一些特別的功能。
Linux Kbuild主要有四個部分:
- .config: kernel相關設置
- arch/$(ARCH)/Makefile: 與架構相關的Makefile
- scripts/Makefile.*: Kbuild的一些規則
- 根目錄下的Makefile以及其他資料夾下的Makefiles
Kbuild的用法相當簡單,對於一個driver的撰寫者最常會碰到的就2種:
obj-y
: 將module編進kernel中- 注意對於
obj-y
直接將module編進kernel時,編譯的順序很重要,因為這會影響開機時module init的順序,如果順序沒搞好可能就會遇到問題!可以參考[Linux Kernel慢慢學]Linux modules載入及載入順序
- 注意對於
obj-m
: 將module編成.ko檔,以便之後可以進行載入
其他還有產生library的lib-y
、產生executable的hostprogs-y
…有興趣的再自己去看
那Kbuild要怎麼進入一個資料夾呢? 透過obj-$(CONFIG_) += directory/
的方式
- 在kernel code裡面較少使用上面番外篇的遞迴形式來跑所有的子目錄,因為通常kernel的Makefile還會搭配一堆config,這些config可以讓你選擇這次編譯時要將哪些module編進來哪些不要,所以會需要一個一個資料夾的方式條列在Makefile內
實際來看一了例子吧!kernel 4.14的/drivers/clk/Makefile
0 | # /drivers/clk/Makefile |
透過CONFIG_
是m或是y來決定kernel要怎麼去編譯這些modules,而這些CONFIG_
是透過Kconfig來進行設置,最後記錄在.config內。
Kernel各個driver下的Makefile幾乎都長的差不多這樣,甚至比外面琳瑯滿目的Makefile還相對單純,所以看一兩個就熟悉了。
Linux Makefile Code Trace
最後,既然是Makefile,總該有個進入點吧?
想像上應該是對根目錄的Makefile下make
相關指令,然後應該有一個地方會定義需要被一同編譯進去的子資料夾。
稍微來trace一下根目錄下的Makefile:
- 第一個target在_all
120
121
122# That's our default target when none is given on the command line
PHONY := _all
_all: - line 194: _all依賴all,這段就不放code了
- all依賴vmlinux
619
620
621
622
623# The all: target is the default when no target is given on the
# command line.
# This allow a user to issue only 'make' to build a kernel including modules
# Defaults to vmlinux, but the arch makefile usually adds further targets
all: vmlinux - vmlinux依賴四個項目: scripts/link-vmlinux.sh, vmlinux_prereq, $(vmlinux-deps), FORCE
1004
1005vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE
+$(call if_changed,link-vmlinux) - 其中,$(vmlinux-deps)依賴$(vmlinux-dirs),$(vmlinux-dirs)定義了所有應該被編譯進kernel的folder。後續的細節就不追了,有興趣可以參考linux kernel Makefile编译流程分析
951
952
953vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))
最後,介紹一下關於FORCE的用法:
實際上他就是一個沒有dependency和rule的target,在Makefile中如果依賴和指令都為空的target每次都需要強制生成,所以對於每次都需要強制重新產生的target中可以在最後加上FORCE的項目。
1714 | PHONY += FORCE |
References
- Makefile 語法簡介
- 簡單學 makefile:makefile 介紹與範例程式
- Recursive make in subdirectories
- 【Linux + Makefile】十分鐘教你學會Makefile的FORCE
- Linux Kernel Makefiles
- KBUILD 系统原理分析