Linux驱动编程 step-by-step (八) 阻塞型字符设备驱动

转载 2012年03月21日 10:24:02

阻塞型字符设备驱动

前面说到了 如何实现read write 等操作,但如果设备缓冲已满,现在想而用户此时又想写入设备,此请求就无法立即执行,那怎么办呢?
第一种情况是:驱动程序想用户返回请求失败的信息。
第二种情况是:使调用进程阻塞等待设备可以被操作。

而用户更希望自己选择在请求无法满足时候如何操作,所以在用户空间有了O_NONBLOCK标志
在打开设备的时候如果用户指定了此标志(会保存到filp->f_flags中),表示用户希望以非阻塞的形式打开设备,读写时如果设备不能满足要求,则返回错误码(-EAGAIN).
如果用户不指定此标志位,则默认是阻塞型打开,在设备不能满足请求时候需要就阻塞直到设备可以被操作。

休眠的介绍

进程被阻塞即意味着释放CPU,直到另一个进程修改了某个状态,使调度器再次去调度他,所以进程休眠后不知道自己在休眠期间做了什么事情,所以在被唤醒之后还需要检查当前状态,看等待的条件是否为真
在linux中等待队列通过等待队列头来管理,(wait_queue_head_t)
初始化队列头
  1. init_waitqueue_head(wait_queue_head_t * queue);  

还有一种简单的方式去初始化队列头(静态初始化)

  1. DECLARE_WAIT_QUEUE_HEAD(name)  
它会将name 定义为一个 struct wait_queue_head_t 结构,并初始化

使进程休眠

让一个进程进入睡眠,在醒来时进程仍需要检测一个条件是否为真,正如上边提到的一样
  1. #define wait_event(wq, condition)   
  2. #define wait_event_timeout(wq, condition, timeout)  
  3. #define wait_event_interruptible(wq, condition)  
  4. #define wait_event_interruptible_timeout(wq, condition, timeout)  
wq: 指对应的等待队列头

condition 表示检测的的条件,在condition为真时,睡眠终止。

含有 interruptible 的函数 表示睡眠可以被信号中断,当睡眠被信号中断事,函数返回负值(-ERESTARTSYS)

含有timeout 的函数 timeout 参数表示最长的等待时间,

进程唤醒

有睡眠就有唤醒

在其他进程操作了设备 使等待进程的 等待条件(condition)为真,则需要在此进程中唤醒睡眠的进程

  1. #define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)  
  2. #define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL)   
  3. #define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)  
  4. #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)  
  5. #define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)  
wake_up 会首先唤醒队列上1个独占等待的进程(下边有简单的解释),如果没有则去唤醒其他所有的进程

wake_up_all 唤醒等待队列上的所有进程,不管独占 还是 非独占

wake_up_interruptible跟wake_up 类似, 不过他只会唤醒 队列中可中断的睡眠进程

wake_up_interruptible_all 会唤醒等待队列上的所有可中断睡眠,

wake_up_interruptible_nr 回唤醒等待队列上的nr个独占进程独占等待进程 : 因为在一个睡眠队列上,睡眠的进程可能会有很多,而每次唤醒之后只有一个进程唤醒执行,其他进程继续在等待队列上睡眠,这就导致了有些=重要的进程迟迟不呢个得到调用,为改善这个情况,有了独占进程的概念, 在睡眠时候我们可以指定一个参数WQ_FLAG_EXCLUSEVE标志会加到队列的尾部(不加此标志则默认加到队列头部), 在调用wake_up_xx 对应的函数时候会首先唤醒这些进程。

  1. while(dev->datawr == dev->datard)  
  2. {  
  3.         
  4.       while(dev->datard == dev->datawr) //循环检测等待条件是否真正的满足  
  5.       {  
  6.           up(&dev->semp)  
  7.           if(filp->f_flags & O_NONBLOCK) //判断是否是非阻塞打开  
  8.           {  
  9.                 return -EAGAIN;  
  10.           }  
  11.           D("[%s] reading going to sleep!", current->comm);  
  12.       
  13.           if(wait_event_interruptible(dev->rdque, dev->datard != dev->datawr)) //使当前进程睡眠在读睡眠队列  
  14.           {  
  15.                 return -ERESTARTSYS;  
  16.           }  
  17.           D("waked up %d\n", __LINE__);  
  18.       
  19.           ...  
  20.       
  21.           if(down_interruptible(&dev->semp) < 0)//获取锁  
  22.           {  
  23.                 printk(KERN_ERR "[%s]get the mutex lock error %d, %s",  
  24.                                  current->comm, __LINE__, __func__);  
  25.                 return -ERESTARTSYS;  
  26.            }  
  27.            else  
  28.            {  
  29.                 D("have get the mutex %d\n", __LINE__);  
  30.            }  
  31.          }  
  32.            
  33.          /*read data*/  
  34.          wake_up_interruptible(dev->wrque)//读出数据 唤醒写睡眠队列上的进程  
  35. }  

poll 函数实现

  1. unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait);  
在系统编程时候我们经常会只用到poll 或者 select函数去监听某个文件描述符是否可写或者可读等
这些系统调用都是通过poll函数驱动实现
当调用此函数时候内核会分配一个 poll_table_struct 结构,我们 多需要的动作有两步
1、在等待队列上调用poll_wait
  1. static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)  
