Linux 工作队列和等待队列的区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 工作队列和等待队列的区别相关的知识,希望对你有一定的参考价值。

work queue是一种bottom half,中断处理的后半程,强调的是动态的概念,即work是重点,而queue是其次。
wait queue是一种「任务队列」,可以把一些进程放在上面睡眠等待某个事件,强调静态多一些,重点在queue上,即它就是一个queue,这个queue如何调度,什么时候调度并不重要
等待队列在内核中有很多用途,尤其适合用于中断处理,进程同步及定时。这里只说,进程经常必须等待某些事件的发生。例如,等待一个磁盘操作的终止,等待释放系统资源,或者等待时间经过固定的间隔。
等待队列实现了在事件上的条件等待,希望等待特定事件的进程把放进合适的等待队列,并放弃控制权。因此。等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒进程。
等待队列由循环链表实现,其元素包括指向进程描述符的指针。每个等待队列都有一个等待队列头,等待队列头是一个类型为wait_queue_head_t的数据结构。
等待队列链表的每个元素代表一个睡眠进程,该进程等待某一事件的发生,描述符地址存放在task字段中。然而,要唤醒等待队列中所有的进程有时并不方便。例如,如果两个或多个进程在等待互斥访问某一个要释放的资源,仅唤醒等待队列中一个才有意义。这个进程占有资源,而其他进程继续睡眠可以用DECLARE_WAIT_QUEUE_HEAD(name)宏定义一个新的等待队列,该宏静态地声明和初始化名为name的等待队列头变量。 init_waitqueue_head()函数用于初始化已动态分配的wait queue head变量等待队列可以通过DECLARE_WAITQUEUE()静态创建,也可以用init_waitqueue_head()动态创建。进程放入等待队列并设置成不可执行状态。
工作队列,workqueue,它允许内核代码来请求在将来某个时间调用一个函数。用来处理不是很紧急事件的回调方式处理方法.工作队列的作用就是把工作推后,交由一个内核线程去执行,更直接的说就是写了一个函数,而现在不想马上执行它,需要在将来某个时刻去执行,那就得用工作队列准没错。
如果需要用一个可以重新调度的实体来执行下半部处理,也应该使用工作队列。是唯一能在进程上下文运行的下半部实现的机制。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,都会非常有用。
参考技术A 打个比喻,你去银行办理业务,银行只开四个窗口,即工作队列为4。但你取号的时候,前面还有30人,即等待队列是30 。本回答被提问者和网友采纳 参考技术B 工作队列很忙,等待队列不忙

linux内核数据结构之等待队列

Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。


在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。从上图可以清晰看到。所以我们知道,如果要实现一个等待队列,首先要有两个部分。队列头和队列项。下面看他们的数据结构。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct  list_head      struct  list_head *next, *prev; ;     struct  __wait_queue_head      spinlock_t lock;      struct  list_head task_list; ;     typedef  struct  __wait_queue_head wait_queue_head_t;     struct  __wait_queue      unsigned  int  flags; #define WQ_FLAG_EXCLUSIVE    0x01      void  * private ; //2.6版本是采用void指针,而以前的版本是struct task_struct * task;                              //实际在用的时候,仍然把private赋值为task      wait_queue_func_t func;      struct  list_head task_list; ;

 所以队列头和队列项是通过list_head联系到一起的,list_head是一个双向链表,在linux内核中有着广泛的应用。并且在list.h中对它有着很多的操作。 

2.对列头和队列项的初始化:
 wait_queue_head_t my_queue;

init_waitqueue_head(&my_queue);

动态直接定义并初始化。init_waitqueue_head()函数会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。

静态定义并初始化

DECLARE_WAIT_QUEUE_HEAD(my_queue);

3.定义等待队列: DECLARE_WAITQUEUE(name,tsk);

[cpp]  view plain copy
  1. #define   DECLARE_WAITQUEUE(name,   tsk)       /   
  2. wait_queue_t   name     =__WAITQUEUE_INITIALIZER(name,   tsk)   
  3.   
  4. #define   __WAITQUEUE_INITIALIZER(name,   tsk)            task:     tsk,        task_list:    NULL,   NULL   ,  __WAITQUEUE_DEBUG_INI(name)   

