核心子產品編譯:Shared Makefile 運作機理
文章目錄
- 核心子產品編譯:Shared Makefile 運作機理
-
- 1. 引言
-
- 1.1. 我想解決的問題
- 1.2. 現有的解釋
- 2. 簡化的結論
- 3. Shared Makefile 詳細執行過程
-
- 3.1. 使用者代碼目錄執行過程
- 3.2. 第一跳:核心源碼目錄執行過程
- 3.3. 第二跳:Makefile.build 執行流程
- 4. 探究實驗
-
- 4.1. 實驗目的
- 4.2. 目錄配置
- 4.3. 檔案配置
- 4.4. 實驗結果及說明
- 5. 尾記
1. 引言
1.1. 我想解決的問題
作為練手,我想寫這樣一個 Makefile,其能自動編譯目前目錄下所有
.C
檔案,并生成一個以目前目錄名為名的核心子產品。
在經過一番嘗試後,以某常見的 Shared Makefile 為藍本,我改編出了這樣一個很奇怪的 Makefile:
modname ?= $(shell basename $(M))
srclist ?= $(shell ls $(M) | grep "\.c")
objlist ?= $(srclist:.c=.o)
#headerdir ?= $(wildcard include)
#==========================================================
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m := $(modname).o
#ccflags-y := -I$(headerdir)
$(modname)-y := $(objlist)
else
# normal makefile
KDIR ?= /lib/modules/$(shell uname -r)/build
default:
$(MAKE) -C $(KDIR) M=$$PWD
clean:
$(MAKE) -C $(KDIR) M=$$PWD clean
.PHONY = default install clean
endif
為了弄清其作用機理,有兩個問題需要解決:
- 為何 modname 的值被正确設定了,而非空值;
- obj-m 是如何傳遞給核心源碼目錄的 Makefile 的。
盡管這些問題在事後看來都是很簡單的…
1.2. 現有的解釋
在我看來,現在的有關核心子產品編譯的資料有很多問題,總結起來歸為如下幾種:
- 它們完全規避了機理問題,隻談 How,不談 Why;
- 它們是針對整個核心編譯過程的,對「核心子產品」這個點的分析不夠充分;
- 它們對核心子產品編譯的所謂解析十分簡單,甚至充滿謬誤,乃至将 Makefile 第二次執行的時機解釋為跳轉到核心源碼目錄執行;
- 它們沒有基于較新的核心版本,v2.6.0 是較為常見的版本,然而 Linux 對未來的布局隻會展現在新核心中,而新核心的 kbuild 系統有所變化,感覺主要是子產品化有所增強;
- 它們排版不美觀,缺乏代碼高亮以及層次感;
當然,在我眼中,最重要的原因其實是第五點…
2. 簡化的結論
執行使用者代碼目錄下的 makefile (記為 Usr Makefile,UM) 時,UM 将目前路徑以變量
M
的值的形式,傳遞給核心源碼目錄下的 makefile (記為 Kernel Makefile,KM),并轉而執行 KM。
KM 在初始化變量之前,會 include UM。之後,KM 同時滿足:
- 具備 UM 傳遞的指令行變量,
等(若 KM 通過$(M)
執行别的 makefile,則指令行變量make -f
也會被别的 makefile 所繼承);M
- 具備 UM 全部上下文,包括變量、條件判斷及規則等;
- 将重新計算 UM 上下文,包括變量、條件判斷等;
- 具備執行 UM 邏輯真分支的條件,因為
已被 KM 初始化;$(KERNELRELEASE)
- 其中
的值被 UM 上下文覆寫obj-m
因為條件 1) 和條件 2),問題 1# 得到解決;因為上述全部條件,問題 2# 得到解決。
注:
- KM 不是指核心源碼根目錄下的 makefile,而是指核心源碼目錄中,所有用到的 makefile。正因如此,是以上述括号中的備注其實很重要。具體細節見下述介紹。
- 根據,《跟我一起寫 Makefile(三)》,Makefile 會先讀入所有的 Makefile,然後讀入所有被 include 的 Makefile,再初始化檔案中的變量。
3. Shared Makefile 詳細執行過程
3.1. 使用者代碼目錄執行過程
- 初始化變量,顯然上下文中不存在變量
,是以将執行為$(KERNELRELEASE)
指派的分支;$(KIDR)
ifneq ($(KERNELRELEASE),) ... else KDIR ?= /lib/modules/$(shell uname -r)/build
- 根據生成指令生成僞目标
;其中default
表示将目前路徑以變量 M 的值的形式,傳遞給核心源碼根目錄下的 makefile,并執行該 makefile。$(MAKE) -C $(KDIR) M=$$PWD
default: $(MAKE) -C $(KDIR) M=$$PWD rm -rf modules.order .tmp_versions *.mod* *.o *.o.cmd .*.cmd
3.2. 第一跳:核心源碼目錄執行過程
注:以下分析系基于 Linux-4.4.92 核心版本。限于篇幅和個人精力,我隻摘取了與外部子產品編譯有關的、且較為重要的步驟。
- 檔案
:includemakefile
。該檔案隻是被 include 進去,其中的變量将在稍後統一初始化;scripts/Kbuild.include
# maokelong: @ line 344 include scripts/Kbuild.include
- 檔案
:判定 M 變量源自指令行,将makefile
置為 M 的值;從下面開始,将使用KBUILD_EXTMOD
代替KBUILD_EXTMOD
;M
# maokelong: @ line 190 ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M) endif
- 檔案
:定義了一個重要的變量scripts/Kbuild.include
,通過該變量,開發者僅需通過使用$(build)
便可以完成等同于$(Q)$(MAKE) $(build)=dir
的功能:$(Q)$(MAKE) -f scripts/Makefile.build obj=dir
# maokelong: @ line 175 build := -f $(srctree)/scripts/Makefile.build obj
- 檔案
:makefile
(是第一項僞目标,也即預設僞目标)依賴_all
;module
# maokelong: @ line 197 ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif
- 檔案
:makefile
,此時若再調用别的 makefile,就不必通過傳參傳值了,隻需讀取環境變量export KBUILD_EXTMOD KBUILD_EXTMOD
和KBUILD_CHECKSRC
即可;KBUILD_EXTMOD
# maokelong: @ line 340 export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD ... # maokelong: @ line 412 export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
- 檔案
:定義生成目标makefile
,modules
依賴modules
,module-dirs
又依賴于module-dirs
以及crmodverdir
,根據前文對$(objtree)/Module.symvers
的解釋,生成該依賴項的時候還會執行相當于這樣的指令:$(build)
。$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$(patsubst _module_%,%,[email protected])
生成# maokelong: @ line 902 ifeq ($(KBUILD_EXTMOD),) ... # maokelong: @ line 1369 else # KBUILD_EXTMOD ... ## maokelong: @ line 1402 module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD)) PHONY += $(module-dirs) modules $(module-dirs): crmodverdir $(objtree)/Module.symvers $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,[email protected]) modules: $(module-dirs) @$(kecho) ' Building modules, stage 2.'; $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost ... # maokelong: @ line 1446 endif # KBUILD_EXTMOD
時生成的兩個目标和所需的一個指令,其作用分别為:module-dirs
-
:在子產品的目前目錄下建立一個檔案夾crmodverdir
,并将其内部檔案全部删除;.tmp_versions
-
:檢查核心源碼根目錄下是否存在檔案$(objtree)/Module.symvers
;Module.symvers
-
:解決問題的核心,将在下面進行介紹。$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$(patsubst _module_%,%,[email protected])
-
3.3. 第二跳:Makefile.build 執行流程
- 檔案
:include UM;$(srctree)/scripts/Makefile.build
# maokelong: @ line 41 # The filename Kbuild has precedence over Makefile kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file)
- 檔案
:$(srctree)/scripts/Makefile.build
原始定義處,obj-m
将在變量初始化過程中被 UM 中的obj-m
覆寫。在計算obj-m
的時候,UM 上下文繼承了指令行變量obj-m
(源于M
,這是經下節的小實驗證明的),并據此計算出我所期望的子產品名$(MAKE) -C $(KDIR) M=$$PWD
,modname ?= $(shell basename $(M))
的值就此計算完畢:obj-m
。obj-m := $(modname).o
# maokelong: @ line 10 # Init all relevant variables used in kbuild files so # 1) they have correct type # 2) they do not inherit any value from the environment obj-y := obj-m :=
- 略…
4. 探究實驗
4.1. 實驗目的
探究
make -f
所執行的 makefile 能否
- 繼承來自指令行的變量,即來自于如下實驗中的
的變量make -f test/hell M=$(M)
M
- 繼承來自檔案的變量,即來自于如下實驗中的
的變量C = $(M)
C
4.2. 目錄配置
建立這麼一個目錄樹,其中
test
和
test2
為目錄名,
hell
、
fuc
和
makefile
是随便命名的 makefile。
+--test
| |
| +--hell
|
+--test2
| |
| +--fuc
|
+--makefile
4.3. 檔案配置
# makefile
M = $(shell pwd)
C = $(M)
default:
@echo $(C)
make -f test/hell M=$(M)
# hell
default:
@echo hell
@echo $(C)
@echo $(M)
make -f test2/fuc
# fuc
default:
@echo fuc
@echo $(M)
4.4. 實驗結果及說明
/mnt/d/pwd/maketest
make -f test/hell M=/mnt/d/pwd/maketest
make[1]: Entering directory '/mnt/d/pwd/maketest'
hell
/mnt/d/pwd/maketest
make -f test2/fuc
make[2]: Entering directory '/mnt/d/pwd/maketest'
fuc
/mnt/d/pwd/maketest
make[2]: Leaving directory '/mnt/d/pwd/maketest'
make[1]: Leaving directory '/mnt/d/pwd/maketest'
所有變量
M
均順利列印;
C
僅在定義其的檔案中列印成功。
實驗結果說明
make -f
所執行的 makefile
- 能繼承來自指令行的變量,即來自于如上實驗中的
的變量make -f test/hell M=$(M)
M
- 不能繼承來自檔案的變量,即來自于如上實驗中的
的變量C = $(M)
C
5. 尾記
做完之後,覺得這幾天的調研還是很有意義的,比如我對 makefile 的執行流程的了解又加深了(先獲得内容再建構規則再執行),比如我學會了如何閱讀和調試 makefile($(warning …)),比如我知道哪怕核心的 kbuild 也不是玄學的(所有機理都有迹可循,不是嗎),比如我知道了 make 指令行變量的生命力居然這麼強(在獲得指令行變量後執行 make -f,該變量會被傳遞給下一個 makefile)…
不過,限于時間與精力有限,以上工作難免有部分不足:
- 對 makefile 的具體處理流程不清楚。網上介紹的寥寥數步實在難以解釋一些特殊情況,比如
為何打破了先 include 再處理變量的流程,比如include $(varName)
在什麼時候執行等;export varName
- 未對
的具體加工過程進行調研;obj-m
- 剛買了個暖耳罩子,這篇文章是我煲機的時候寫的,是以寫得有點飄…
吐槽: CSDN 這謎一樣的 markdown 引擎… 哎