2、返回一个描述设备可读可写的掩码
  1. #define POLLIN      0x0001 //设备可读  
  2. #define POLLPRI     0x0002 //无阻塞读取高优先级数据  
  3. #define POLLOUT     0x0004 //设备可写入  
  4. #define POLLERR     0x0008 //设备发生错误  
  5. #define POLLHUP     0x0010 //设备挂起  
  6. #define POLLRDNORM      0x0040 //normal data is ready now  
  7. #define POLLWRNORM  0x0100 //normal data can be writen to device  
example:
  1. static int simple_poll(struct file *filp, struct poll_table_struct *wait)  
  2. {  
  3.     struct simple_dev *dev = filp->private_data;  
  4.     unsigned int mask = 0;  
  5.     char *next_ptr;  
  6.   
  7.     if(down_interruptible(&dev->semp))  
  8.     {  
  9.         printk(KERN_ERR "get the semphore err %d \n",__LINE__);  
  10.         return -ERESTARTSYS;  
  11.     }  
  12.   
  13.     D("have get the semphore %d\n", __LINE__);  
  14.   
  15.     poll_wait(filp, &dev->inq, wait);  
  16.     poll_wait(filp, &dev->outq, wait);  
  17.   
  18.     if(dev->datard != dev->datawr)  
  19.     {  
  20.         mask |= POLLIN | POLLRDNORM;   //can be read  
  21.     }  
  22.       
  23.     if(dev->datawr+1 == dev->dataend)  
  24.         next_ptr = dev->data;  
  25.     else  
  26.         next_ptr = dev->datawr+1;  
  27.       
  28.     if(next_ptr != dev->datard)  
  29.     {  
  30.         mask |= POLLOUT | POLLWRNORM; //can be write  
  31.     }  
  32.   
  33.     up(&dev->semp);  
  34.     return mask;  
  35. }  

下一节 会以一个驱动程序 模拟管道 结束基于内存的模拟字符设备驱动程序 ,  同时介绍 驱动测试的相关东东

Linux驱动编程 step-by-step (八) 阻塞型字符设备驱动

阻塞型字符设备驱动 前面说到了 如何实现read write 等操作,但如果设备缓冲已满,现在想而用户此时又想写入设备,此请求就无法立即执行,那怎么办呢? 第一种情况是:驱动程序想用户返回请求失败...
  • a8039974
  • a8039974
  • 2014年11月14日 22:46
  • 425

Linux驱动编程 step-by-step (八)

阻塞型字符设备驱动 前面说到了 如何实现read write 等操作,但如果设备缓冲已满,现在想而用户此时又想写入设备,此请求就无法立即执行,那怎么办呢? 第一种情况是:驱动程序想用户返回请求失败...
  • jshazk1989
  • jshazk1989
  • 2011年11月24日 00:31
  • 2403

Linux驱动编程 step-by-step (二) 简单字符设备驱动

简单字符设备驱动 1、主次设备号 主设备号标识设备连接的的驱动,此设备好由内核使用,标识在相应驱动下得对应的设备 在linux中设备号是一个32位的dev_t类型 typedef __u3...
  • a8039974
  • a8039974
  • 2014年11月14日 22:44
  • 462

Linux驱动编程 step-by-step (一)

转载于: http://blog.csdn.net/jshazk1989/article/details/6908472 驱动程序的作用: 简单来说 驱动程序就是使计算机与设备通...
  • guoyaoyao1990
  • guoyaoyao1990
  • 2014年07月13日 16:57
  • 346

Linux驱动编程 step-by-step (十一)

Linux 内核链表(2) 之前描述了如何创建内核链表(INIT_LIST_HEAD)向链表中添加节点(list_add)删除一个链表节点(list_del)获取一个链表节点对应的结构体(list_...
  • a8039974
  • a8039974
  • 2014年11月14日 22:50
  • 291

Linux驱动编程 step-by-step (七)

并发 竞态 (信号量与自旋锁) 代码传至并发竞态控制 并发进程 导致竞态的一个例子 前面所述的字符驱动都是没有考虑并发竟态的情况,想象一下 一个进程去读一个字符设备,另一个进程在同...
  • jshazk1989
  • jshazk1989
  • 2011年11月17日 00:24
  • 5729

Linux驱动编程 step-by-step (四)

似乎每一章介绍的内容比较少,但学习是一个循序渐进的过程,不在于一天学多少,重要的一天能真正的学懂多少,所以我主张一步一步来,从多个渠道去学习知识,实现互补。 本节测试代码传到此处了:char_ste...
  • jshazk1989
  • jshazk1989
  • 2011年11月05日 23:48
  • 3308

Linux驱动编程 step-by-step (三)

字符设备中 重要的数据结构 大部分字符驱动设计三个重要的数据结构 struct file_operations struct file struct inode   一、文件操...
  • jshazk1989
  • jshazk1989
  • 2011年11月05日 16:11
  • 3061

Linux驱动编程 step-by-step (五)

主要的文件操作方法实现 文件操作函数有很多的操作接口,驱动编程需要实现这些接口,在用户编程时候系统调用时候会调用到这些操作 struct file_operations { ... loff_...
  • jshazk1989
  • jshazk1989
  • 2011年11月11日 00:35
  • 7724

字符型设备驱动程序--gpio 驱动实例

概述: 字符设备驱动程序: 是按照字符设备要求完成的由操作系统调用的代码。 重点理解以下内容:  1. 驱动是写给操作系统的代码,它不是直接给用户层程序调用的,而是给系统调用的  2. 所以驱动要向系...
  • hejinjing_tom_com
  • hejinjing_tom_com
  • 2014年05月09日 18:03
  • 4896
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux驱动编程 step-by-step (八) 阻塞型字符设备驱动
举报原因:
原因补充:

(最多只允许输入30个字)