linux內核模塊編譯makefile


linux內核可加載模塊的makefile

在開發linux內核驅動時,免不了要接觸到makefile的編寫和修改,盡管網上的makefile模板一大堆,做一些簡單的修改就能用到自己的項目上,但是,對於這些基礎的東西,更應該做到知其然並知其所以然。
本篇文章中只討論linux內核模塊編譯的makefile,linux內核makefile總覽可以參考另一篇博客:linux內核makefile概覽

本篇博客參考官方文檔

linux內核使用的是kbuild編譯系統,在編譯可加載模塊時,其makefile的風格和常用的編譯C程序的makefile有所不同,盡管如此,makefile的作用總歸是給編譯器提供編譯信息。

最簡單的makefile

我們先來看看一個最簡單的makefile是怎樣的:

    obj-m+=hello.o
    all:
            make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
    clean:
            make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean

這個makefile的作用就是編譯hello.c文件,最終生成hello.ko文件。

obj-m+=hello.o

obj-m表示編譯生成可加載模塊。

相對應的,obj-y表示直接將模塊編譯進內核。

可以看到,這里並沒有輸入hello.c源文件,熟悉makefile的人應該知道,這得益於makefile的自動推導功能,需要編譯生成filename.o文件而沒有顯示地指定filename.c文件位置時,make查找filename.c是否存在,如果存在就正常編譯,如果不存在,則報錯。

obj-m+=hello.o,這條語句就是顯式地將hello.o編譯成hello.ko,而hello.o則由make的自動推導功能編譯hello.c文件生成。

all,clean

all,clean這一類的是makefile中的偽目標,偽目標並不是一個真正的編譯目標,它代表着一系列你想要執行的命令集合,通常一個makefile會對應多個操作,例如編譯,清除編譯結果,安裝,就可以使用這些偽目標來進行標記。在執行時就可以鍵入:

    make clean
    make install

等指令來完成相應的指令操作,當make后不帶參數時,默認執行第一個偽目標的操作。

make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules

標准的make指令是這樣的:make -C $KDIR M=$PWD [target],下面分別介紹每個字段的含義。

-C選項:此選項指定內核源碼的位置,make在編譯時將會進入內核源碼目錄,執行編譯,編譯完成時返回。

$KDIR:/lib/modules/$(shell uname -r)/build/,指定內核源碼的位置。

直接在目標板上編譯時,內核頭文件默認存放在/lib/modules/$(shell uname -r)/build/中,這個build/目錄是一個軟連接,鏈接到源碼頭文件的安裝位置。而內核真正的源碼庫則直接引用正在運行的內核鏡像。

當跨平台編譯時,就需要指定相應的內核源碼目錄,而不是系統中的源碼目錄,但是交叉編譯時,需不需要指定架構平台和交叉編譯工具鏈呢?我們接着往下看;

M=$(PWD):需要編譯的模塊源文件地址


[target]:modules,事實上,這是個可選選項。默認行為是將源文件編譯並生成內核模塊,即module(s),但是它還支持一下選項:

  • modules_install:安裝這個外部模塊,默認安裝地址是/lib/modules/$(uname -r)/extra/,同時可以由內建變量INSTALL_MOD_PATH指定安裝目錄
  • clean:卸載源文件目錄下編譯過程生成的文件,在上文的makefile最后一行可以看到。
  • help:幫助信息

更多選項

編譯多個源文件

hello_world總是簡單的,但是在實際開發中,就會出現更復雜的情況,這時候就需要了解更多的makefile選項:

首先,當一個.o目標文件的生成依賴多個源文件時,顯然make的自動推導規則就力不從心了(它只能根據同名推導,比如編譯filename.o,只會去查找filename.c),我們可以這樣指定:

    obj-m  += hello.o
    hello-y := a.o b.o hello_world.o

hello.o目標文件依賴於a.o,b.o,hello_world.o,那么這里的a.o和b.o如果沒有指定源文件,根據推導規則就是依賴源文件a.c,b.c,hello_world.c.
除了hello-y,同時也可以用hello-objs,實現效果是一樣的。

同時編譯多個可加載模塊

kbuild支持同時編譯多個可加載模塊,也就是生成多個.ko文件,它的格式是這樣的:

    obj-m := foo.o bar.o
    foo-y := <foo_srcs>
    bar-y := <bar_srcs>

就是這么簡單。

ifneq ($(KERNELRELEASE),)

通常,標准的makefile會寫成這樣:

    ifneq ($(KERNELRELEASE),)
    obj-m  := hello.o

    else
    KDIR ?= /lib/modules/`uname -r`/build

    all:
            $(MAKE) -C $(KDIR) M=$(PWD) modules
    clean:
            $(MAKE) -C $(KDIR) M=$(PWD) clean
    endif

為什么要添加一個ifneq,else,all條件判斷。

這得從linux內核模塊make執行的過程說起:當鍵入make時,make在當前目錄下尋找makefile並執行,KERNELRELEASE在頂層的makefile中被定義,所以在執行當前makefile時KERNELRELEASE並沒有被定義,走else分支,直接執行

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

而這條指令會進入到$(KDIR)目錄,調用頂層的makefile,在頂層makefile中定義了KERNELRELEASE變量.

在頂層makefile中會遞歸地再次調用到當前目錄下的makefile文件,這時KERNELRELEASE變量已經非空,所以執行if分支,在可加載模塊編譯列表添加hello模塊,由此將模塊編譯成可加載模塊放在當前目錄下。

歸根結底,各級子目錄中的makefile文件的作用就是先切換到頂層makefile,然后通過obj-m在可加載模塊編譯列表中添加當前模塊,kbuild就會將其編譯成可加載模塊。

如果是直接編譯整個內核源碼,就省去了else分支中進入頂層makefile的步驟。

需要注意的一個基本概念是:每一次編譯,頂層makefile都試圖遞歸地進入每個子目錄調用子目錄的makefile,只是當目標子目錄中沒有任何修改時,默認不再進行重復編譯以節省編譯時間。

這里同時解決了上面的一個疑問:既然是從頂層目錄開始編譯,那么只要頂層目錄中指定了架構(ARCH)和交叉編譯工具鏈地址(CROSS_COMPILE),各子目錄中就不再需要指定這兩個參數。

頭文件的放置

當編譯的目標模塊依賴多個頭文件時,kbuild對頭文件的放置有這樣的規定:

  • 直接放置在makefile同在的目錄下,在編譯時當前目錄會被添加到頭文件搜索目錄。

  • 放置在系統目錄,這個系統目錄是源代碼目錄中的include/linux/。

  • 與通用的makefile一樣,使用-I$(DIR)來指定,不同的是,代表編譯選項的變量是固定的,為ccflag.

      一般的用法是這樣的:
    
              ccflags-y := -I$(DIR)/include   
      kbuild就會將$(DIR)/includ目錄添加到編譯時的頭文件搜索目錄中。  
    

linux內核makefile總覽可以參考另一篇博客:linux內核makefile概覽

好了,關於linux編譯內核模塊的makefile介紹就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM