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