開發可統計單詞個數的Android驅動程序(1)

轉自http://blog.csdn.net/nokiaguy/article/details/8523272

Android本質上是基於Linux內核的系統,也就是說Android就是一種Linux操作系統。只不過大多數時候都會運行在ARM架構的設備上,例如,Android手機、平板等。Android驅動實際上就是Linux驅動,只是這裏使用Android深度探索(卷1):安裝C/C++交叉編譯環境 介紹的交叉編譯器將Linux驅動編譯成了ARM架構的,所以驅動可以安裝在Android模擬器、Android手機(需要root)或平板上(這些設備都要使用給予ARM架構的CPU),當然,使用傳統的GCC也可以編譯成X86架構的驅動(並不需要修改代碼),這樣也可以在Ubuntu Linux上安裝Linux驅動。

       本文及後面幾篇文章主要介紹如何利用Android模擬器和S3C6410開發板開發給予ARM架構的Linux驅動,當然,測試的環境是Android,而不是我們通常使用的Ubuntu Linux等X86架構的系統。最後會介紹通過多種方式測試這個驅動,測試方法包括命令行、NDK、Android程序(Java代碼)等,當然,在最最後還會介紹如果將驅動嵌入到LInux內核中,這樣Android在啓動是就自動擁有了這個驅動。

      想學習Android底層開發的童鞋可以通過本文完全掌握開發基於Android的LInux驅動的完整步驟。在《Android深度探索(卷1):HAL與驅動開發》隨書光盤上有完整的實驗環境(VMWare Ubuntu Linux12.04LTS),如果嫌自己配置麻煩,可以從光盤中複製該虛擬環境,虛擬文件太大(3.6G),傳不上去,只能發文章了!

一、Linux驅動到底是個什麼東西

       對於從未接觸過驅動開發的程序員可能會感覺Linux驅動很神祕。感覺開發起來會很複雜。其實這完全是誤解。實際上Linux驅動和普通的LinuxAPI沒有本質的區別。只是使用Linux驅動的方式與使用Linux API的方式不同而已。

       在學習Linux驅動之前我們先來介紹一下Linux驅動的工作方式。如果讀者以前接觸過Windows或其他非Unix體系的操作系統,最好將它們的工作方式暫時忘掉,因爲這些記憶會干擾我們理解Linux底層的一些細節。

      Linux驅動的工作和訪問方式是Linux的亮點之一,同時受到了業界的廣泛好評。Linux系統將每一個驅動都映射成一個文件。這些文件稱爲設備文件或驅動文件,都保存在/dev目錄中。這種設計理念使得與Linux驅動進行交互就像與普通文件進行交互一樣容易。當然,也比訪問LinuxAPI更容易。由於大多數Linux驅動都有與其對應的設備文件,因此與Linux驅動交換數據就變成了與設備文件交換數據。例如,向Linux打印機驅動發送一個打印命令,可以直接使用C語言函數open打開設備文件,再使用C語言函數ioctl向該驅動的設備文件發送打印命令。

     當然,要編寫Linux驅動程序還需要更高級的功能。如向打印機驅動寫入數據時,對於打印機驅動來說,需要接收這些被寫入的數據,並將它們通過PC的並口、USB等端口發送給打印機。要實現這一過程就需要Linux驅動可以響應應用程序傳遞過來的數據。這就是Linux驅動的事件,雖然在C語言裏沒有事件的概念,但卻有與事件類似的概念,這就是回調(callback)函數。因此,編寫Linux驅動最重要的一步就是編寫回調函數,否則與設備文件交互的數據將無法得到處理。圖6-1是應用軟件、設備文件、驅動程序、硬件之間的關係。

二、編寫Linux驅動程序的步驟

Linux驅動程序與其他類型的Linux程序一樣,也有自己的規則。對於剛開始接觸Linux驅動開發的讀者可能對如何開發一個LInux驅動程序還不是太瞭解。爲了解決這部分讀者的困惑,本節給出了編寫一個基本的Linux驅動的一般步驟。讀者可以按着這些步驟循序漸進地學習Linux驅動開發。

第1步:建立Linux驅動骨架(裝載和卸載Linux驅動)

