Linux设备驱动模板:等待队列


我们为什么要用等待队列?

假设我们在 kernel 里产生一个 buffer,user 可以经由 read,write 等 system call 来读取或写资料到这个 buffer 里。如果有一个 user 写资料到 buffer 时,此时 buffer 已经满了。那请问你要如何去处理这种情形呢 ? 
第一种,传给 user 一个错误讯息,说 buffer 已经满了,不能再写入。
第二种,将 user 的要求 block 住,等有人将 buffer 内容读走,留出空位时,再让 user 写入资料。
但问题来了,你要怎么将 user 的要求 block 住。难道你要用
    while ( is_full );
    write_to_buffer;
这样的程序代码吗? 想想看,如果你这样做会发生什么事? 
第一,kernel会一直在这个 while 里执行。
第二,如果 kernel 一直在这个 while 里执行,表示它没有办法去 maintain系统的运作。那此时系统就相当于当掉了。
在这里 is_full 是一个变量,当然,你可以让 is_full 是一个 function,在这个 function里会去做别的事让 kernel 可以运作,那系统就不会死。这是一个方式。还有,你说可以在while里面把buffer里的内容读走,再把is_full的值改了,但是我们会可能把重 要的数据在我们不想被读的时候被读走了,那是比较麻烦的,而且很不灵活.
如果我们使用 wait_queue 的话,那程序看起来会比较漂亮,而且也比较让人了解,如下所示:

struct wait_queue_head_t wq; /* global variable */

DECLARE_WAIT_QUEUE_HEAD (wq);

while ( is_full )

{

    interruptible_sleep_on( &wq );

write_to_buffer();

interruptible_sleep_on( &wq ) 是用来将目前的 process,也就是要求写资料到buffer 的 process放到 wq 这个 wait_queue 里。在 interruptible_sleep_on 里,则是最后会呼叫 schedule() 来做 schedule 的动作,谁调用了schedule谁就趴下,让别人去运行,醒来就原地起来,执行schedule()后的代码。那那个调用了schedule的家伙什么 醒过来呢?这时候就需要用到另一个函数了wake_up_interruptible()了。

结构图:

  1.                                task_struct             task_struct  
  2.                               |---------|<--+         |---------|<--+  
  3.                               |         |   |         |         |   |  
  4.                               |         |   |         |         |   |  
  5.                               |         |   |         |         |   |  
  6.                               |---------|   |         |---------|   |  
  7.                                             |                       |  
  8.                           wait_queue_t      |     wait_queue_t      |  
  9.                         |---------------|   |   |---------------|   |  
  10. wait_queue_head_t       |    flags      |   |   |    flags      |   |  
  11. |---------------|       |    *task      |---+   |    *task      |---+  
  12. |     lock      |       |    func       |       |    func       |  
  13. |---------------|       |---------------|       |---------------|  
  14. |list_head *next|------>|list_head *next|------>|list_head *next|  
  15. |list_head *prev|<------|list_head *prev|<------|list_head *prev|  
  16. |---------------|       |---------------|       |---------------|  

linux中最简单的休眠方式: 
wait_event(queue, condition)  /* 进程将被置于非中断休眠(uninterruptible sleep)*/
wait_event_interruptible(queue, condition) /*进程可被信号中断休眠,返回非0值表示休眠被信号中断*/
wait_event_timeout(queue, condition, timeout)    /*等待限定时间jiffy,condition满足其一返回0*/ 
wait_event_interruptible_timeout(queue, condition, timeout) 

queue是等待队列头,传值方式

condition是任意一个布尔表达式,在休眠前后多次对condition求值,为真则唤醒

唤醒进程的基本函数:
void wake_up(wait_queue_head_t *queue);     /*唤醒等待在给定queue上的所有进程*/
void wake_up_interruptible(wait_queue_head_t *queue); 

总结一下Linux等待等列用法:

1、定义:wait_queue_head_t my_queue;
2、初始化 init_waitqueue_head(&my_queue);
3、在一个函数里面等待:wait_event_interruptible(queue, condition) ;(别在中断里面调用)
4、在另一个函数里面唤醒:wake_up_interruptible(wait_queue_head_t *queue); (这个可以在中断调用,去唤醒别的进程,特别是DMA操作等)

最后附上一个小的模板:

  1. struct xxx_dev    
  2. {    
  3.     struct cdev cdev;    
  4.     wait_queue_head_t r_wait;    
  5.     wait_queue_head_t w_wait;    
  6.     bool have_data;  
  7. };     
  8.   
  9. struct xxx_dev *xxx_devp;  
  10.   
  11. static ssize_t xxx_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)  
  12. {  
  13.     struct xxx_dev *dev = filp->private_data;  
  14.     ......  
  15.     /* 注意:使用的while,而不是if */  
  16.     while(!have_data)  
  17.     {  
  18.         /* 如果用户设置了非阻塞的方式,则不需要调用等待队列 */  
  19.         if (filp->f_flags & O_NONBLOCK)  
  20.             return -EAGAIN;  
  21.         /* interruptible:进程的唤醒有可能是其他信号 */  
  22.         else  
  23.             wait_event_interruptible(xxx_devp->r_wait,have_data);  
  24.     }  
  25.     ......  
  26. }  
  27.   
  28. static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)  
  29. {  
  30.     struct xxx_dev *dev = filp->private_data;  
  31.     ......  
  32.     have_data = true;  
  33.     wake_up_interruptible(&xxx_devp->r_wait);    /* 唤醒等待队列 */  
  34.     ......  
  35. }  
  36.   
  37. static int memdev_init(void)  
  38. {  
  39.     ......  
  40.     xxx_devp = kmalloc(sizeof(struct xxx_dev), GFP_KERNEL);  
  41.     ......  
  42.     xxx_devp->have_data = false;  
  43.     init_waitqueue_head(&xxx_devp->r_wait);  /*初始化读等待队列头 */  
  44.     init_waitqueue_head(&xxx_devp->w_wait);  /*初始化写等待队列头 */  
  45.     ......  
  46. }  

相关内容