上一篇文章介紹了 Linux 驅(qū)動編程需要了解的基礎(chǔ)知識:
這篇文章來介紹一下,如何構(gòu)建一個驅(qū)動模塊。構(gòu)建一個模塊,可以在兩個地方完成:
內(nèi)核樹外部
除此之外,還需要編寫相應(yīng)的構(gòu)建腳本文件 makefile。
接下來,我們逐步進行介紹。
makefile 是用來執(zhí)行一組操作的特殊文件,其中最重要的操作是程序的編譯。專用工具 make 用于解析makefile。
在說明整個make文件之前,先介紹一下 obj-<X> kbuild 變量。<X> 可以取值為 y、m、n或者空白。
例如:
告訴 kbuild 在當(dāng)前目錄中有一個名為 mymodule.o 的對象。mymodule.o 將從 mymodule.c 或 mymodule.S 構(gòu)建。<X> 的值決定了如何構(gòu)建以及是否構(gòu)建或鏈接 mymodule.o
如果 <X> 設(shè)置為 n,則使用變量 obj-n,不會構(gòu)建 mymodule.o
如果后邊跟著是某個目錄,例如
obj-<X> += onedir/
kbuild 應(yīng)該進入 onedir 目錄,查找其中所有的 makefile 并處理它們,從而決定應(yīng)該構(gòu)建哪些對象。
一份完整的模塊構(gòu)建Makefile 示例:
obj-m := hellowolrd.o
解析,obj-m
列出要構(gòu)建的模塊。對于每一個 <filename> .o,進行系統(tǒng)構(gòu)建時會查找<filename> .c
KERNELDIR := /lib/modules/$(shell uname -r)/build
解析,KERNELDIR
是預(yù)構(gòu)建的內(nèi)核源碼的位置。構(gòu)建任何模塊都需要預(yù)構(gòu)建內(nèi)核。 -C
要求 make 在讀取 makefile 或執(zhí)行其他任何操作之前先更改到指定的目錄。
M=$(shell pwd)
解析,這與內(nèi)核構(gòu)建系統(tǒng)相關(guān)。內(nèi)核 makefile 使用這個變量來定位要構(gòu)建的外部模塊的目錄,.c 文件應(yīng)該被放置在這。
(MAKE) -C $(KERNELDIR ) M=$(shell pwd) modules;
構(gòu)建驅(qū)動模塊的規(guī)則
在內(nèi)核樹中構(gòu)建驅(qū)動程序,需要把驅(qū)動程序的代碼文件放在特定的目錄中。驅(qū)動程序中的每個子目錄都有 makefile 和 kconfig。
例如,驅(qū)動文件 mychardev.c 為字符驅(qū)動程序源碼,則應(yīng)該把他放在內(nèi)核源碼的 drivers/char 目錄中。
一個 kconfig 的示例文件如下:
config PACKT_MYCDEV
tristate 'Our packtpub special Characterdriver'
default m
help
Say Y here if you want to support the/dev/mycdev device.
The /dev/mycdev device is used to access packtpub.
同時,在這個目錄下的 makefile 文件中添加一下語句:
注意,.o 文件名稱必須與 .c 文件名完全一致。
配置完成后,可以分別使用 make 和 make modules 構(gòu)建內(nèi)核和模塊。
內(nèi)核源碼樹中包含的模塊安裝在 /lib/modules/$(KERNELRELEASE)/kernel/
中。在Linux系統(tǒng)上,它是/lib/modules/$(uname -r)/kernel/
在構(gòu)建外部模塊之前,需要有一個完整的、預(yù)編譯的內(nèi)核源代碼樹。內(nèi)核源碼樹版本必須與將加載和使用模塊的內(nèi)核相同。
有兩種方法可以獲得預(yù)構(gòu)建的內(nèi)核版本:
自己構(gòu)建
sudo apt-get update
sudo apt-get install linux-headers-$(uname -r)
這將只安裝頭文件,而不是整個源代碼樹。
頭文件將被安裝在 /usr/src/linux-headers-$(uname -r)
目錄下 。
有一個符號鏈接 /lib/modules/$(uname-r)/build
,指向前面安裝的頭文件,這應(yīng)該是在 makefile 中指定為內(nèi)核目錄的路徑。
以上內(nèi)容準(zhǔn)備完成之后,就可以進行構(gòu)建驅(qū)動模塊。
處理完 makefile 后,只需要切換到源碼目錄并運行 make 命令,即可開始構(gòu)建模塊。
一個簡單模塊程序 helloworld.c
構(gòu)建腳本文件 Makefile
KERNELDIR ?= /lib/modules/`uname -r`/build
obj-m:= helloworld.o
all :
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules;
clean:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) clean;
rm -f *.ko;
開始構(gòu)建:
構(gòu)建完成后,在當(dāng)先目錄下會生成一些文件,其中 helloworld.ko
,為最終生成的內(nèi)核模塊。
交叉編譯
上面的例子使用的是本地構(gòu)建,在 x86 機器上為x86 機器編譯。交叉編譯怎么實現(xiàn)?
這個過程是在機器 A(稱為宿主機)上編譯,該代碼要運行在機器 B(稱為目標(biāo)機)上;宿主機和目標(biāo)機具有不同的體系結(jié)構(gòu)。
常見的交叉編譯是在 x86 機器上構(gòu)建的代碼要運行在 ARM 架構(gòu)上。交叉編譯內(nèi)核模塊時,構(gòu)建 makefile 需要指定兩個變量:ARCH 和 CROSS_COMPILE,它們分別表示目標(biāo)體系結(jié)構(gòu)和編譯器的前綴名稱。
因此,內(nèi)核模塊本地編譯和交叉編譯之間的差別在于 makefile 構(gòu)建文件。
另外,還需要一份目標(biāo)機器正在使用的內(nèi)核源碼,編譯模塊的時候需要用到。
模塊構(gòu)建完成后,可以通過 insmod
指令進行裝載。注意裝載和卸載需要 root 訪問權(quán)限,可以在模塊加載指令之前加上 sudo
$ sudo insmod helloworld.ko
指令執(zhí)行完,看不到任何信息。
加載內(nèi)核模塊,模塊的打印信息需要通過 dmsg
指令查看??梢钥吹饺肟诤瘮?shù) helloworld_init()
打印的 Hello world!
卸載模塊,通過 rmmod
指令
同樣出口函數(shù)打印的信息,dmesg
指令查看。
另外,用 modinfo
查看模塊信息如下:
$ modinfo helloworld.ko
filename: /home/user/learn/drivers/chap2/helloworld.ko
license: GPL
author: zsky
srcversion: 6EFA6AC1502C67E96C09216
depends:
retpoline: Y
name: helloworld
vermagic: 5.15.0-73-generic SMP mod_unload modversions