任何類型的程序都有一個基本的結構,例如,C語言需要有一個入口函數main。Linux驅動程序也不例外。Linux內核在使用驅動時首先需要裝載驅動。在裝載過程中需要進行一些初始化工作,例如,建立設備文件,分配內存地址空間等。當Linux系統退出時需要卸載Linux驅動,在卸載的過程中需要釋放由Linux驅動佔用的資源,例如,刪除設備文件、釋放內存地址空間等。在Linux驅動程序中需要提供兩個函數來分別處理驅動初始化和退出的工作。這兩個函數分別用module_init和module_exit宏指定。Linux驅動程序一般都都需要指定這兩個函數,因此包含這兩個函數以及指定這兩個函數的兩個宏的C程序文件也可看作是Linux驅動的骨架。

第2步:註冊和註銷設備文件

任何一個Linux驅動都需要有一個設備文件。否則應用程序將無法與驅動程序交互。建立設備文件的工作一般在第1步編寫的處理Linux初始化工作的函數中完成。刪除設備文件一般在第1步編寫的處理Linux退出工作的函數中完成。可以分別使用misc_register和misc_deregister函數創建和移除設備文件。 

第3步:指定與驅動相關的信息

驅動程序是自描述的。例如,可以通過modinfo命令獲取驅動程序的作者姓名、使用的開源協議、別名、驅動描述等信息。這些信息都需要在驅動源代碼中指定。通過MODULE_AUTHOR、MODULE_LICENSE 、MODULE_ALIAS 、MODULE_DESCRIPTION等宏可以指定與驅動相關的信息。

第4步:指定回調函數

Linux驅動包含了多種動作,也可稱爲事件。例如,向設備文件寫入數據時會觸發“寫”事件,Linux系統會調用對應驅動程序的write回調函數,從設備文件讀數據時會觸發“讀”事件,Linux系統會調用對應驅動程序的read回調函數。一個驅動程序並不一定要指定所有的回調函數。回調函數會通過相關機制進行註冊。例如,與設備文件相關的回調函數會通過misc_register函數進行註冊。

第5步:編寫業務邏輯

這一步是Linux驅動的核心部分。光有骨架和回調函數的Linux驅動是沒有任何意義的。任何一個完整的Linux驅動都會做一些與其功能相關的工作,如打印機驅動會向打印機發送打印指令。COM驅動會根據傳輸數率進行數據交互。具體的業務邏輯與驅動的功能有關。業務邏輯可能有多個函數、多個文件甚至是多個Linux驅動模塊組成。具體的實現讀者可以根據實際情況而定。

第6步:編寫Makefile文件

Linux內核源代碼的編譯規則是通過Makefile文件定義的。因此編寫一個新的Linux驅動程序必須要有一個Makefile文件。

第7步:編譯Linux驅動程序

Linux驅動程序可以直接編譯進內核,也可以作爲模塊單獨編譯。

第8步:安裝和卸載Linux驅動

如果將Linux驅動編譯進內核,只要Linux使用該內核,驅動程序就會自動裝載。如果Linux驅動程序以模塊單獨存在,需要使用insmod或modprobe命令裝載Linux驅動模塊,使用rmmod命令卸載Linux驅動模塊。

上面8步中的前5步是關於如何編寫Linux驅動程序的,通過後3步可以使Linux驅動正常工作。


三、編寫Linux驅動程序前的準備工作

本例的Linux驅動源代碼並未與linux內核源代碼放在一起,而是單獨放在一個目錄。首先使用下面的命令建立存放Linux驅動程序的目錄。

# mkdir –p  /root/drivers/ch06/word_count

# cd  /root/drivers/ch06/word_count

然後使用下面的命令建立驅動源代碼文件(word_count.c)

# echo '' > word_count.c

最後編寫一個Makefile文件,實際上這是6.2節介紹的編寫Linux驅動程序的第6步。當熟悉編寫Linux驅動程序的步驟後可以不按6.2節介紹的順序來編寫Linux驅動。

# echo 'obj-m := word_count.o'  > Makefile

