linux设备驱动归纳总结(三):5.阻塞型IO实现
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、休眠简介:
进程休眠,简单的说就是正在运行的进程让出CPU。休眠的进程会被内核搁置在在一边,只有当内核再次把休眠的进程唤醒,进程才会会重新在CPU运行。这是内核中的进程调度,以后的章节会介绍。
现在应该先知道这样的一个概念,一个CPU在同一时间只能有一个进程在运行,在宏观上,我们觉得是所有进程同时进行的。实际上并不是这样,内核给每个进程分配了4G的虚拟内存,并且让每个进程傻乎乎的以为自己霸占着CPU运行。同时,内核暗中的将所有的进程按一定的算法将CPU轮流的给每个进程使用,而休眠就是进程没有被运行时的一种形式。在休眠下,进程不占用CPU,等待被唤醒。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、阻塞型IO的实现:
知道什么是休眠,接下来就好办了。接下来就是要实现阻塞型的read和write函数,函数将实现一下功能:
read:当没数据可读时,函数让出CPU,进入休眠状态,等待write写入数据后唤醒read。
write:写入数据,并唤醒read。
先上函数:我只上需要修改的函数,open和release就不贴了
/*3rd_char_5/1st/test.c*/
1 #include <linux/module.h>
2 #include <linux/init.h>
3 #include <linux/fs.h>
4 #include <linux/cdev.h>
5
6 #include <linux/wait.h>
7 #include <linux/sched.h>
8
9 #include <asm/uaccess.h>
10 #include <linux/errno.h>
11
12 #define DEBUG_SWITCH 1
13 #if DEBUG_SWITCH
14 #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCT ION__, ##args)
15 #else
16 #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCT ION__, ##args)
17 #endif
18
19 #define DEV_SIZE 100
20
21 struct _test_t{
22 char kbuf[DEV_SIZE];
23 unsigned int major;
24 unsigned int minor;
25 unsigned int cur_size;
26 dev_t devno;
27 struct cdev test_cdev;
28 wait_queue_head_t test_queue; //1、定义等待队列头
29 };
30
。。。。。。省略。。。。。。。
43
44 ssize_t test_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
45 {
46 int ret;
47 struct _test_t *dev = filp->private_data;
48
49 /*休眠*/
50 P_DEBUG("read data.....\n");
51 if(wait_event_interruptible(dev->test_queue, dev->cur_size > 0))
52 return - ERESTARTSYS;
53
54 if (copy_to_user(buf, dev->kbuf, count)){
55 ret = - EFAULT;
56 }else{
57 ret = count;
58 dev->cur_size -= count;
59 P_DEBUG("read %d bytes, cur_size:[%d]\n", count, dev->cur_size);
60 }
61
62 return ret; //返回实际写入的字节数或错误号
63 }
64
65 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
66 {
67 int ret;
68 struct _test_t *dev = filp->private_data;
69
70 if(copy_from_user(dev->kbuf, buf, count)){
71 ret = - EFAULT;
72 }else{
73 ret = count;
74 dev->cur_size += count;
75 P_DEBUG("write %d bytes, cur_size:[%d]\n", count, dev->cur_size);
76 P_DEBUG("kbuf is [%s]\n", dev->kbuf);
77 /*唤醒*/
78 wake_up_interruptible(&dev->test_queue);
79 }
80
81 return ret; //返回实际写入的字节数或错误号
82 }
83
84 struct file_operations test_fops = {
85 .open = test_open,
86 .release = test_close,
87 .write = test_write,
88 .read = test_read,
89 };
90
91 struct _test_t my_dev;
92
93 static int __init test_init(void) //模块初始化函数
94 {
95 int result = 0;
96 my_dev.cur_size = 0;
97 my_dev.major = 0;
98 my_dev.minor = 0;
99
100 if(my_dev.major){
101 my_dev.devno = MKDEV(my_dev.major, my_dev.minor);
102 result = register_chrdev_region(my_dev.devno, 1, "test new driver") ;
103 }else{
104 result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver");
105 my_dev.major = MAJOR(my_dev.devno);
106 my_dev.minor = MINOR(my_dev.devno);
107 }
108
109 if(result < 0){
110 P_DEBUG("register devno errno!\n");
111 goto err0;
112 }
113
114 printk("major[%d] minor[%d]\n", my_dev.major, my_dev.minor);
115
116 cdev_init(&my_dev.test_cdev, &test_fops);
117 my_dev.test_cdev.owner = THIS_MODULE;
118 /*初始化等待队列头,注意函数调用的位置*/
119 init_waitqueue_head(&my_dev.test_queue);
120
121 result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1);
122 if(result < 0){
123 P_DEBUG("cdev_add errno!\n");
124 goto err1;
125 }
126
127 printk("hello kernel\n");
128 return 0;
129
130 err1:
131 unregister_chrdev_region(my_dev.devno, 1);
132 err0:
133 return result;
134 }
为了方便讲解,函数我精简了很多,红色好亮代码是新加的知识点,其他都是之前已经讲过的。
下面开始介绍上面使用的知识:
知识1)什么是等待队列。
前面说了进程休眠,而其他进程为了能够唤醒休眠的进程,它必须知道休眠的进程在哪里,出于这样的原因,需要有一个称为等待队列的结构体。等待队列是一个存放着等待某个特定事件进程链表。
在这里的程序,用于存放等待唤醒的进程。
既然是队列,当然要有个队列头,在使用等待队列之前,必须先定义并初始化等待队列头。
先看一下队列头的样子:
/*linux/wait.h*/
50 struct __wait_queue_head {
51 spinlock_t lock; //这个是自旋锁,在这里不需要理会。
52 struct list_head task_list; //这就是队列头中的核心,链表头。
53 };
54 typedef struct __wait_queue_head wait_queue_head_t;
说白了就是定义并初始化一个链表。以后就能够在这个链表添加需要等待的进程了。
定义并初始化队列头有两种方法:
1)静态定义并初始化,一个函数执行完两个操作。省力又省心。
DECLARE_WAIT_QUEUE_HEAD(name)
使用:定义并初始化一个叫name的等待队列。
2)分开两步执行。
2.1)定义
wait_queue_head_t test_queue;
2.2)初始化
init_waitqueue_head(&test_queue);
我使用的是第二种方法,这些都是在加载模块时应该完成的操作。其中,等待队列头的定义我放在”struct _test_t”结构体中,初始化放在模块加载函数中。
这里值得注意的是初始化函数的位置,它必须在cdev添加函数”cdev_add”前。因为”cdev_add”执行成功就意味着设备可以被操作,设备被操作前当然需要把所有的事情都干完,包括等待队列的初始化。
知识2)进程休眠
在test_read函数中就实现了进程休眠,使用了函数”wait_evenr_interruptible”。
wait_event_interruptible(wq, condition)
使用:
如果condition为真,函数将进程添加到等待队列头wq并等待唤醒。
返回值:
添加成功返回0。另外,interruptition的意思是休眠进程可以被某个信号中断中断,如果被中断,驱动程序应该返回-ERESTARTSYS。
这有一类的函数,操作跟”wait_evevt_interruptition”类似
wait_event(queue, condition)
/*函数成功会进入不可中断休眠,不推荐*/
wait_event_interruptible(queue, condition)
/*函数调用成功会进入可中断休眠,推荐,返回非零值意味着休眠被中断,且驱动应返回-ERESTARTSYS*/
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
/*比上面两个函数多了限时功能,若休眠超时,则不管条件为何值返回0,*/
上面的四个函数大致都是完成一下的操作:
以wait_event_interruptible(dev->test_queue,
dev->cur_size > 0)举例:
1、定义并初始化一个wait_queue_t结构体,然后将它添加到等待队列test_queue中。
2、更改进程的状态,休眠的状态有两种:(可中断休眠)TASK_INTERRUPTIBLE和(不可中断休眠)TASK_UNINTERRUPTIBLE。上面的函数会切换到可中断休眠。
3、判断条件
dev->cur_size > 0是否成立,如果不成立,则调用函数schedule()让出CPU。注意,一旦让出CPU进入休眠后,进程再次被唤醒后就会从这一步开始,再次检测条件是否成立,如果还是不成立,继续让出CPU,等待下一次的唤醒。如果成立,则进行下一步的操作。所以,这个函数的条件会被多次判断,因此这个判断语句并不能对这个进程带来任何副作用。
4、条件成立后做一些相应的清理工作,并把进程