[開發可統計單詞個數的Android驅動程序(2)](http://blog.csdn.net/nokiaguy/article/details/8584632)
**八、?指定回調函數**
????? 本節講的內容十分關鍵。不管Linux驅動程序的功能多么復雜還是多么“酷”,都必須允許用戶空間的應用程序與內核空間的驅動程序進行交互才有意義。而最常用的交互方式就是讀寫設備文件。通過file_operations.read和file_operations.write成員變量可以分別指定讀寫設備文件要調用的回調函數指針。
???? 在本節將為word_count.c添加兩個函數:word_count_read和word_count_write。這兩個函數分別處理從設備文件讀數據和向設備文件寫數據的動作。本節的例子先不考慮word_count要實現的統計單詞數的功能,先用word_count_read和word_count_write函數做一個讀寫設備文件數據的實驗,以便讓讀者了解如何與設備文件交互數據。本節編寫的word_count.c文件是一個分支,讀者可在word_count/read_write目錄找到word_count.c文件。可以用該文件覆蓋word_count目錄下的同名文件測試本節的例子。
???? 本例的功能是向設備文件/dev/wordcount寫入數據后,都可以從/dev/wordcount設備文件中讀出這些數據(只能讀取一次)。下面先看看本例的完整的代碼。
~~~
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#define DEVICE_NAME "wordcount" // 定義設備文件名
static unsigned char mem[10000]; // 保存向設備文件寫入的數據
static char read_flag = 'y'; // y:已從設備文件讀取數據 n:未從設備文件讀取數據
static int written_count = 0; // 向設備文件寫入數據的字節數
// 從設備文件讀取數據時調用該函數
// file:指向設備文件、buf:保存可讀取的數據 count:可讀取的字節數 ppos:讀取數據的偏移量
static ssize_t word_count_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
// 如果還沒有讀取設備文件中的數據,可以進行讀取
if(read_flag == 'n')
{
// 將內核空間的數據復制到用戶空間,buf中的數據就是從設備文件中讀出的數據
copy_to_user(buf, (void*) mem, written_count);
// 向日志輸出已讀取的字節數
printk("read count:%d", (int) written_count);
// 設置數據已讀狀態
read_flag = 'y';
return written_count;
}
// 已經從設備文件讀取數據,不能再次讀取數據
else
{
return 0;
}
}
// 向設備文件寫入數據時調用該函數
// file:指向設備文件、buf:保存寫入的數據 count:寫入數據的字節數 ppos:寫入數據的偏移量
static ssize_t word_count_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
// 將用戶空間的數據復制到內核空間,mem中的數據就是向設備文件寫入的數據
copy_from_user(mem, buf, count);
// 設置數據的未讀狀態
read_flag = 'n';
// 保存寫入數據的字節數
written_count = count;
// 向日志輸出已寫入的字節數
printk("written count:%d", (int)count);
return count;
}
// 描述與設備文件觸發的事件對應的回調函數指針
// 需要設置read和write成員變量,系統才能調用處理讀寫設備文件動作的函數
static struct file_operations dev_fops =
{ .owner = THIS_MODULE, .read = word_count_read, .write = word_count_write };
// 描述設備文件的信息
static struct miscdevice misc =
{ .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops };
// 初始化Linux驅動
static int word_count_init(void)
{
int ret;
// 建立設備文件
ret = misc_register(&misc);
// 輸出日志信息
printk("word_count_init_success\n");
return ret;
}
// 卸載Linux驅動
static void word_count_exit(void)
{
// 刪除設備文件
misc_deregister(&misc);
// 輸出日志信息
printk("word_init_exit_success\n");
}
// 注冊初始化Linux驅動的函數
module_init( word_count_init);
// 注冊卸載Linux驅動的函數
module_exit( word_count_exit);
MODULE_AUTHOR("lining");
MODULE_DESCRIPTION("statistics of word count.");
MODULE_ALIAS("word count module.");
MODULE_LICENSE("GPL");
~~~
編寫上面代碼需要了解如下幾點。
1\. word_count_read和word_count_write函數的參數基本相同,只有第2個參數buf稍微一點差異。word_count_read函數的buf參數類型是char*,而word_count_write函數的buf參數類型是const char*,這就意味著word_count_write函數中的buf參數值無法修改。word_count_read函數中的buf參數表示從設備文件讀出的數據,也就是說,buf中的數據都可能由設備文件讀出,至于可以讀出多少數據,取決于word_count_read函數的返回值。如果word_count_read函數返回n,則可以從buf讀出n個字符。當然,如果n為0,表示無法讀出任何的字符。如果n小于0,表示發生了某種錯誤(n為錯誤代碼)。word_count_write函數中的buf表示由用戶空間的應用程序寫入的數據。buf參數前有一個“__user”宏,表示buf的內存區域位于用戶空間。
2\. 由于內核空間的程序不能直接訪問用戶空間中的數據,因此,需要在word_count_read和word_count_write函數中分別使用copy_to_user和copy_from_user函數將數據從內核空間復制到用戶空間或從用戶空間復制到內核空間。
3\. 本例只能從設備文件讀一次數據。也就是說,寫一次數據,讀一次數據后,第二次無法再從設備文件讀出任何數據。除非再次寫入數據。這個功能是通過read_flag變量控制的。當read_flag變量值為n,表示還沒有讀過設備文件,在word_count_read函數中會正常讀取數據。如果read_flag變量值為y,表示已經讀過設備文件中的數據,word_count_read函數會直接返回0。應用程序將無法讀取任何數據。
4\. 實際上word_count_read函數的count參數表示的就是從設備文件讀取的字節數。但因為使用cat命令測試word_count驅動時。直接讀取了32768個字節。因此count參數就沒什么用了(值總是32768)。所以要在word_count_write函數中將寫入的字節數保存,在word_count_read函數中直接使用寫入的字節數。也就是說,寫入多少個字節,就讀出多少個字節。
5.? 所有寫入的數據都保存在mem數組中。該數組定義為10000個字符,因此寫入的數據字節數不能超過10000,否則將會溢出。
????? 為了方便讀者測試本節的例子,筆者編寫了幾個Shell腳本文件,允許在UbuntuLinux、S3C6410開發板和Android模擬器上測試word_count驅動。其中有一個負責調度的腳本文件build.sh。本書所有的例子都會有一個build.sh腳本文件,執行這個腳本文件就會要求用戶選擇將源代碼編譯到那個平臺,選擇菜單如圖6-11所示。用戶可以輸入1、2或3選擇編譯平臺。如果直接按回車鍵,默認值會選擇第1個編譯平臺(UbuntuLinux)。

