驅動程式英文全稱Device Driver,也稱作裝置驅動程式。
在現代電腦體繫結構中,作業系統並不直接於硬體打交道,而是通過驅動程式於硬體通訊。
在同一台電腦上,儘管裝置是相同的,但是由於作業系統不同,驅動程式是有很大差別的。但是,無論什麼都是相似的,可以歸納為下面三點:
這是驅動程式最基本的功能,初始化通過匯流排識別裝置,訪問裝置寄存器,按照需求配置裝置地連接埠,設定中斷等。
裝置驅動程式向作業系統提供了一類裝置通用的軟體介面,如硬碟裝置向作業系統提供了讀寫磁碟塊、定址等介面,無論是哪種品牌的硬碟驅動向作業系統提供的介面都是一致的。
現代電腦的處理能力越來越強,作業系統有一類虛擬設備驅動,可以類比真實裝置的操作,如虛擬印表機驅動向作業系統提供了印表機的介面,在系統沒有印表機制情況下仍然可以執行列印操作。
Linux核心模組是一種
通過核心模組可以擴充核心的功能,通常核心模組被用於裝置驅動、檔案系統等。如果沒有核心模組,需要向核心添加功能就需要修改代碼、重新編譯核心、安裝新核心等步驟,不僅繁瑣而且容易保出錯,不易於調試。
核心模組是一個應用程式,但是與普通應用程式有所不同,區別在於:
核心模組運行在核心空間,可以訪問系統的幾乎所有的軟硬體資源;普通應用程式運行在使用者空間,可以訪問的資源受到限制。這也是核心模組與普通應用程式最主要的區別。由於核心模組可以獲得與作業系統核心相同的許可權,因此在編程的時候應該格外注意,可能在使用者空間看到的一點小錯誤在核心空間就會導致系統崩潰。
普通應用程式為了完成某個特定的目標,功能定位明確;核心模組是為其他的核心模組以及應用程式服務的,通常提供的是通用的功能。
核心模組只能調用核心提供的函數,訪問其他的函數會導致運行異常;普通應用程式可能調用自身以外的函數,只要能正確串連就有運行。
程式的並發性。
由於代碼的可重新進入特性,必須考慮到資料結構在多線程環境下不被其他線程破壞,對於共用資料更是應該採用加鎖的方法保護。驅動程式員的通常錯誤是假定某段代碼不會出現並發,導致資料被破壞而很難於調試。
應用程式使用虛擬記憶體,有一個巨大的地址空間,在應用程式中可以分配大塊的記憶體。核心模組可以供使用的記憶體非常小,最小可能小到一個記憶體頁面(4096位元組)。在編寫核心模組代碼的時候要注意記憶體的分配和使用。
因此,一個核心模組至少包括載入和卸載兩個函數。在linux 2.6系列核心中,通過module_init()宏可以在載入核心模組的時候調用核心模組的初始化函數,module_exit()宏可以在卸載核心模組的時候調用核心模組的卸載函數。
static int __init init_func(void); //初始化函數static void __exit exit_func(void); //清除函數
這兩個函數的名稱可以由使用者自己定義,但是必須使用規定的傳回值和參數格式。
kmod模組與使用者態的kmodule模組通訊,擷取核心模組的資訊。
通過insmod命令和modprobe命令都可以載入一個核心模組。
命令載入核心模組的時候不檢查核心模組的符號是否已經在核心中定義。
不僅檢查核心模組符號表,而且還會檢查模組的依賴關係。
另外,linux核心可以在需要載入某個模組的時候,通過kmod機制通知使用者態的modprobe載入模組。
通常,核心輸出符號被儲存在核心模組列表第一個模組結構裡。insmod命令把核心模組載入到虛擬記憶體,利用核心輸出符號表來修改被載入模組中沒有解析的核心功能的資源地址。
因為核心模組是工作在核心態的,訪問使用者態的資源需要做地址轉換。申請好空間後,insmod把核心模組複製到新空間,然後把模組加入到核心模組列表的尾部,並且設定模組標誌為UNINTIALIZED,表示模組還沒有被引用。
一個核心模組被其他模組引用的時候,自身的引用計數器會增加1.當卸載模組的時候,需要判斷模組引用計數器值是否為0,如果為0才能卸載模組,否則只能把模組計數減1.
超級使用者使用rmmod命令可以卸載指定的模組。
此外,核心kmod機制會定期檢查每個模組的引用計數器,如果某個模組的引用計數器值為0,kmod會卸載該模組。
還是以最經典的"Hello World !"為例子吧。
/* 核心模組: ModuleHelloWorld.c */#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mystety"); /* init function */static int __init hello_init(void) { printk(KERN_ALERT "(init)Hello,World!\n"); return 0;}/* exit function */static void __exit hello_exit(void) { printk(KERN_ALERT "(exit)Bye-bye,Mystery!\n");}module_init(hello_init); module_exit(hello_exit);
編譯核心模組需要建立一個Makefile,主要目的是使用核心標頭檔,因為核心模組對核心版本有很強的依賴關係。
❶我用的系統是Ubuntu的,
sudo apt-get install linux-source
❷安裝核心代碼完畢後,在ModuleHelloWorld.c同一目錄下
ifneq ($(KERNELRELEASE),) obj-m := ModuleHelloWorld.oelse KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd)default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendif
程式
,通過設定KERNELDIR和PWD環境變數,然後通過核心指令碼編譯當前檔案,產生核心模組檔案。
❸Makefile建立完畢後,在shell下輸入""斷行符號編譯核心模組。
❹編譯結束後,產生ModuleHelloWorld.ko核心模組,
在載入過程中可以看到hello_init()函數的輸出資訊。
❺載入核心模組成功後,可以使用
卸載模組的時候,核心會調用核心的卸載函數,輸出hello_exit()函數的內容。
模組卸載以後,使用,如果沒有任何輸出,表示HelloWorld核心模組已經被成功卸載。
lsmod | grep ModuleHelloWorld
驅動程式常需要在載入的時候提供一個或者多個參數,內模組提供了設定參數的能力。
通過odule_param()宏可以為核心模組設定一個參數。
定義如下:
其中,;
#include <linux/init.h> #include <linux/module.h>#include <linux/kernel.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("Mystety");static int initValue = 0; //模組參數 initValue = <int value>static char *initName = NULL; //模組參數 initName = <char*>module_param(initValue, int, S_IRUGO);module_param(initName, charp, S_IRUGO);/* init function */static int __init hello_init(void){ printk(KERN_ALERT"initValue = %d initName = %s \n",initValue,initName); //列印參數值 printk(KERN_ALERT "(init)Hello,World!\n"); return 0;}/* exit function */static void __exit hello_exit(void){ printk(KERN_ALERT "(exit)Bye-bye,Mystery!\n");} module_init(hello_init); module_exit(hello_exit);
在原來的代碼中,重新編譯,帶參數載入模組。
從輸出結果可以看出,核心模組的參數被正確傳遞到了程式中。
驅動其實也沒有傳說中的難,
本文出自 “成鵬致遠” 部落格,請務必保留此出處http://infohacker.blog.51cto.com/6751239/1218461