linux裝置驅動第二篇:一個簡單hello world驅動如何?,linuxhello
上一篇介紹了linux驅動的概念,以及linux下裝置驅動的基本分類情況及其各個分類的依據和差異,這一篇我們來描述如何寫一個類似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。
下面再來介紹幾個重要的概念,這些概念可以先做一個瞭解,後續的文章中還會提到。
核心空間和使用者空間
linux系統分為兩個層級。核心運行在最進階別,可以進行所有的操作。而應用程式運行在最低層級,處理器控制著對硬體的直接存取以及對記憶體的非授權訪問。核心空間和使用者空間不僅有不同的優先順序等級,而且有不同的記憶體映射,有各自的地址空間。詳見記憶體管理。
應用程式只能通過系統調用或中斷從使用者空間切換到核心空間,其中系統調用是非強制中斷(0x80號中斷)。執行系統調用的系統代碼運行在進程上下文中,它代表調用進程執行操作,因此能夠訪問進程地址空間的所有資料。而處理硬體中斷的核心代碼和進程是非同步,與任何一個特定進程無關。
核心中的並發
核心編程區別於常見應用程式編程的地方在於對並發的處理。大部分應用程式除多線程外,通常是順序執行的,不需要關心由於其他事情的發生而改變它的運行環境。核心代碼不是這樣,同一時刻,可能有多個進程使用訪問同一個模組。
核心編程要考慮並發問題的原因:1.linux是通常正在運行多個並發進程,並且可能有多個進程同時使用我們的驅動程式。2.大多數裝置能夠中斷處理器,而中斷處理常式非同步進行,而且可能在驅動程式正試圖處理其它任務時被調用。3.一些類似核心定時器的代碼在非同步運行。4.運行在對稱式多處理器上(SMP),不止一個cpu在運行驅動程式。5.核心代碼是可搶佔的。
當前進程
核心代碼可通過訪問全域項current來獲得當前進程。current指標指向當前正在啟動並執行進程。在open、read、等系統調用的執行過程中,當前進程指的是調用這些系統調用的進程。核心代碼可以通過current指標獲得與當前進程相關的資訊。
核心中帶“__”的函數:核心API函數具有這種名稱的,通常都是一些介面的底層函數,應該謹慎使用。實質上,這裡的雙底線就是要告訴程式員:謹慎調用,否則後果自負。以__init為例,__init表明該函數僅在初始化期間使用。在模組被裝載之後,模組裝載器就會將初始化函數扔掉,這樣可以將函數佔用的記憶體釋放出來,已做它用。注意,不要在結束初始化之後仍要使用的函數(或者資料結構)上使用__init、__initdata標記。這裡摘抄網上的一段總結,如下。
__init, __initdata等屬性標誌,是要把這種屬性的代碼放入目標檔案的.init.text節,資料放入.init.data節──這一過程是通過編譯核心時為相關目標平台提供了xxx.lds連結指令碼來指導ld完成的。
對編譯成module的代碼和資料來說,當模組載入時,__init屬性的函數就被執行;
對靜態編入核心的代碼和資料來說,當核心引導時,do_basic_setup()函數調用do_initcalls()函數,後者負責所有.init節函數的執行。
在初始化完成後,用這些關鍵字標識的函數或資料所佔的記憶體會被釋放掉。
1) 所有標識為__init的函數在連結的時候都放在.init.text這個區段內,在這個區段中,函數的擺放順序是和連結的順序有關的,是不確定的。
2) 所有的__init函數在區段.initcall.init中還儲存了一份函數指標,在初始化時核心會通過這些函數指標調用這些__init函數指標,並在整個初始化完成後,釋放整個init區段(包括.init.text,.initcall.init等),注意,這些函數在核心初始化過程中的調用順序只和這裡的函數指標的順序有關,和1)中所述的這些函數本身在.init.text區段中的順序無關。
下面我們來看一個驅動程式的hello world程式是如何?的:
#include <linux/init.h>#include <linux/module.h>MODULE_LICENSE("Dual BSD/GPL");static int hello_init(void){ printk(KERN_ALERT "Hello, world\n"); return 0;}static void hello_exit(void){ printk(KERN_ALERT "Goodbye, cruel world\n");}module_init(hello_init);module_exit(hello_exit);
核心模組的編譯與應用程式的編譯有些區別,此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的時候執行,這個是模組的初始化函數,另一個是hello_exit,在rmmod的時候執行,是模組卸載時要執行的函數。此模組的唯一功能就是在insmod的時候輸出Hello,world,在rmmod的時候輸出Goodbye,cruel world。
在編寫應用程式時,我們一般都是由多個源檔案組成的,這個時候編譯肯定就不能繼續使用命令列編譯了,就要使用到Makefile。同樣,驅動模組的編譯也需要使用的makefile,下面就是一個在編譯含有多個源碼檔案的驅動模組時可以參考的Makefile檔案。
ifndef CROSS_COMPILEexport CROSS_COMPILE ?=arm-none-linux-gnueabi-endifARCH ?= armSRC_DIR := /home/XXX/XXXOBJ_DIR := $(SRC_DIR)/objPWD := $(shell pwd)LINUX_SRC ?= /home/XXX/kernelCFG_INC = -I$(SRC_DIR) \-I$(DIR_A) \-I$(DIR_B)CFG_FLAGS += -O2EXTRA_CFLAGS += $(C_FLAGS) $(CFG_INC) $(CFG_INC)obj-m := mymodule.omymodule-objs := a.omymodule-objs += b.omymodule-objs += c.omodules:@make ARCH=$(ARCH) -C $(LINUX_SRC) M=$(PWD) modulesclean:@echo "cleaning..."rm -f mymodule.ko mymodule.o mymodule.mod.* modules.order Module.symversrm -f $(mymodule-objs)
第一時間獲得部落格更新提醒,以及更多技術資訊分享,歡迎關注個人公眾平台:程式員互動聯盟(coder_online)
1.直接幫你解答linux裝置驅動疑問點
2.第一時間獲得業內十多個領域技術文章
3.針對文章內疑點提出問題,第一時間回複你,幫你耐心解答
4.讓你和原創作者成為很好的朋友,拓展自己的人脈資源
掃一掃下方二維碼或搜尋號coder_online即可關注,我們可以線上交流。