其中obj-m表示將Linux驅動作爲模塊(.ko文件)編譯。如果使用obj-y,則將Linux驅動編譯進Linux內核。obj-m或obj-y需要使用“:=”賦值。如果obj-m或obj-y的值爲word_count.o,表示make命令會把Linux驅動源代碼目錄中的word_count.c或word_count.s文件編譯成word_count.o文件。如果使用obj-m,word_count.o會被連接進word_count.ko文件,然後使用insmod或modprobe命令裝載word_count.ko。如果使用obj-y,word_count.o會被連接進built-in.o文件,最終會被連接進內核。其中built-in.o文件是連接同一類程序的.o文件生成的中間目標文件。例如,所有的字符設備驅動程序會最終生成一個built-in.o文件。讀者可以在<Linux內核源代碼目錄>/drivers/char目錄找到一個built-in.o文件。該目標文件包含了所有可連接進Linux內核的字符驅動(通過make menuconfig命令可以配置每一個驅動及其他內核程序是否允許編譯進內核,關於配置Linux內核的技術詳見4.2.4節介紹)。

如果Linux驅動依賴其他程序,如process.c、data.c。需要按如下方式編寫Makefile文件。

obj-m := word_count.o

word_count-y := process.o  data.o

其中依賴文件要使用module-y或module-objs指定。module表示模塊名,如word_count。


四、編寫Linux驅動程序的骨架


