Linux设备驱动中的阻塞与非阻塞I/O

阻塞和非阻塞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 
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值