linux設備驅動第二篇:一個簡單hello world驅動如何實現

上一篇介紹了linux驅動的概念,以及linux下設備驅動的基本分類狀況及其各個分類的依據和差別,這一篇咱們來描述如何寫一個相似hello world的簡單測試驅動程序。而這個驅動的惟一功能就是輸出hello world。linux

在編寫具體的實例以前,咱們先來了解下linux內核下調試程序的一個重要函數printk以及幾個重要概念。程序員

printk相似c語言的printf,是內核中輸出打印信息的函數。之後驅動調試中的重要性不言而喻,下面先作一個簡單介紹。shell

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/*調試級別的消息*/ubuntu

沒有指定日誌級別的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程序是如何實現的:

[cpp] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. #include <linux/init.h>  

  2. #include <linux/module.h>  

  3. MODULE_LICENSE("Dual BSD/GPL");  

  4.   

  5. static int hello_init(void)  

  6. {  

  7.         printk(KERN_ALERT "Hello, world\n");  

  8.         return 0;  

  9. }  

  10. static void hello_exit(void)  

  11. {  

  12.   

  13.         printk(KERN_ALERT "Goodbye, cruel world\n");  

  14. }  

  15.   

  16. module_init(hello_init);  

  17. 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文件。

[cpp] view plaincopy在CODE上查看代碼片派生到個人代碼片

  1. ifndef CROSS_COMPILE  

  2. export CROSS_COMPILE ?=arm-none-linux-gnueabi-  

  3. endif  

  4.   

  5. ARCH ?= arm  

  6.   

  7. SRC_DIR := /home/XXX/XXX  

  8. OBJ_DIR  := $(SRC_DIR)/obj  

  9. PWD := $(shell pwd)  

  10.   

  11. LINUX_SRC ?= /home/XXX/kernel  

  12.   

  13. CFG_INC = -I$(SRC_DIR) \  

  14.     -I$(DIR_A) \  

  15.     -I$(DIR_B)  

  16.   

  17. CFG_FLAGS += -O2  

  18. EXTRA_CFLAGS  += $(C_FLAGS) $(CFG_INC) $(CFG_INC)  

  19.   

  20. obj-m := mymodule.o  

  21.   

  22. mymodule-objs := a.o  

  23. mymodule-objs += b.o  

  24. mymodule-objs += c.o  

  25.   

  26. modules:  

  27.     @make ARCH=$(ARCH) -C $(LINUX_SRC) M=$(PWD) modules  

  28.   

  29. clean:  

  30.     @echo "cleaning..."  

  31.     rm -f mymodule.ko mymodule.o mymodule.mod.* modules.order Module.symvers  

  32.     rm -f $(mymodule-objs)  


第一時間得到博客更新提醒,以及更多技術信息分享,歡迎關注我的微信公衆平臺:程序員互動聯盟(coder_online)

1.直接幫你解答linux設備驅動疑問點

2.第一時間得到業內十多個領域技術文章

3.針對文章內疑點提出問題,第一時間回覆你,幫你耐心解答

4.讓你和原創做者成爲很好的朋友,拓展本身的人脈資源

掃一掃下方二維碼或搜索微信號coder_online便可關注,咱們能夠在線交流。

相關文章
相關標籤/搜索