現在編寫Linux驅動程序的骨架部分,也就是前面介紹的第1步。骨架部分主要是Linux驅動的初始化和退出函數,代碼如下:

  1. #include <linux/module.h>  
  2.   
  3. #include <linux/init.h>  
  4.   
  5. #include <linux/kernel.h>  
  6.   
  7. #include <linux/fs.h>  
  8.   
  9. #include <linux/miscdevice.h>  
  10.   
  11. #include <asm/uaccess.h>  
  12.   
  13. //  初始化Linux驅動  
  14.   
  15. static int word_count_init(void)  
  16. {  
  17.     //  輸出日誌信息  
  18.     printk("word_count_init_success\n");  
  19.     return 0;  
  20. }  
  21.   
  22. // 退出Linux驅動  
  23. static void word_count_exit(void)  
  24. {  
  25.     //  輸出日誌信息  
  26.    printk("word_count_init_exit_success\n");  
  27. }  
  28. //  註冊初始化Linux驅動的函數  
  29. module_init(word_count_init);  
  30. //  註冊退出Linux驅動的函數  
  31. module_exit(word_count_exit);     

      在上面的代碼中使用了printk函數。該函數用於輸出日誌信息(關於printk函數的詳細用法將在10.1節詳細介紹)。printk函數與printf函數的用法類似。有的讀者可能會有疑問,爲什麼不用printf函數呢?這裏就涉及到一個Linux內核程序可以調用什麼,不可以調用什麼的問題。Linux系統將內存分爲了用戶空間和內核空間,這兩個空間的程序不能直接訪問。printf函數運行在用戶空間,printk函數運行在內核空間。因此,屬於內核程序的Linux驅動是不能直接訪問printf函數的。就算包含了stdio.h頭文件,在編譯Linux驅動時也會拋出stdio.h文件沒找到的錯誤。當然,運行在用戶空間的程序也不能直接調用printk函數。那麼是不是用戶空間和內核空間的程序就無法交互了呢?答案是否定的。否則這兩塊內存不就成了孤島了嗎。運行在這兩塊內存中的程序之間交互的方法很多。其中設備文件就是一種主要的交互方式(在後面的章節還會介紹/proc虛擬文件的交互方式)。如果用戶空間的程序要訪問內核空間,只要做一個可以訪問內核空間的驅動程序,然後用戶空間的程序通過設備文件與驅動程序進行交互即可。

      看到這可能有的讀者疑問更大了。Linux驅動程序無法直接訪問運行在用戶空間的程序,那麼很多功能就都得自己實現了。例如,在C語言中會經常使用malloc函數動態分配內存空間,該函數在Linux驅動程序中是無法使用的。那麼如何在Linux驅動程序中動態分配內存空間呢?解決類似的問題也很簡單。既然Linux驅動無法直接調用運行在用戶空間的函數,那麼在Linux內核中就必須要提供替代品。讀者可以進入<Linux內核源代碼>/include目錄,該目錄的各個子目錄中包含了大量的C語言頭文件。這些頭文件中定義的函數、宏等資源就是運行在用戶空間的程序的替代品。運行在用戶空間的函數庫對應的頭文件在/usr/include目錄中。剛纔提到的malloc函數在內核空間的替代品是kmalloc(需要包含slab.h頭文件,#include  <linux/slab.h>)。

注意:用戶空間與內核空間完成同樣或類似功能的函數、宏等資源的名稱並不一定相同,有的名稱類似,如malloc和kmalloc,有的完全是兩個不同的名字:如atoi(用戶空間)和simple_strtol(內核空間)、itoa(用戶空間)和snprintf(內核空間)。讀者在使用內核相關資源時要注意在一點。

如果讀者想看看前面編寫的程序的效果,可以使用下面的命令編譯Linux驅動源代碼(X86架構)。

# make  -C /usr/src/linux-headers-3.0.0-15-generic M=/root/drivers/ch06/word_count

     在測試Linux驅動未必一定在Android設備上完成。因爲Android系統和Ubuntu Linux以及其他Linux發行版本都是基於Linux內核的,大多數Linux驅動程序可以在Ubuntu Linux或其他Linux發行版上測試完再重新用交叉編譯器編譯成基於ARM架構的目標文件,然後再安裝到Android上即可正常運行。由於編譯Linux內核源代碼需要使用Linux內核的頭文件。爲了在Ubuntu Linux上測試驅動程序,需要使用-C命令行參數指定Linux內核頭文件的目錄(/usr/src/linux-headers-3.0.0-15-generic)。其中linux-headers-3.0.0-15-generic目錄是Linux內核源代碼目錄,在該目錄中只有include子目錄有實際的頭文件,其他目錄只有Makefile和其他一些配置文件,並不包含Linux內核源代碼。該目錄就是爲了開發當前Linux內核版本的驅動及其他內核程序而提供的(因爲在編譯Linux驅動時生成目標文件只需要頭文件,在進行目標文件鏈接時只要有相關的目標文件即可,並不需要源代碼文件)。如果以模塊方式編譯Linux驅動程序,需要使用M指定驅動程序所在的目錄(M= root/drivers/ch06/word_count)。

注意:如果讀者使用的Linux發行版採用了其他Linux內核,需要爲-C命令行參數設置正確的路徑。

執行上面的命令後,會輸出如圖6-2所示信息。從這些信息可以看出,已經將word_count.c文件編譯成了Linux驅動模塊文件word_count.ko。


         使用ls命令列出/root/drivers/ch06/word_count目錄中的文件後發現,除了多了幾個.o和.ko文件,還多了一些其他的文件,如圖6-3所示。這些文件是有編譯器自動生成的,一般並不需要管這些文件的內容。

      本文編寫的Linux驅動程序雖然什麼實際的功能都沒有,但已經可以作爲驅動程序安裝在Linux內核空間了。讀者可以使用下面的命令安裝、查看、卸載Linux驅動,也可以查看由驅動程序輸出的日誌信息(執行下面命令時需要先進入word_count目錄)。

安裝Linux驅動

# insmod word_count.ko

查看word_count是否成功安裝

# lsmod | grep word_count

卸載Linux驅動

# rmmod word_count

查看由Linux驅動輸出的日誌信息

# dmesg | grep word_count | tail –n 2

執行上面的命令後,如果輸出如圖6-4所示的信息說明讀者已成功完成本節的學習,可以繼續看下一節了。


        dmesg命令實際上是從/var/log/messages(Ubuntu Linux 10.04)或/var/log/syslog(Ubuntu Linux11.10)文件中讀取的日誌信息,因此也可以執行下面的命令獲取由Linux驅動輸出的日誌信息。

# cat /var/log/syslog | grep word_count | tail –n 2

執行上面的命令後會輸出更多的信息,如圖6-5所示。

本文節選至《Android深度探索(卷1):HAL與驅動開發》,接下來幾篇文章將詳細闡述如何開發ARM架構的Linux驅動,並分別利用android程序、NDK、可執行文件測試Linux驅動。可在ubuntu Linux、Android模擬器和S3C6410開發板(可以選購OK6410-A開發板,需要刷Android)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章