build.sh腳本文件的代碼如下:
~~~
source /root/drivers/common.sh
# select_target是一個函數,用語顯示圖6-11所示的選擇菜單,并接收用戶的輸入
# 改函數在common.sh文件中定義
select_target
if [ $selected_target == 1 ]; then
source ./build_ubuntu.sh # 執行編譯成Ubuntu Linux平臺驅動的腳本文件
elif [ $selected_target == 2 ]; then
source ./build_s3c6410.sh # 執行編譯成s3c6410平臺驅動的腳本文件
elif [ $selected_target == 3 ]; then
source ./build_emulator.sh # 執行編譯成Android模擬器平臺驅動的腳本文件
fi
~~~
????? 在build.sh腳本文件中涉及到了3個腳本文件(build_ubuntu.sh、build_s3c6410.sh和build_emulator.sh),這3個腳本文件的代碼類似,只是選擇的Linux內核版本不同。對于S3C6410和Android模擬器平臺,編譯完后Linux驅動,會自動將編譯好的Linux驅動文件(*.so文件)上傳到相應平臺的/data/local目錄,并安裝Linux驅動。例如,build_s3c6410.sh腳本文件的代碼如下:
~~~
source /root/drivers/common.sh
# S3C6410_KERNEL_PATH變量是適用S3C6410平臺的Linux內核源代碼的路徑,
# 該變量以及其它類似變量都在common.sh腳本文件中定義
make -C $S3C6410_KERNEL_PATH M=${PWD}
find_devices
# 如果什么都選擇,直接退出
if [ "$selected_device" == "" ]; then
exit
else
# 上傳驅動程序(word_count.ko)
adb -s $selected_device push ${PWD}/word_count.ko /data/local
# 判斷word_count驅動是否存在
testing=$(adb -s $selected_device shell lsmod | grep "word_count")
if [ "$testing" != "" ]; then
# 刪除已經存在的word_count驅動
adb -s $selected_device shell rmmod word_count
fi
# 在S3C6410開發板中安裝word_count驅動
adb -s $selected_device shell "insmod /data/local/word_count.ko"
fi
~~~
使用上面的腳本文件,需要在read_write目錄建立一個Makefile文件,內容如下:
obj-m := word_count.o
現在執行build.sh腳本文件,選擇要編譯的平臺,并執行下面的命令向/dev/word_count設備文件寫入數據。
?# echo ‘hello lining’ > /dev/wordcount
然后執行如下的命令從/dev/word_count設備文件讀取數據。
?# cat /dev/wordcount
如果輸出“hello lining”,說明測試成功。
**?注意:**如果在S3C6410開發板和Android模擬器上測試word_count驅動,需要執行shell.sh腳本文件或adb shell命令進入相應平臺的終端。其中shell.sh腳本在/root/drivers目錄中。這兩種方式的區別是如果有多個Android設備和PC相連時,shell.sh腳本會出現一個類似圖6-11所示的選擇菜單,用戶可以選擇進入哪個Android設備的終端,而adb shell命令必須要加-s命令行參數指定Android設備的ID才可以進入相應Android設備的終端。
**九、實現統計單詞數的算法**
????? 本節開始編寫word_count驅動的業務邏輯:統計單詞數。本節實現的算法將由空格、制表符(ASCII:9)、回車符(ASCII:13)和換行符(ASCII:10)分隔的字符串算做一個單詞,該算法同時考慮了有多個分隔符(空格符、制表符、回車符和換行符)的情況。下面是word_count驅動完整的代碼。在代碼中包含了統計單詞數的函數get_word_count。
~~~
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#define DEVICE_NAME "wordcount" // 定義設備文件名
static unsigned char mem[10000]; // 保存向設備文件寫入的數據
static int word_count = 0; // 單詞數
#define TRUE -1
#define FALSE 0
// 判斷指定字符是否為空格(包括空格符、制表符、回車符和換行符)
static char is_spacewhite(char c)
{
if(c == ' ' || c == 9 || c == 13 || c == 10)
return TRUE;
else
return FALSE;
}
// 統計單詞數
static int get_word_count(const char *buf)
{
int n = 1;
int i = 0;
char c = ' ';
char flag = 0; // 處理多個空格分隔的情況,0:正常情況,1:已遇到一個空格
if(*buf == '\0')
return 0;
// 第1個字符是空格,從0開始計數
if(is_spacewhite(*buf) == TRUE)
n--;
// 掃描字符串中的每一個字符
for (; (c = *(buf + i)) != '\0'; i++)
{
// 只由一個空格分隔單詞的情況
if(flag == 1 && is_spacewhite(c) == FALSE)
{
flag = 0;
}
// 由多個空格分隔單詞的情況,忽略多余的空格
else if(flag == 1 && is_spacewhite(c) == TRUE)
{
continue;
}
// 當前字符為空格時單詞數加1
if(is_spacewhite(c) == TRUE)
{
n++;
flag = 1;
}
}
// 如果字符串以一個或多個空格結尾,不計數(單詞數減1)
if(is_spacewhite(*(buf + i - 1)) == TRUE)
n--;
return n;
}
// 從設備文件讀取數據時調用的函數
static ssize_t word_count_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned char temp[4];
// 將單詞數(int類型)分解成4個字節存儲在buf中
temp[0] = word_count >> 24;
temp[1] = word_count >> 16;
temp[2] = word_count >> 8;
temp[3] = word_count;
copy_to_user(buf, (void*) temp, 4);
printk("read:word count:%d", (int) count);
return count;
}
// 向設備文件寫入數據時調用的函數
static ssize_t word_count_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
ssize_t written = count;
copy_from_user(mem, buf, count);
mem[count] = '\0';
// 統計單詞數
word_count = get_word_count(mem);
printk("write:word count:%d", (int)word_count);
return written;
}
// 描述與設備文件觸發的事件對應的回調函數指針
static struct file_operations dev_fops =
{ .owner = THIS_MODULE, .read = word_count_read, .write = word_count_write };
// 描述設備文件的信息
static struct miscdevice misc =
{ .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops };
// 初始化Linux驅動
static int word_count_init(void)
{
int ret;
// 建立設備文件
ret = misc_register(&misc);
// 輸出日志信息
printk("word_count_init_success\n");
return ret;
}
// 卸載Linux驅動
static void word_count_exit(void)
{
// 刪除設備文件
misc_deregister(&misc);
// 輸出日志信息
printk("word_init_exit_success\n");
}
// 注冊初始化Linux驅動的函數
module_init( word_count_init);
// 注冊卸載Linux驅動的函數
module_exit( word_count_exit);
MODULE_AUTHOR("lining");
MODULE_DESCRIPTION("statistics of word count.");
MODULE_ALIAS("word count module.");
MODULE_LICENSE("GPL");
~~~
編寫word_count驅動程序需要了解如下幾點。
1.? get_word_count函數將mem數組中第1個為“\0”的字符作為字符串的結尾符,因此在word_count_write函數中將mem[count]的值設為“\0”,否則get_word_count函數無法知道要統計單詞數的字符串到哪里結束。由于mem數組的長度為10000,而字符串最后一個字符為“\0”,因此待統計的字符串最大長度為9999。
2.? 單詞數使用int類型變量存儲。在word_count_write函數中統計出了單詞數(word_count變量的值),在word_count_read函數中將word_count整型變量值分解成4個字節存儲在buf中。因此,在應用程序中需要再將這4個字節組合成int類型的值。
**十、編譯、安裝、卸載Linux驅動程序**
????? 在上一節word_count驅動程序已經全部編寫完成了,而且多次編譯測試該驅動程序。安裝和卸載word_count驅動也做過多次。word_count驅動與read_write目錄中的驅動一樣,也有一個build.sh和3個與平臺相關的腳本文件。這些腳本文件與6.3.5節的實現類似,這里不再詳細介紹。現在執行build.sh腳本文件,并選擇要編譯的平臺。然后執行下面兩行命令查看日志輸出信息和word_count驅動模塊(word_count.ko)的信息。
?# dmesg |tail -n 1
?# modinfo word_count.ko
如果顯示如圖6-12所示的信息,表明word_count驅動工作完全正常。

