http://blog.csdn.net/haomcu/article/details/10364329
2013
上一篇介紹了Linux驅動的概念,以及l(fā)inux下設備驅動的基本分類情況及其各個分類的依據和差異,這一篇我們來描述如何寫一個類似hello world的簡單測試驅動程序。而這個驅動的唯一功能就是輸出hello world。
在編寫具體的實例之前,我們先來了解下linux內核下調試程序的一個重要函數printk以及幾個重要概念。
printk類似C語言的printf,是內核中輸出打印信息的函數。以后驅動調試中的重要性不言而喻,下面先做一個簡單介紹。
printk的級別
日志級別一共有8個級別,printk的日志級別定義如下(在include/linux/kernel.h中):
#define KERN_EMERG 0/*緊急事件消息,系統崩潰之前提示,表示系統不可用*/
#define KERN_ALERT 1/*報告消息,表示必須立即采取措施*/
#define KERN_CRIT 2/*臨界條件,通常涉及嚴重的硬件或軟件操作失敗*/
#define KERN_ERR 3/*錯誤條件,驅動程序常用KERN_ERR來報告硬件的錯誤*/
#define KERN_WARNING 4/*警告條件,對可能出現問題的情況進行警告*/
#define KERN_NOTICE 5/*正常但又重要的條件,用于提醒*/
#define KERN_INFO 6/*提示信息,如驅動程序啟動時,打印硬件信息*/
#define KERN_DEBUG 7/*調試級別的消息*/
沒有指定日志級別的printk語句默認采用的級別是:DEFAULT_ MESSAGE_LOGLEVEL(這個默認級別一般為<4>,即與KERN_WARNING在一個級別上),其定義在kernel/printk.c中可以找到。在驅動調試過程中打開所有日志信息可使用echo 7 > /proc/sys/kernel/printk,相對應關閉日志使用echo 0 > /proc/sys/kernel/printk。
下面再來介紹幾個重要的概念,這些概念可以先做一個了解,后續(xù)的文章中還會提到。
內核空間和用戶空間
linux系統分為兩個級別。內核運行在最高級別,可以進行所有的操作。而應用程序運行在最低級別,處理器控制著對硬件的直接訪問以及對內存的非授權訪問。內核空間和用戶空間不僅有不同的優(yōu)先級等級,而且有不同的內存映射,有各自的地址空間。詳見內存管理。
應用程序只能通過系統調用或中斷從用戶空間切換到內核空間,其中系統調用是軟中斷(0x80號中斷)。執(zhí)行系統調用的系統代碼運行在進程上下文中,它代表調用進程執(zhí)行操作,因此能夠訪問進程地址空間的所有數據。而處理硬件中斷的內核代碼和進程是異步的,與任何一個特定進程無關。
內核中的并發(fā)
內核編程區(qū)別于常見應用程序編程的地方在于對并發(fā)的處理。大部分應用程序除多線程外,通常是順序執(zhí)行的,不需要關心由于其他事情的發(fā)生而改變它的運行環(huán)境。內核代碼不是這樣,同一時刻,可能有多個進程使用訪問同一個模塊。
內核編程要考慮并發(fā)問題的原因:1.linux是通常正在運行多個并發(fā)進程,并且可能有多個進程同時使用我們的驅動程序。2.大多數設備能夠中斷處理器,而中斷處理程序異步進行,而且可能在驅動程序正試圖處理其它任務時被調用。3.一些類似內核定時器的代碼在異步運行。4.運行在對稱多處理器上(SMP),不止一個cpu在運行驅動程序。5.內核代碼是可搶占的。
當前進程
內核代碼可通過訪問全局項current來獲得當前進程。current指針指向當前正在運行的進程。在open、read、等系統調用的執(zhí)行過程中,當前進程指的是調用這些系統調用的進程。內核代碼可以通過current指針獲得與當前進程相關的信息。
內核中帶“__”的函數:內核API函數具有這種名稱的,通常都是一些接口的底層函數,應該謹慎使用。實質上,這里的雙下劃線就是要告訴程序員:謹慎調用,否則后果自負。以__init為例,__init表明該函數僅在初始化期間使用。在模塊被裝載之后,模塊裝載器就會將初始化函數扔掉,這樣可以將函數占用的內存釋放出來,已做它用。注意,不要在結束初始化之后仍要使用的函數(或者數據結構)上使用__init、__initdata標記。這里摘抄網上的一段總結,如下。
__init, __initdata等屬性標志,是要把這種屬性的代碼放入目標文件的.init.text節(jié),數據放入.init.data節(jié)──這一過程是通過編譯內核時為相關目標平臺提供了xxx.lds鏈接腳本來指導ld完成的。
對編譯成module的代碼和數據來說,當模塊加載時,__init屬性的函數就被執(zhí)行;
對靜態(tài)編入內核的代碼和數據來說,當內核引導時,do_basic_setup()函數調用do_initcalls()函數,后者負責所有.init節(jié)函數的執(zhí)行。
在初始化完成后,用這些關鍵字標識的函數或數據所占的內存會被釋放掉。
1) 所有標識為__init的函數在鏈接的時候都放在.init.text這個區(qū)段內,在這個區(qū)段中,函數的擺放順序是和鏈接的順序有關的,是不確定的。
2) 所有的__init函數在區(qū)段.initcall.init中還保存了一份函數指針,在初始化時內核會通過這些函數指針調用這些__init函數指針,并在整個初始化完成后,釋放整個init區(qū)段(包括.init.text,.initcall.init等),注意,這些函數在內核初始化過程中的調用順序只和這里的函數指針的順序有關,和1)中所述的這些函數本身在.init.text區(qū)段中的順序無關。
下面我們來看一個驅動程序的hello world程序是如何實現的:
內核模塊的編譯與應用程序的編譯有些區(qū)別,此hello world模塊的編譯命令為:
make -C /xxx/xxx/kernel_src/ M=$(PWD) modules
其中/xxx/xxx/kernel_src/ 為已經配置編譯過的內核源碼路徑,ubuntu下一般在/lib/modules/$(shell uname -r)/build目錄下。
此函數只有兩個函數,一個是hello_init,在insmod的時候執(zhí)行,這個是模塊的初始化函數,另一個是hello_exit,在rmmod的時候執(zhí)行,是模塊卸載時要執(zhí)行的函數。此模塊的唯一功能就是在insmod的時候輸出Hello,world,在rmmod的時候輸出Goodbye,cruel world。
在編寫應用程序時,我們一般都是由多個源文件組成的,這個時候編譯肯定就不能繼續(xù)使用命令行編譯了,就要使用到Makefile。同樣,驅動模塊的編譯也需要使用的makefile,下面就是一個在編譯含有多個源碼文件的驅動模塊時可以參考的Makefile文件。Makefile模板