等待队列浅析
前言:之前一直以为等待队列是什么高大上的机制,其实也就那么回事,搞明白进程调度的问题,等待队列完全就是链表操作和进程状态改变啦,下面就着重说一下“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之后的版本都没有改过,我这里就没标明针对哪个内核版本。要想知道一个机制的使用方式和限制,最好的办法就是看代码!!!