Windows XP Windows 7 Windows 2003 Windows Vista Windows教程綜合 Linux 系統教程
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟件教程
 Windows教程網 >> Linux系統教程 >> Linux教程 >> linux驅動開發模塊中Makefile的理解 & hello kernel模塊實例講解

linux驅動開發模塊中Makefile的理解 & hello kernel模塊實例講解

日期:2017/2/7 14:22:23      編輯:Linux教程
 

對於一個簡單的驅動模塊,以下為Makefile的經典構成:

 //------------Makefile----------------------
 obj-m := hello.o
 KERNELDIR := /lib/modules/$(shell uname -r)/build
 PWD := $(shell pwd)
 modules:
     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules #注意前面必須為tab
 modules_install:
     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install #注意前面必須為tab

下面逐一分析一下各個語句:

  • obj-m := hello.o
這句意為有一個模塊需要從目標文件hello.o中構造,構造的模塊名稱為hello.ko.
  • KERNELDIR := /lib/modules/$(shell uname -r)/build
這裡是定義一個變量KERNELDIR,並且賦值為"/lib/modules/$(shell uname -r)/build"。
這個值中要解釋的只有一點,即$(shell uname -r):

 

大家可以嘗試在terminal中輸入 $:uname -r是什麼結果,沒錯,這個命令會獲取當前內核的版本號,如“2.6.38.2”。
然後我們再查看"/lib/modules"目錄下有哪些文件:

 

$:ls /lib/modules/
結果為:

 

2.6.38.2  2.6.38-8-generic
所以"/lib/modules/$(shell uname -r)/build"的意思已經很明確了,就是當前內核的源代碼目錄
  • PWD := $(shell pwd)
有了KERNELDIR的解釋,相信這個也不多說了,就是獲取當前目錄了。總之在shell裡,$(shell xxx)就是相當於在terminal中執行 xxx命令。
  • $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
這就是編譯模塊了:首先改變目錄到-C選項指定的位置(即內核源代碼目錄),其中保存有內核的頂層makefile;M=選項讓該makefile在構造modules目標之前返回到模塊源代碼目錄;然後,modueles目標指向obj-m變量中設定的模塊;在上面的例子中,我們將該變量設置成了hello.o。(—引自ldd3 P29)  

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效果完全一致就不足為怪了。

 Hello,kernel模塊實例

在學習C語言的時候我們一開篇會學習hello,world的程序,我相信大家都覺得及其簡單,以至於我重復寫下面的程序,大家都覺得是多余的:
 

C++代碼
  1. #include <stdio.h>   
  2. int main()   
  3. {   
  4.     printf(“hello,world/n”);   
  5.     return 0;   
  6. }  

請同學們思考兩個問題:
 為什麼我們必須寫一個main()函數?內核的C程序需要main嗎?

在這裡#include <stdio.h>是為了讓我們使用printf(), 實際上他們都是C語言庫的函數,他們能夠在內核程序中使用嗎?

我們先回答這兩個問題, C語言的應用程序必須要有一個main()函數,因為它是應用程序的入口,至於為什麼非要是這樣個入口,我們只有一個答案: 規定的,強制性的。C應用程序有應用程序的規定, 作為內核模塊有內核模塊的規定,所以我們在寫內核模塊框架的時候,記住這是規定就可以了。

至於第二個問題比較重要:應用程序可以調用C語言標准庫的函數,而內核程序將是絕對不可以的,如果大家還記得我們說fopen,是依賴於open的系統調用,而系統調用是有內核導出的話,那麼如果我們能夠在內核程序中使用標准函數庫,那麼就轉入了”到底是雞生蛋,還是蛋生雞”的怪圈。

下面的程序就是Linux內核模塊的標准的框架(請大家在初次學習的時候看老師是如何寫這段代碼的)。

C++代碼
  1. #include <linux/module.h>   
  2. #include <linux/init.h>   
  3. #include <linux/kernel.h>  //使用printk,需要包含此文件   
  4. MODULE_LICENSE(“Dual BSD/GPL”);   
  5. MODULE_AUTHOR(“stephanxu@eetek”);   
  6. MODULE_DESCRIPTION(“the first kernel module”)   
  7. static int __init hello_init(void)   
  8. {   
  9.     return 0;   
  10. }   
  11. static void __exit hello_exit(void)   
  12. {   
  13. }   
  14. module_init(hello_init);   
  15. module_exit(hello_exit);  

 

 

 

這就是一個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的宏描述。

Copyright © Windows教程網 All Rights Reserved