天天看點

核心子產品編譯:Shared Makefile 運作機理核心子產品編譯:Shared Makefile 運作機理

核心子產品編譯: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
           

為了弄清其作用機理,有兩個問題需要解決:

  1. 為何 modname 的值被正确設定了,而非空值;
  2. obj-m 是如何傳遞給核心源碼目錄的 Makefile 的。

盡管這些問題在事後看來都是很簡單的…

1.2. 現有的解釋

在我看來,現在的有關核心子產品編譯的資料有很多問題,總結起來歸為如下幾種:

  1. 它們完全規避了機理問題,隻談 How,不談 Why;
  2. 它們是針對整個核心編譯過程的,對「核心子產品」這個點的分析不夠充分;
  3. 它們對核心子產品編譯的所謂解析十分簡單,甚至充滿謬誤,乃至将 Makefile 第二次執行的時機解釋為跳轉到核心源碼目錄執行;
  4. 它們沒有基于較新的核心版本,v2.6.0 是較為常見的版本,然而 Linux 對未來的布局隻會展現在新核心中,而新核心的 kbuild 系統有所變化,感覺主要是子產品化有所增強;
  5. 它們排版不美觀,缺乏代碼高亮以及層次感;

當然,在我眼中,最重要的原因其實是第五點…

2. 簡化的結論

執行使用者代碼目錄下的 makefile (記為 Usr Makefile,UM) 時,UM 将目前路徑以變量

M

的值的形式,傳遞給核心源碼目錄下的 makefile (記為 Kernel Makefile,KM),并轉而執行 KM。

KM 在初始化變量之前,會 include UM。之後,KM 同時滿足:

  1. 具備 UM 傳遞的指令行變量,

    $(M)

    等(若 KM 通過

    make -f

    執行别的 makefile,則指令行變量

    M

    也會被别的 makefile 所繼承);
  2. 具備 UM 全部上下文,包括變量、條件判斷及規則等;
  3. 将重新計算 UM 上下文,包括變量、條件判斷等;
  4. 具備執行 UM 邏輯真分支的條件,因為

    $(KERNELRELEASE)

    已被 KM 初始化;
  5. 其中

    obj-m

    的值被 UM 上下文覆寫

因為條件 1) 和條件 2),問題 1# 得到解決;因為上述全部條件,問題 2# 得到解決。

注:
  1. KM 不是指核心源碼根目錄下的 makefile,而是指核心源碼目錄中,所有用到的 makefile。正因如此,是以上述括号中的備注其實很重要。具體細節見下述介紹。
  2. 根據,《跟我一起寫 Makefile(三)》,Makefile 會先讀入所有的 Makefile,然後讀入所有被 include 的 Makefile,再初始化檔案中的變量。

3. Shared Makefile 詳細執行過程

3.1. 使用者代碼目錄執行過程

  • 初始化變量,顯然上下文中不存在變量

    $(KERNELRELEASE)

    ,是以将執行為

    $(KIDR)

    指派的分支;
    ifneq ($(KERNELRELEASE),)
    ...
    else
    KDIR ?= /lib/modules/$(shell uname -r)/build
               
  • 根據生成指令生成僞目标

    default

    ;其中

    $(MAKE) -C $(KDIR) M=$$PWD

    表示将目前路徑以變量 M 的值的形式,傳遞給核心源碼根目錄下的 makefile,并執行該 makefile。
    default:
        $(MAKE) -C $(KDIR) M=$$PWD
        rm -rf modules.order .tmp_versions *.mod* *.o *.o.cmd .*.cmd
               

3.2. 第一跳:核心源碼目錄執行過程

注:以下分析系基于 Linux-4.4.92 核心版本。限于篇幅和個人精力,我隻摘取了與外部子產品編譯有關的、且較為重要的步驟。
  • 檔案

    makefile

    :include

    scripts/Kbuild.include

    。該檔案隻是被 include 進去,其中的變量将在稍後統一初始化;
    # maokelong: @ line 344
    include scripts/Kbuild.include
               
  • 檔案

    makefile

    :判定 M 變量源自指令行,将

    KBUILD_EXTMOD

    置為 M 的值;從下面開始,将使用

    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

    export KBUILD_EXTMOD KBUILD_EXTMOD

    ,此時若再調用别的 makefile,就不必通過傳參傳值了,隻需讀取環境變量

    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

    時生成的兩個目标和所需的一個指令,其作用分别為:
    1. crmodverdir

      :在子產品的目前目錄下建立一個檔案夾

      .tmp_versions

      ,并将其内部檔案全部删除;
    2. $(objtree)/Module.symvers

      :檢查核心源碼根目錄下是否存在檔案

      Module.symvers

    3. $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=$(patsubst _module_%,%,[email protected])

      :解決問題的核心,将在下面進行介紹。

3.3. 第二跳:Makefile.build 執行流程

  • 檔案

    $(srctree)/scripts/Makefile.build

    :include UM;
    # 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

    原始定義處,

    obj-m

    将在變量初始化過程中被 UM 中的

    obj-m

    覆寫。在計算

    obj-m

    的時候,UM 上下文繼承了指令行變量

    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 能否

  1. 繼承來自指令行的變量,即來自于如下實驗中的

    make -f test/hell M=$(M)

    的變量

    M

  2. 繼承來自檔案的變量,即來自于如下實驗中的

    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)…

不過,限于時間與精力有限,以上工作難免有部分不足:

  1. 對 makefile 的具體處理流程不清楚。網上介紹的寥寥數步實在難以解釋一些特殊情況,比如

    include $(varName)

    為何打破了先 include 再處理變量的流程,比如

    export varName

    在什麼時候執行等;
  2. 未對

    obj-m

    的具體加工過程進行調研;
  3. 剛買了個暖耳罩子,這篇文章是我煲機的時候寫的,是以寫得有點飄…
吐槽: CSDN 這謎一樣的 markdown 引擎… 哎

繼續閱讀