它的解释是: 
通过DECLARE_WAITQUEUE宏将等待队列项初始化成对应的任务结构,并且用于连接的相关指针均设置为空。其中加入了调试相关代码。 
进程通过执行下面步骤将自己加入到一个等待队列中:
1) 调用DECLARE_WAITQUEUE()创建一个等待队列的
项;
2) 调用add_wait_queue()把自己加入到等待队列中。该队列会在进程等待的条件满足时唤醒它。在其他地方写相关代码,在事件发生时,对等的队列执行wake_up()操作。
3) 将进程状态变更为: TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE。
4) 如果状态被置为TASK_INTERRUPTIBLE ,则信号唤醒进程。即为伪唤醒(唤醒不是因为事件的发生),因此检查并处理信号。
5) 检查condition是否为真,为真则没必要休眠,如果不为真,则调用scheduled()。
6) 当进程被唤醒的时候,它会再次检查条件是否为真。真就退出循环,否则再次调用scheduled()并一直重复这步操作。
7) condition满足后,进程将自己设置为TASK_RUNNING 并通过remove_wait_queue()退出。

 4.(从等待队列头中)添加/移出等待队列

(1)add_wait_queue()函数: (2)remove_wait_queue()函数:

 5.等待事件:(有条件睡眠

1)wait_event()宏:

[cpp]  view plain copy
  1. #define wait_event(wq, condition) /   
  2.   
  3. do  /   
  4. if (condition) /   
  5. break; /   
  6. __wait_event(wq, condition); /   
  7.  while (0)   
  8.    
  9. #define __wait_event_timeout(wq, condition, ret) /   
  10.   
  11. do  /   
  12. DEFINE_WAIT(__wait); /   
  13. /   
  14. for (;;)  /   
  15. prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); /   
  16. if (condition) /   
  17. break; /   
  18. ret = schedule_timeout(ret); /   
  19. if (!ret) /   
  20. break; /   
  21.  /   
  22. finish_wait(&wq, &__wait); /   
  23.  while (0)   

 在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值.

(2)wait_event_interruptible()函数:

wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是condition为真,则返回0.

(3)wait_event_timeout():

也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.

(4)wait_event_interruptible_timeout():

wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码.

(5) wait_event_interruptible_exclusive()

同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程.

 6.唤醒队列:

(1)wake_up()函数:

唤醒等待队列.可唤醒处于TASK_INTERRUPTIBLETASK_UNINTERUPTIBLE状态的进程,wait_event/wait_event_timeout成对使用.

2)wake_up_interruptible()函数: #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程.,wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成对使用.

 TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);

TASK_UNINTERRUPTIBLE,不接收任何 singal

 7.在等待队列上睡眠:(无条件睡眠,老内核使用,新内核建议不用)

 (1)sleep_on()函数:

该函数的作用是定义一个等待队列(wait),并将当前进程添加到等待队列中(wait),然后将当前进程的状态置为TASK_UNINTERRUPTIBLE,并将等待队列(wait)添加到等待队列头(q)中。之后就被挂起直到资源可以获取,才被从等待队列头(q)中唤醒,从等待队列头中移出。在被挂起等待资源期间,该进程不能被信号唤醒。

(2)sleep_on_timeout()函数:

 sleep_on()函数的区别在于调用该函数时,如果在指定的时间内(timeout)没有获得等待的资源就会返回。实际上是调用schedule_timeout()函数实现的。值得注意的是如果所给的睡眠时间(timeout)小于0,则不会睡眠。该函数返回的是真正的睡眠时间。

 (3)interruptible_sleep_on()函数:

该函数和sleep_on()函数唯一的区别是将当前进程的状态置为TASK_INTERRUPTINLE,这意味在睡眠如果该进程收到信号则会被唤醒。

(4)interruptible_sleep_on_timeout()函数:

类似于sleep_on_timeout()函数。进程在睡眠中可能在等待的时间没有到达就被信号打断而被唤醒,也可能是等待的时间到达而被唤醒。


来源: <http://blog.csdn.net/funy_liu/article/details/5321147>  

以上是关于Linux 工作队列和等待队列的区别的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核的基础设施:工作队列等待队列

Linux 内核 唤醒等待队列的时间多久?

在Linux驱动程序中,使用等待队列的作用?

linux中的等待队列-51

linux中的等待队列-51

linux中的等待队列-51