阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式
本例子讲述了这两者的区别 并实现I/O的等待队列机制, 并进行了用户空间的验证
基本概念:
1> 阻塞操作 是指 在执行设备操作时,若不能获得资源,则挂起进程直到满足操作条件后再进行操作。被挂起的进 程进入休眠, 被从调度器移走,直到条件满足。
2> 非阻塞操作 在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作。非阻塞应用 程序通常使 用select系统调用查询是否可以对设备进行无阻塞的访问最终会引发设备驱动中poll函数执行。
1 /*
2 * a globalfifo driver as example of char device drivers
3 * This example is to introduce poll, blocking and non-blocking access
4 *
5 *The initial developer of the original code is Baohua Song
6 *<auther@linuxdriver.cn>. All Rights Reserved
7 *
8 * 1>只当FIFO 中有数据时(有进程把数据写到这个 FIFO而且没有被 读进程 读空)
9 * 读进程才能把数据读出,读出后数据 从 FIFO 中拿掉
10 * 2>只有当FIFO 非满时(即还有空间未被读写或满后被读进程读出了数据)
11 * 写进程才能往里面写数据,
12 * 这样 读唤醒写 写唤醒读
13 */
14
15 #include<linux/module.h>
16 #include<linux/types.h>
17 #include<linux/fs.h>
18 #include<linux/errno.h>
19 #include<linux/mm.h>
20 #include<linux/sched.h>
21 #include<linux/init.h>
22 #include<linux/cdev.h>
23 #include<asm/io.h>
24 #include<asm/system.h>
25 #include<asm/uaccess.h>
26 #include<linux/poll.h>
27
28 #define GLOBALFIFO_SIZE 10 /*全局fifo最大10字节 不要太大 方便写满测试*/
29 #define FIFO_CLEAR 0X1 /*清0全局内存的长度*/
30 #define GLOBALFIFO_MAJOR 249 /*预设的globalfifo 的主设备号*/
31
32 static int globalfifo_major = GLOBALFIFO_MAJOR;
33
34 /*globalfifo设备结构体*/
35 struct globalfifo_dev{
36 struct cdev cdev; /*cdev结构体*/
37 unsigned int current_len; /*fifo有效数据长度*/
38 unsigned char mem[GLOBALFIFO_SIZE]; /*全局内存*/
39 struct semaphore sem; /*并发控制用的信号量*/
40 wait_queue_head_t r_wait; /*阻塞读用的等待队列 内核双向循环链表 都可以为头*/
41 wait_queue_head_t w_wait; /*阻塞写用的等待队列头*/
42 };
43
44 struct globalfifo_dev *globalfifo_devp; /*设备结构体指针*/
45
46 /*globalfifo读函数*/
47 static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
48 {
49 int ret;
50 struct globalfifo_dev *dev = filp->private_data;
51 DECLARE_WAITQUEUE(wait, current);
52
53 down(&dev->sem); /*获得信号量*/
54 add_wait_queue(&dev->r_wait, &wait); /*加入读等待队列头 到内核*/
55
56 /*等待FIFO 非空*/
57 if(dev->current_len == 0){
58 if(filp->f_flags & O_NONBLOCK){ /*如果进程为 非阻塞打开 设备文件*/
59 ret = -EAGAIN;
60 goto out;
61 }
62 __set_current_state(TASK_INTERRUPTIBLE); /*改变进程状态为睡眠*/
63 up(&dev->sem); /*释放信号量*/
64
65 schedule(); /*调度其他进程执行*/ //需要主动调用schedule()让出CPU 等待wakeup(&_r_wait)
检查当前进程是否有信号处理,返回不为0表示有信号需要处理。
返回 -ERESTARTSYS 表示信号函数处理完毕后重新执行信号函数前的某个系统调用。也就是说,如果信号函数前有发生系统调用,在调度信号处理函数之前,内核会检查系统调用的返回值,看看是不是因为这个信号而中断了系统调用.
如果返回值-ERESTARTSYS,并且当前调度的信号具备-ERESTARTSYS属性,系统就会在用户信号函数返回之后再执行该系统调用。
66 if(signal_pending(current)){ //被wakeup(&_r_wait) 信号唤醒
67 /*如果是因为信号唤醒*/
68 ret = -ERESTARTSYS;
69 goto out2;
70 }
71 down(&dev->sem);
72 }
73
74 /*拷贝到用户空间*/
75 if(count > dev->current_len)
76 count = dev->current_len;
77 if(copy_to_user(buf, dev->mem, count)){
78 ret = -EFAULT;
79 goto out;
80 }else{
81 memcpy(dev->mem, dev->mem + count, dev->current_len - count);/*数据前移*/
82 dev->current_len -= count; /*有效数据长度减少*/
83 printk(KERN_INFO"read %d bytes(s),current_len:%d\n",count, dev->current_len);
84
85 wake_up_interruptible(&dev->w_wait); /*唤醒写等待队列*/
86 ret = count;
87 }
88 out:
89 up(&dev->sem); /*释放信号量*/
90 out2:
91 remove_wait_queue(&dev->w_wait, &wait); /*从属的等待队列头移除*/
92 set_current_state(TASK_RUNNING);
93 return ret;
94 }
95
96 /*globalfifo 写操作*/
97 static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
98 {
99 struct globalfifo_dev *dev = filp->private_data;
100 int ret;
101 DECLARE_WAITQUEUE(wait, current); /*定义等待队列*/
102
103 down(&dev->sem); /*获得信号量*/
104 add_wait_queue(&dev->w_wait, &wait); /*进入写等待队列头*/
105
106 /*等待FIFO非满*/
107 if(dev->current_len == GLOBALFIFO_SIZE){
108 if(filp->f_flags & O_NONBLOCK){ /*如果进程非阻塞打开的文件*/
109 ret = -EAGAIN;
110 goto out;
111 }
112
113 __set_current_state(TASK_INTERRUPTIBLE); /*改变进程状态为睡眠*/
114 up(&dev->sem); /*释放信号量*/
115
116 schedule(); /*调度其他进程执行*/
117 if(signal_pending(current)){
118 /*如果是因为信号唤醒*/
119 ret = -ERESTARTSYS;
120 goto out2;
121 }
122 down(&dev->sem); /*获得信号量*/
123 }
124
125 /*从用户空间拷贝数据到内核空间*/
126 if(count > GLOBALFIFO_SIZE - dev->current_len){
127 /*如果要拷贝的数据大于 剩余有效内存长度
128 *则 只拷贝最大 能装下的长度
129 */
130 count = GLOBALFIFO_SIZE - dev->current_len;
131 }
132 if(copy_from_user(dev->mem + dev->current_len, buf, count)){
133 ret = -EFAULT;
134 goto out;
135 }else {
136 dev->current_len += count;
137 printk(KERN_INFO"written %d bytes(s), current_len: %d\n",count, dev->current_len);
138
139 wake_up_interruptible(&dev->r_wait); /*唤醒读等待队列*/
140 ret = count;
141 }
142 out:
143 up(&dev->sem); /*释放信号量*/
144 out2:
145 remove_wait_queue(&dev->w_wait, &wait); /*从附属的等待队列头移除*/
146 set_current_state(TASK_RUNNING);
147 return ret;
148 }
149
150
151 /*ioctl 设备控制函数*/
152 static int globalfifo_ioctl(struct inode *inodep,struct file *filp, unsigned int cmd, unsigned long arg)
153 {
154 struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/
155
156 switch(cmd){
157 case FIFO_CLEAR:
158 down(&dev->sem); /*获得信号量*/
159 dev->current_len = 0;
160 memset(dev->mem, 0, GLOBALFIFO_SIZE);
161 up(&dev->sem); /*释放信号量*/
162
163 printk(KERN_INFO"globalfifo is set to zero\n");
164 break;
165
166 default:
167 return -EINVAL;
168 }
169 return 0;
170 }
171
172 /*在驱动中的增加轮询操作*/
173 static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
174 {
175 unsigned int mask = 0;
176 struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/
177
178 down(&dev->sem);
179 poll_wait(filp, &dev->r_wait, wait);
180 poll_wait(filp, &dev->w_wait, wait);
181
182 /*fifo非空*/
183 if(dev->current_len != 0){
184 mask |= POLLIN | POLLRDNORM; /*标示数据可以获得*/
185 }
186
187 /*fifo 非满*/
188 if(dev->current_len != GLOBALFIFO_SIZE){
189 mask |= POLLOUT | POLLWRNORM ; /*标示数据可以写入*/
190 }
191
192 up(&dev->sem);
193 return mask; /*返回驱动是否可读 或可写的 状态*/
194 }
195
196 /*文件打开函数*/
197 int globalfifo_open(struct inode *inode, struct file *filp)
198 {
199 /*让设备结构体作为设备的私有信息*/
200 filp->private_data = globalfifo_devp;
201 return 0;
202 }
203
204 /*文件释放函数*/
205 int globalfifo_release(struct inode *inode, struct file *filp)
206 {
207 return 0;
208 }
209
210 /*文件操作结构体*/
211 static const struct file_operations globalfifo_fops = {
212 .owner = THIS_MODULE,
213 .read = globalfifo_read,
214 .write = globalfifo_write,
215 .ioctl = globalfifo_ioctl,
216 .poll = globalfifo_poll,
217 .open = globalfifo_open,
218 .release = globalfifo_release,
219 };
220
221 /*初始化并注册cdev*/
222 static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
223 {
224 int err, devno = MKDEV(globalfifo_major, index);
225
226 cdev_init(&dev->cdev, &globalfifo_fops);
227 dev->cdev.owner = THIS_MODULE;
228 err = cdev_add(&dev->cdev, devno, 1);
229 if(err)
230 printk(KERN_NOTICE "Error %d adding LED %d", err, index);
231 }
232
233 /*设备驱动模块加载函数*/
234 int globalfifo_init(void)
235 {
236 int ret;
237 dev_t devno = MKDEV(globalfifo_major, 0);
238
239 /*申请设备号*/
240 if(globalfifo_major)
241 ret = register_chrdev_region(devno, 1, "globalfifo");
242 else{/*动态申请设备号*/
243 ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
244 globalfifo_major = MAJOR(devno);
245 }
246
247 if(ret < 0)
248 return ret;
249
250 /*动态申请设备结构体的内存*/
251 globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
252 if(!globalfifo_devp){
253 ret = - ENOMEM;
254 goto fail_malloc;
255 }
256
257 memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
258
259 globalfifo_setup_cdev(globalfifo_devp, 0);
260
261 init_MUTEX(&globalfifo_devp->sem); /*初始化信号量*/
262 init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化读等待队列头*/
263 init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化写等待队列头*/
264
265 return 0;
266
267 fail_malloc: unregister_chrdev_region(devno, 1);
268 return ret;
269 }
270
271 void globalfifo_exit(void)
272 {
273 cdev_del(&globalfifo_devp->cdev); /*注销cdev*/
274 kfree(globalfifo_devp); /*释放设备结构体内存*/
275 unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*释放设备号*/
276 }
277
278 MODULE_AUTHOR("Song Baohua");
279 MODULE_LICENSE("Dual BSD/GPL");
280
281 //module_param()
282
283 module_init(globalfifo_init);
284 module_exit(globalfifo_exit);
阻塞测试:
驱动的轮询操作(非阻塞) 测试程序:
1 /*测试程序:该程序用于监控 globalfifo 的可读 可写状态(是否可以非阻塞读写)*/
2 #include<stdio.h>
3 #include<sys/select.h>
4 #include<fcntl.h>
5
6 #define FIFO_CLEAR 0X1
7 #define BUFFER_LEN 20
8
9 int main(void)
10 {
11 int fd, num;
12 char rd_ch[BUFFER_LEN];
13 fd_set rfds, wfds; /*读写文件描述符集*/
14
15 /*以非阻塞的方式打开 设备文件*/
16 fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);
17 if(fd != -1){
18 /*FIFO 清零*/
19 //if(ioctl(fd, FIFO_CLEAR, 0) < 0)
20 // printf("ioctl command failed\n");
21
22 while(1){
23 FD_ZERO(&rfds); /*清除一个文件描述符集*/
24 FD_ZERO(&wfds);
25 FD_SET(fd, &rfds); /*将一个文件描述符 加入 文件描述符集中*/
26 FD_SET(fd, &wfds);
27
28 /*最高文件描述符+1 要监控的 读 写 异常 等待超时返回*/
29 select(fd + 1, &rfds, &wfds, NULL, NULL); /*该函数最终调用poll*/
30
31 /*数据可获得*/
32 if(FD_ISSET(fd, &rfds))
33 printf("Poll monitor:can be read\n");
34
35 /*数据可写入*/
36 if(FD_ISSET(fd, &wfds))
37 printf("Poll monitor:can be written\n");
38 }
39 }else{
40 printf("Device open failure\n");
41 }
42 return 0;
43 }
每次实验前都要先把之前后台还在运行的测试杀死以避免对接下来实验的干扰:
1>FIFO 为空 只可写
2>FIFO 为满 只可读
3>FIFO 不满 不空 可读可写
参考文献 :《LINUX设备驱动程序》 (第三版)
《设备驱动开发详解》(第二版) 宋宝华
《UNIX环境高级编程》 (第二版)
---------------------
作者:wenhui_
来源:CSDN
原文:https://blog.csdn.net/wenhui_/article/details/6817659
版权声明:本文为博主原创文章,转载请附上博文链接!