驅動(dòng)程序是專(zhuān)用于控制和管理特定硬件設備的軟件,因此也被稱(chēng)作設備驅動(dòng)程序。從操作系統的角度來(lái)看,它可以位于內核空間(以特權模式運行),也可以位于用戶(hù)空間(具有較低的權限) 。
對于 Linux 驅動(dòng)程序來(lái)說(shuō),其運行在內核空間,把硬件功能提供給用戶(hù)程序。
本篇文章主要介紹Linux驅動(dòng)程序的一些基礎知識。后面的文章再逐步展開(kāi)。
內核空間和用戶(hù)空間的概念有點(diǎn)抽象,主要涉及內存的訪(fǎng)問(wèn)權限。內核是有特權的,而用戶(hù)應用程序則是受限制的。
內核空間
內核駐留和運行的地址空間。
內核內存受訪(fǎng)問(wèn)標志保護,只能由內核訪(fǎng)問(wèn),用戶(hù)應用程不能訪(fǎng)問(wèn)。另一方面,內核可以訪(fǎng)問(wèn)整個(gè)系統內存,因為它在系統上以更高的優(yōu)先級運行。在內核模式下,CPU可以訪(fǎng)問(wèn)整個(gè)內存(內核空間和用戶(hù)空間)
用戶(hù)空間
用戶(hù)程序運行的地址空間。
在用戶(hù)模式下,CPU只能訪(fǎng)問(wèn)標有用戶(hù)空間訪(fǎng)問(wèn)權限的內存。用戶(hù)應用程序運行到內核空間的唯一方法是通過(guò)系統調用。
當進(jìn)程執行系統調用時(shí),軟件中斷被發(fā)送到內核,這將打開(kāi)特權模式,以便該進(jìn)程可以在內核空間中運行。系統調用返回時(shí),內核關(guān)閉特權模式,進(jìn)程再次受限。
Linux內核可以在運行時(shí)擴展。當系統運行時(shí),我們可以向內核添加、刪除功能。
可以在運行時(shí)添加到內核中的代碼被稱(chēng)為“模塊”。內核模塊是即插即用的,一旦插入就可以使用。
模塊要運行,應該先把它加載到內核,可以用 insmod 或 modprobe 來(lái)實(shí)現,前者需要指定模塊路徑作為參數,這是開(kāi)發(fā)期間的首選;后者更智能化,是生產(chǎn)系統中的首選。
常用的模塊卸載命令是 rmmod,使用該命令時(shí),應該把要卸載的模塊名作為參數向其傳遞。當卸載某個(gè)模塊時(shí),不會(huì )有其他影響,則會(huì )直接卸載;若有不良影響,內核會(huì )阻止這次卸載。
rmmod mymodule或者使用下邊的指令
Linux系統的模塊有三種基本類(lèi)型:
字符模塊
塊模塊
網(wǎng)絡(luò )模塊
對應的設備設備驅動(dòng)程序:
字符設備驅動(dòng)
塊設備驅動(dòng)
網(wǎng)絡(luò )設備驅動(dòng)
字符設備是個(gè)能夠像字節流一樣被訪(fǎng)問(wèn)的設備,由字符設備驅動(dòng)程序來(lái)實(shí)現。
塊設備每次只能傳輸一個(gè)或者多個(gè)完整的塊,每塊包含512字節(或者2的更高次冪字節的數據)。
網(wǎng)絡(luò )接口由內核中的網(wǎng)絡(luò )子系統驅動(dòng),負責發(fā)送和接收數據包。網(wǎng)絡(luò )驅動(dòng)程序不需要知道各個(gè)連接的相關(guān)信息,它只負責處理數據包即可。
當然還有其他劃分驅動(dòng)程序模塊的方法,此處不再贅述。
Linux 驅動(dòng)程序是有固定框架的,我們按照既定的框架,填寫(xiě)內容即可。
先看一個(gè)簡(jiǎn)單的內核模塊程序 helloworld.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
/* 模塊入口點(diǎn)函數 */
static int helloworld_init(void)
{
pr_info('Hello world!\n');
return 0;
}
/* 模塊出口點(diǎn)函數 */
static void helloworld_exit(void)
{
pr_info('End of the world\n');
}
/* 指定函數用途 */
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_AUTHOR('zsky');
MODULE_LICENSE('GPL');內核驅動(dòng)程序與用戶(hù)空間的程序是有很大區別的。
內核模塊驅動(dòng)程序有入口點(diǎn)和出口點(diǎn),函數名字可以任意。用戶(hù)程序的入口函數名稱(chēng)一般為 main()。
對于內核模塊程序來(lái)說(shuō),需要開(kāi)發(fā)人員指定入點(diǎn)和出點(diǎn)函數。在上例中,module_init()用于聲明模塊加載(使用 insmod 或 modprobe)時(shí)應該調用的函數為 helloworld_init,入口函數中要完成的操作是定義模塊的行為。
module_exit() 用于聲明模塊卸載(使用 rmmod )時(shí)應該調用的函數為 helloworld_exit。
模塊加載或者卸載后,init 函數或者 exit 函數立即運行一次。
在編寫(xiě)驅動(dòng)程序的時(shí)候,需要包含很多頭文件,以便獲取函數、數據類(lèi)型、變量的定義。有幾個(gè)頭文件是專(zhuān)門(mén)用于模塊的:
module.h包含可裝載模塊需要的大量符號和函數的定義。init.h 用于指定入口函數和出口函數。
模塊信息
內核模塊使用其 .modinfo 部分來(lái)存儲關(guān)于模塊的信息,所有MODULE_*宏都用參數傳遞的值更新這部分的內容 。
其中一些宏是 MODULE_DESCRIPTION()、MODULE_AUTHOR() 和 MODULE_LICENSE()。
MODULE_LICENSE() 告訴內核模塊采用何種許可,他對模塊行為有影響,如果與指定的許可不兼容將導致內核模塊被污染。
MODULE_AUTHOR() 用于聲明模塊的作者。
MODULE_DESCRIPTION() 簡(jiǎn)要描述模塊的功能。
在模塊函數處理過(guò)程中,一定要檢測返回值,確保所有的請求操作已經(jīng)真正成功。
當遇到錯誤時(shí),必須撤銷(xiāo)在這個(gè)錯誤發(fā)生之前的所有設置。通常的做法是使用goto語(yǔ)句。
ptr = kmalloc(sizeof (device_t));
if(!ptr)
{
ret = -ENOMEM
goto err_alloc;
}
ev = init(&ptr);
if(dev)
{
ret = -EIO
goto err_init;
}
eturn 0;
err_init:
free(ptr);
err_alloc:
return ret;若模塊裝載過(guò)程中出錯,要將出錯之前的任何注冊工作全部撤銷(xiāo),否則內核會(huì )處于一種不穩定的狀態(tài),因為內核中包含了一些指向并不存在的代碼內部指針。
錯誤有時(shí)會(huì )跨越內核空間,傳播到用戶(hù)空間。如果返回的錯誤是對系統調用(open、read、ioctl、mmap)的響應,則該值將自動(dòng)賦給用戶(hù)空間 errno 全局變量,在該變量上調用 strerror(errno) 可以將錯誤轉換為可讀字符串。
當返回指針的函數返回錯誤時(shí),通常返回的是NULL 指針。而去檢查為什么會(huì )返回空指針是沒(méi)有任何意義的,因為無(wú)法準確了解為什么會(huì )返回空指針。為此,內核提供了3個(gè)函數 ERR_PTR、IS_ERR 和 PTR_ERR:
ERR_PTR 函數實(shí)際上把錯誤值作為指針?lè )祷?。假若函數在內存申請失敗后要執行語(yǔ)句 return -ENOMEM,則必須改為這樣的語(yǔ)句:return ERR_PTR (-ENOMEM);。
IS_ERR 函數用于檢查返回值是否是指針錯誤:if(IS_ERR(foo))。
PTR_ERR 函數返回實(shí)際錯誤代碼:return PTR_ERR(foo);。
消息打印--printk()
不同于用戶(hù)空間的 printf()函數。printk()是在內核空間使用的,其作用和在用戶(hù)空間使用 printf() 一樣,執行 dmesg 命令可以顯示 printk() 寫(xiě)入的信息。
根據所打印消息的重要性不同,可以選用 include/linux/kern_levels.h 中定義的八個(gè)級別的日志消息,每個(gè)級別對應iyge字符串格式的數字,其優(yōu)先級與該數字的值成反比:
#define KERN_SOH '\001' /* ASCII頭開(kāi)始 */
#define KERN_SOH_ASCII '\001'
#define KERN_EMERG KERN_SOH '0' /* 系統不可用*/
#define KERN_ALERT KERN_SOH '1' /* 必須立即采取行動(dòng)*/
#define KERN_CRIT KERN_SOH '2' /* 重要條件*/
#define KERN_ERR KERN_SOH '3' /* 錯誤條件*/
#define KERN_WARNING KERN_SOH '4' /* 警報條件*/
#define KERN_NOTICE KERN_SOH '5' /* 正常但重要的情況*/
#define KERN_INFO KERN_SOH '6' /* 信息 */
#define KERN_DEBUG KERN_SOH '7' /* 調試級別消息 */舉例:
實(shí)際上可以使用以下宏,其名稱(chēng)更有意義,它們是對前面所定義內容的包裝—— pr_emerg、pr_alert、pr_crit、pr_err、pr_warning、pr_notice、pr_info和 pr_debug。
printk() 的實(shí)現是這樣的:調用它時(shí),內核會(huì )將消息日志級別與當前控制臺的日志級別進(jìn)行比較;如果前者比后者更高(值更低),則消息會(huì )立即打印到控制臺。
像用戶(hù)程序一樣,內核模塊也可以接收命令行參數。這樣能夠根據給定的參數動(dòng)態(tài)地改變模塊的行為,開(kāi)發(fā)者不必在測試/調試期間無(wú)限期地修改/編譯模塊。
為了對此進(jìn)行設置,首先應該聲明用于保存命令行參數值的變量,并在每個(gè)變量上使用 module_param() 宏:
module_param(name, type, perm);name:用作參數的變量的名稱(chēng)。
type:參數的類(lèi)型(bool、charp、byte、short、ushort、int、uint、long、ulong),其中 charp 代表字符指針。
當使用模塊參數時(shí),應該用 MODULE_PARM_DESC 描述每個(gè)參數。這個(gè)宏將把每個(gè)參數的描述填充到模塊信息部分。
舉例:
要在加載該模塊時(shí)提供參數,請執行以下操作:
# insmod hellomodule-params.ko
mystring='packtpub' myint=15 myArray=1,2,3在加載模塊之前,執行modinfo可以顯示該模塊支 持的參數說(shuō)明:
好了,感謝閱讀。加油~

聯(lián)系客服