等待队列浅析

前言:之前一直以为等待队列是什么高大上的机制,其实也就那么回事,搞明白进程调度的问题,等待队列完全就是链表操作和进程状态改变啦,下面就着重说一下“wait_event”和“wake_up”这一对等待和唤醒操作。

1. wait_event,什么都不说,贴上代码一切明了。

1 #define wait_event(wq, condition)                                       \
2 do {                                                                    \
3         if (condition)                                                  \
4                 break;                                                  \
5         __wait_event(wq, condition);                                    \
6 } while (0)
 1 #define __wait_event(wq, condition)                                     \
 2 do {                                                                    \
 3         DEFINE_WAIT(__wait);                                            \
 4                                                                         \
 5         for (;;) {                                                      \
 6                 prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \
 7                 if (condition)                                          \
 8                         break;                                          \
 9                 schedule();                                             \
10         }                                                               \
11         finish_wait(&wq, &__wait);                                      \
12 } while (0)
 1 void
 2 prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state) 
 3 {
 4         unsigned long flags;  
 5     
 6         wait->flags &= ~WQ_FLAG_EXCLUSIVE;
 7         spin_lock_irqsave(&q->lock, flags);
 8         if (list_empty(&wait->task_list)) 
 9                 __add_wait_queue(q, wait);     
10         set_current_state(state);      
11         spin_unlock_irqrestore(&q->lock, flags);
12 }
 1 void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
 2 {
 3         unsigned long flags;
 4 
 5         __set_current_state(TASK_RUNNING);
 6         /*
 7          * We can check for list emptiness outside the lock
 8          * IFF:
 9          *  - we use the "careful" check that verifies both
10          *    the next and prev pointers, so that there cannot
11          *    be any half-pending updates in progress on other
12          *    CPU's that we haven't seen yet (and that might
13          *    still change the stack area.
14          * and
15          *  - all other users take the lock (ie we can only
16          *    have _one_ other CPU that looks at or modifies
17          *    the list).
18          */
19         if (!list_empty_careful(&wait->task_list)) {
20                 spin_lock_irqsave(&q->lock, flags);
21                 list_del_init(&wait->task_list);
22                 spin_unlock_irqrestore(&q->lock, flags);
23         }
24 }

关于等待的代码就这么多,很明显的调度关系我就不说了,下面说一下很重要的问题,在当前进程让出cpu去等待之前,需要做什么事情,先看下面一段定义:

1 #define DEFINE_WAIT_FUNC(name, function)                                \
2         wait_queue_t name = {                                           \
3                 .private        = current,                              \
4                 .func           = function,                             \
5                 .task_list      = LIST_HEAD_INIT((name).task_list),     \
6         }
7     
8 #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

现在回答当前进程让出cpu去等待之前要做什么:当他要等待的条件满足了(逻辑意义上,不要跟代码扯上关系)他回来做什么。其实要做的事就这么简单,而上述结构提供了所有原材料,存下了当前线程结构,并初始化了一个链表结构,把wait_queue_t结构挂到等待队列的链表上去,在回来之前先做一下function指定的函数,做完之后回到__wait_event宏的schedule函数调用语句后面一行,即finish_wait,然后进程开始开心的干他wait_event之后的事情。

2. wake_up操作,再讲完了wait_event之后,wake_up操作基本不用讲什么了,粘几行代码足矣。

 1 #define wake_up(x)                      __wake_up(x, TASK_NORMAL, 1, NULL)
 2 
 3 void __wake_up(wait_queue_head_t *q, unsigned int mode,
 4                         int nr_exclusive, void *key)   
 5 {   
 6         unsigned long flags;  
 7     
 8         spin_lock_irqsave(&q->lock, flags);
 9         __wake_up_common(q, mode, nr_exclusive, 0, key);
10         spin_unlock_irqrestore(&q->lock, flags);
11 }
12 
13 static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
14                         int nr_exclusive, int wake_flags, void *key)
15 {
16         wait_queue_t *curr, *next;
17 
18         list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
19                 unsigned flags = curr->flags;
20 
21                 if (curr->func(curr, mode, wake_flags, key) &&
22                                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
23                         break;
24         }
25 }

上面代码中提到的“curr->func”就是第一部分提到的autoremove_wake_function函数,这个函数全是进程调度的活,看完进程调度再去看他,不是啥问题。

 

总结:尽管该机制简单易懂,但是用起来还不那么简答,很多事情的实现想明白,举个例子:wait_event是有可能放弃当前cpu去睡眠,而wake_up就不会。关于用法我也就不说了,多看看代码就懂了,上面举的是等待队里最简单的例子,其他一些等待事件,道理想通,能用好才是关键,据我调研,这些代码从2.XX到Linux 4.XX之后的版本都没有改过,我这里就没标明针对哪个内核版本。要想知道一个机制的使用方式和限制,最好的办法就是看代码!!!

posted @ 2017-03-21 19:54  一天能写一篇就好啦  阅读(407)  评论(0编辑  收藏  举报