???? 本書的腳本文件都是使用insmod命令安裝Linux驅動的,除了該命令外,使用modprobe命令也可以安裝Linux驅動。insmod和modprobe的區別是modprobe命令可以檢查驅動模塊的依賴性。如A模塊依賴于B模塊(裝載A之前必須先裝載B)。如果使用insmod命令裝載A模塊,會出現錯誤。而使用modprobe命令裝載A模塊,B模塊會現在裝載。在使用modprobe命令裝載驅動模塊之前,需要先使用depmod命令檢測Linux驅動模塊的依賴關系。
?# depmod ?/root/drivers/ch06/word_count/word_count.ko
depmod命令實際上將Linux驅動模塊文件(包括其路徑)添加到如下的文件中。
/lib/modules/3.0.0-16-generic/modules.dep
使用depmod命令檢測完依賴關系后,就可以調用modprobe命令裝載Linux驅動。
?# modprobe word_count
使用depmod和modprobe命令需要注意如下幾點:
1\. depmod命令必須使用Linux驅動模塊(.ko文件)的絕對路徑。
2\. depmod命令會將內核模塊的依賴信息寫入當前正在使用的內核的modules.dep文件。例如,筆者的Ubuntu Linux使用的是Linux3.0.0.16,所以應到3.0.0-16-generic目錄去尋找modules.dep文件。如果讀者使用了其他Linux內核,需要到相應的目錄去尋找modules.dep文件。
3\. modprobe命令只需使用驅動名稱即可,不需要跟.ko。
### [使用Android、S3C6410開發板和Ubuntu測試Linux驅動](http://blog.csdn.net/nokiaguy/article/details/8635795)
本文節選至[《Android深度探索(卷1):HAL與驅動開發》](http://product.dangdang.com/main/product.aspx?product_id=23043311),接下來幾篇文章將詳細闡述如何開發ARM架構的Linux驅動,并分別利用android程序、NDK、可執行文件測試Linux驅動。可在ubuntu Linux、Android模擬器和S3C6410開發板(可以選購OK6410-A開發板,需要刷Android)