Android 第一个驱动之 word_count(二)
1、指定与驱动相关的信息
虽然指定这些信息不是必须的,但一个完整的Linux驱动程序都会指定这些与驱动相关的信息。一般需要为Linux驱动程序指定如下信息。
1. 模块作者:使用MODULE_AUTHOR宏指定。
2. 模块描述:使用MODULE_DESCRIPTION宏指定。
3. 模块别名:使用MODULE_ALIAS宏指定。
4. 开源协议:使用MODULE_LICENSE宏指定。
除了这些信息外,Linux驱动模块自己还会包含一些信息。读者可以执行下面的命令查看word_count.ko的信息。
# modinfo word_count.ko
执行上面的命令后,会输出如图所示的信息。其中depends表示当前驱动模块的依赖,word_count并没有依赖什么,因此该项为空。vermagic表示当前Linux驱动模块在那个Linux内核版本下编译的。
现在使用下面的代码指定上述4种信息。一般会将这些代码放在word_count.c文件的最后。
MODULE_AUTHOR("ZhengChuanyu");
MODULE_DESCRIPTION("statistics of word count");
MODULE_ALIAS("word count module.");
MODULE_LICENSE("GPL");
现在使用上一节的方法重新编译word_count.c文件。然后再执行modinfo命令,就会显示如图6-7所示的信息。从图6-7可以看出,上面的代码设置的信息都包含在了word_count.ko文件中。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/uaccess.h>
//定义设备文件名
#define DEVICE_NAME "wordcount" //定义设备文件名
//描诉与设备文件触发的事件对应的回调函数指针
//owner:设备事件回调函数应用于哪些模块,THIS_MODULE 表示应用与当前驱动模块
static struct file_operations wordcount_fops = {
.owner = THIS_MODULE,
};
//描述设备文件信息
//minor:次设备号 MISC_DYNAMTIC_MINOR,:动态生成次设备号 name:设备文件名称
//fops:file_operations 结构体变量指针
static struct miscdevice wordcount_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &wordcount_fops
};
//初始化 linux 驱动
static int word_count_init(void)
{
if (misc_register (&wordcount_misc_device)) {
printk (KERN_WARNING "wordcount: Couldn't register device 10, "
"%d.\n", wordcount_misc_device.minor);
return -EBUSY;
}
printk("word_count_init_successful\n");
return 0;
}
//退出 linux 驱动
static void word_count_exit(void)
{
//注销设备文件
misc_deregister(&wordcount_misc_device);
printk("word_count_exit_successful\n");
}
module_init(word_count_init);
module_exit(word_count_exit);
MODULE_AUTHOR("ZhengChuanyu");
MODULE_DESCRIPTION("statistics of word count");
MODULE_ALIAS("word count module.");
MODULE_LICENSE("GPL");
编写上面代码需要注意如下几点:
1. 设备文件由主设备号和次设备号描述。而使用misc_register函数只能设置次设备号。主设备号统一设为10。主设备号为10的设备是Linux系统中拥有共同特性的简单字符设备。这类设备称为misc设备。如果读者实现的驱动的功能并不复杂,可以考虑使用10作为其主设备号,而次设备号可以自己指定,也可以动态生成(需要指定MISC_DYNAMIC_MINOR常量)。因为采用这样的方式可以使用misc_register和misc_deregister函数简化注册和注销设备文件的步骤。在后面的博客中会详细介绍如何使用register_chrdev_region和alloc_chrdev_region函数同时指定主设备号和次设备号的方式注册和注销设备文件。
2. miscdevice.name变量的值就是设备文件的名称。在本例中设备文件名称为wordcount。
3. 虽然file_operations结构体中定义了多个回调函数指针变量,但本节并未初始化任何一个回调函数指针变量。只初始化了file_operations.owner变量。如果该变量的值为module结构体,表示file_operations可被应用在这些由module指定的驱动模块中。如果owner变量的值为THIS_MODULE,表示file_operations只应用于当前驱动模块。
4. 如果成功注册了设备文件,misc_register函数返回非0的整数,如果注册设备文件失败,返回0。
5. 可能有的读者注意到了。word_count.c中的所有函数、变量都声明成了static。这是因为在C语言中用static声明函数、变量等资源,系统会将这些函数和变量单独放在内存的某一个区域,直到程序完全退出,否则这些资源不会被释放。Linux驱动一旦装载,除非手动卸载或关机,驱动会一直驻留内存,因此这些函数和变量资源会一直在内存中。也就是说多次调用这些资源不用再进行压栈、出栈操作了。有利于提高驱动的运行效率。
现在重新编译word_count.c文件并使用如下的命令安装word_count驱动。
# insmod word_count.ko
如果word_count驱动已经被安装,应先使用下面的命令下载word_count驱动,然后再使用上面的命令安装word_count驱动。
# rmmod word_count
安装完word_count驱动后,使用下面的命令查看/dev目录中的设备。
# ls –a /dev
执行上面的命令后,会输出如图6-8所示的信息,其中多了一个wordcount文件(在红框中)。
如果想查看 wordcount 设备文件的主设备号和次设备号,可以使用如下的命令。
# ls –l /dev
执行上面的命令会输出如图6-9所示的信息,白框中的第一个数字是主设备号,第二个数字是从设备号。
使用下面的命令可获显示当期系统中有哪些主设备以及主设备号。
# cat /proc/devices
执行上面的命令后会输出如图6-10所示的信息,从中可以找到misc设备以及主设备编号10。
2、指定回调函数
本节讲的内容十分关键。不管Linux驱动程序的功能多么复杂还是多么“酷”,都必须允许用户空间的应用程序与内核空间的驱动程序进行交互才有意义。而最常用的交互方式就是读写设备文件。通过file_operations.read和file_operations.write成员变量可以分别指定读写设备文件要调用的回调函数指针。
在本节将为word_count.c添加两个函数:word_count_read和word_count_write。这两个函数分别处理从设备文件读数据和向设备文件写数据的动作。本节的例子先不考虑word_count要实现的统计单词数的功能,先用word_count_read和word_count_write函数做一个读写设备文件数据的实验,以便让读者了解如何与设备文件交互数据。
本例的功能是向设备文件/dev/wordcount写入数据后,都可以从/dev/wordcount设备文件中读出这些数据(只能读取一次)。下面先看看本例的完整的代码。
1 #include <linux/module.h>
2 #include <linux/init.h>
3 #include <linux/kernel.h>
4 #include <linux/fs.h>
5 #include <linux/miscdevice.h>
6 #include <asm/uaccess.h>
7 #include <linux/uaccess.h>
8
9 //定义设备文件名
10 #define DEVICE_NAME "wordcount" //定义设备文件名
11 static unsigned char mem[10000]; //保存向设备文件写入的数据
12 static char read_flag = 'y'; // y:已从设备文件读取数据 n:未从设备文件读取数据
13 static int written_count = 0; //向设备文件写入数据的字节数
14
15 //file:指向设备文件 buf:保存可读取的数据 count:可读取的字节数 ppos:读取数据的偏移量
16 static ssize_t word_count_read(struct file* file, char __user* buf, size_t count, loff_t *ppos)
17 {
18 //如果还没有读取设备文件中的数据,可以进行读取
19 if (read_flag == 'n')
20 {
21 // 将内核空间的数据复制到用户空间,buf 中的数据就是从设备文件中读出的数据
22 copy_to_user(buf, (void* )mem, written_count);
23 // 向日志输出已读取的字节数
24 printk("read count: %d", written_count);
25 // 设置数据已读状态
26 read_flag = 'y';
27 return written_count;
28 }
29 else
30 {
31 return 0;
32 }
33 }
34
35 static ssize_t word_count_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
36 {
37 // 将用户空间的数据复制到内核空间,mem中的数据就是向设备文件写入的数据
38 copy_from_user(mem, buf, count);
39 // 设置数据的未读状态
40 read_flag = 'n';
41 // 保存写入数据的字节数
42 written_count = count;
43 // 向日志输出已写入的字节数
44 printk("written count:%d", (int)count);
45 return count;
46 }
47
48 //描诉与设备文件触发的事件对应的回调函数指针
49 //owner:设备事件回调函数应用于哪些模块,THIS_MODULE 表示应用与当前驱动模块
50 static struct file_operations wordcount_fops = {
51 .owner = THIS_MODULE,
52 .read = word_count_read,
53 .write = word_count_write
54 };
55
56 //描述设备文件信息
57 //minor:次设备号 MISC_DYNAMTIC_MINOR,:动态生成次设备号 name:设备文件名称
58 //fops:file_operations 结构体变量指针
59 static struct miscdevice wordcount_misc_device = {
60 .minor = MISC_DYNAMIC_MINOR,
61 .name = DEVICE_NAME,
62 .fops = &wordcount_fops
63 };
64
65 //初始化 linux 驱动
66 static int word_count_init(void)
67 {
68 if (misc_register (&wordcount_misc_device)) {
69 printk (KERN_WARNING "wordcount: Couldn't register device 10, "
70 "%d.\n", wordcount_misc_device.minor);
71 return -EBUSY;
72 }
73
74 printk("word_count_init_successful\n");
75 return 0;
76 }
77
78 //退出 linux 驱动
79 static void word_count_exit(void)
80 {
81 //注销设备文件
82 misc_deregister(&wordcount_misc_device);
83 printk("word_count_exit_successful\n");
84 }
85
86 module_init(word_count_init);
87 module_exit(word_count_exit);
88
89 MODULE_AUTHOR("ZhengChuanyu");
90 MODULE_DESCRIPTION("statistics of word count");
91 MODULE_ALIAS("word count module.");
92 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,否则将会溢出。
执行下面的命令向/dev/word_count设备文件写入数据。
# echo ‘I love you’ > /dev/wordcount
然后执行如下的命令从/dev/word_count设备文件读取数据。
# cat /dev/wordcount
如果输出“I love you”,说明测试成功。
3、实现统计单词数的算法
本节开始编写 word_count 驱动的业务逻辑:统计单词数。本节实现的算法将由空格、制表符(ASCII:9)、回车符(ASCII:13)和换行符(ASCII:10)分隔的字符串算做一个单词,该算法同时考虑了有多个分隔符(空格符、制表符、回车符和换行符)的情况。下面是 word_count 驱动完整的代码。在代码中包含了统计单词数的函数get_word_count。
1 #include <linux/module.h>
2 #include <linux/init.h>
3 #include <linux/kernel.h>
4 #include <linux/fs.h>
5 #include <linux/miscdevice.h>
6 #include <asm/uaccess.h>
7 #include <linux/uaccess.h>
8
9 //定义设备文件名
10 #define DEVICE_NAME "wordcount" //定义设备文件名
11 static unsigned char mem[10000]; //保存向设备文件写入的数据
12 static int word_count = 0; //向设备文件写入数据的字节数
13
14 #define TRUE -1
15 #define FALSE 0
16
17 static char is_spacewhite(char c)
18 {
19 if (c == ' ' | c == 9 | c == 10 | c == 13)
20 return TRUE;
21 else
22 return FALSE;
23 }
24
25 static int get_word_count(char* buf)
26 {
27 // flag = 0; 代表正常情况。 flag = 1; 代表遇到空格
28 int flag = 1;
29 int count = 0;
30 int i = 0;
31
32 for (i = 0; buf[i] != '\0'; i ++)
33 {
34
35 if (is_spacewhite(buf[i]) == TRUE)
36 {
37 flag = 1;
38 }
39 else
40 {
41 if (flag == 1)
42 {
43 count ++;
44 flag = 0;
45 }
46 }
47
48 /*
49 if (i == 0)
50 {
51 if (is_spacewhite(buf[i]) == FALSE)
52 count++;
53 }
54 else
55 {
56 if (is_spacewhite(buf[i]) == FALSE
57 && is_spacewhite(buf[i - 1]) == TRUE)
58 {
59 count++;
60 }
61
62 }
63 */
64 }
65
66 printk("get_word_count: count = %d", count);
67
68 return count;
69 }
70
71 //file:指向设备文件 buf:保存可读取的数据 count:可读取的字节数 ppos:读取数据的偏移量
72 static ssize_t word_count_read(struct file* file, char __user* buf, size_t count, loff_t *ppos)
73 {
74 unsigned char temp[4];
75 // 将单词数(int类型)分解成 4 个字节存储在 buf 中
76 memset(temp, 0, sizeof(temp));
77 temp[0] = word_count >> 24;
78 temp[1] = word_count >> 16;
79 temp[2] = word_count >> 8;
80 temp[3] = word_count;
81 copy_to_user(buf, (void* )temp, 4);
82 printk("read: word count:%d", (int)count);
83 }
84
85 static ssize_t word_count_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
86 {
87 ssize_t written = count;
88 // 将用户空间的数据复制到内核空间,mem中的数据就是向设备文件写入的数据
89 copy_from_user(mem, buf, count);
90 mem[count] = '\0';
91
92 word_count = get_word_count(mem);
93
94 // 向日志输出已写入的字节数
95 printk("Write: word count:%d", (int)word_count);
96 return written;
97 }
98
99 //描诉与设备文件触发的事件对应的回调函数指针
100 //owner:设备事件回调函数应用于哪些模块,THIS_MODULE 表示应用与当前驱动模块
101 static struct file_operations wordcount_fops = {
102 .owner = THIS_MODULE,
103 .read = word_count_read,
104 .write = word_count_write
105 };
106
107 //描述设备文件信息
108 //minor:次设备号 MISC_DYNAMTIC_MINOR,:动态生成次设备号 name:设备文件名称
109 //fops:file_operations 结构体变量指针
110 static struct miscdevice wordcount_misc_device = {
111 .minor = MISC_DYNAMIC_MINOR,
112 .name = DEVICE_NAME,
113 .fops = &wordcount_fops
114 };
115
116 //初始化 linux 驱动
117 static int word_count_init(void)
118 {
119 if (misc_register (&wordcount_misc_device)) {
120 printk (KERN_WARNING "wordcount: Couldn't register device 10, "
121 "%d.\n", wordcount_misc_device.minor);
122 return -EBUSY;
123 }
124
125 printk("word_count_init_successful\n");
126 return 0;
127 }
128
129 //退出 linux 驱动
130 static void word_count_exit(void)
131 {
132 //注销设备文件
133 misc_deregister(&wordcount_misc_device);
134 printk("word_count_exit_successful\n");
135 }
136
137 module_init(word_count_init);
138 module_exit(word_count_exit);
139
140 MODULE_AUTHOR("ZhengChuanyu");
141 MODULE_DESCRIPTION("statistics of word count");
142 MODULE_ALIAS("word count module.");
143 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类型的值。