對於一個簡單的驅動模塊,以下為Makefile的經典構成:
下面逐一分析一下各個語句:
$:ls /lib/modules/
2.6.38.2 2.6.38-8-generic
ps
在ldd3 P29頁,講到Makefile時,有一個這樣的示例:
//Makefile很簡單 obj-m := hello.o
但編譯模塊時,則使用以下命令:
$:make -C ~/kernel-2.6 M='pwd' modules
其中"~/kernel-2.6"為內核源代碼樹目錄,要視自己放置位置而更改,故對應本機環境的命令是:
$:make -C /usr/src/linux-source-2.6.38 M=$PWD modules
最後效果和第一種方法完全一樣!
現在,我們對比一下這兩種方法可以知道,其實它們之間的唯一區別就是源碼目錄不一樣,分別為"/lib/modules/$(shell uname -r)/build"和"/usr/src/linux-source-2.6.38/",但如果編譯過內核就會知道,usr目錄下那個源代碼一般是我們自己下載後解壓的,而lib目錄下的則是在編譯時自動copy過去的,兩者的文件結構完全一樣,故make效果完全一致就不足為怪了。
在學習C語言的時候我們一開篇會學習hello,world的程序,我相信大家都覺得及其簡單,以至於我重復寫下面的程序,大家都覺得是多余的:
請同學們思考兩個問題:
為什麼我們必須寫一個main()函數?內核的C程序需要main嗎?
在這裡#include <stdio.h>是為了讓我們使用printf(), 實際上他們都是C語言庫的函數,他們能夠在內核程序中使用嗎?
我們先回答這兩個問題, C語言的應用程序必須要有一個main()函數,因為它是應用程序的入口,至於為什麼非要是這樣個入口,我們只有一個答案: 規定的,強制性的。C應用程序有應用程序的規定, 作為內核模塊有內核模塊的規定,所以我們在寫內核模塊框架的時候,記住這是規定就可以了。
至於第二個問題比較重要:應用程序可以調用C語言標准庫的函數,而內核程序將是絕對不可以的,如果大家還記得我們說fopen,是依賴於open的系統調用,而系統調用是有內核導出的話,那麼如果我們能夠在內核程序中使用標准函數庫,那麼就轉入了”到底是雞生蛋,還是蛋生雞”的怪圈。
下面的程序就是Linux內核模塊的標准的框架(請大家在初次學習的時候看老師是如何寫這段代碼的)。
C++代碼
這就是一個hello內核模塊的框架,如果我們要實現打印出hello,kernel, 我們只需要在修改hello_init為:
static int __init hello_init(void)
{
printk(“hello,kernel/n”);
return 0;
}
模塊的框架包含下面四個部分:
(1) 模塊在加載的時候需要執行的module_init(function),以及在module_init()中指定的function,模塊在卸載的時候執行的module_exit(function)以及在module_exit()中定義的function.如果聲明使用module_exit(),那麼此模塊將不具備動態卸載功能。
(2) 需要定義module_init()調用的初始化函數,以及在module_exit()中使用的清理函數。只有當初始化函數返回非負值(因為在內核中,負值表示操作失敗),內核模塊才能被正確的加載,否則模塊加載失敗。而清理函數返回void類型。一般情況下,初始化函數是在模塊加載的時候用來申請資源,而清理函數是在模塊卸載的時候用來釋放資源,有點類似於C++中的constructor與deconstructor.
(3) 頭文件, 對於內核模塊來講,必須要使用<linux/module.h>和<linux/init.h>。需要特別注意的是,這裡面使用了<>來包含頭文件,但很明顯這兩個頭文件都不會是標准函數的頭文件,因為,正如前面所說,內核模塊不能引用標准函數庫的函數。這裡的頭文件實際上來自於Linux的內核源代碼路徑下的$(KERNELSRC)/include目錄。
(4) 由MODULE_XXX表示的相關內容,這些都是對當前內核模塊的描述,雖然不是必須的,但是一般情況下,還是請你們填上幾項,特別是模塊的許可問題。 當然也讓你有揚名立萬的機會,同時你也該負有責任。你對模塊有更詳盡的描述將對你以後調試錯誤是有幫助的。Modinfo可以讓你更快的識別模塊,如果有需要,請參考LDD(<<Linux 設備驅動程序>>,以後均簡稱為LDD)中有關更多的MODULE_XXX的宏描述。