wait_event_interruptible souece code
#define __wait_event_interruptible(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
schedule(); \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
当 wait_event_interruptible 条件成立且被执行时,就到了finish_wait。
/**
* finish_wait - clean up after waiting in a queue
* @q: waitqueue waited on
* @wait: wait descriptor
*
* Sets current thread back to running state and removes
* the wait descriptor from the given waitqueue if still
* queued.
*/
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
__set_current_state(TASK_RUNNING);
/*
* We can check for list emptiness outside the lock
* IFF:
* - we use the "careful" check that verifies both
* the next and prev pointers, so that there cannot
* be any half-pending updates in progress on other
* CPU's that we haven't seen yet (and that might
* still change the stack area.
* and
* - all other users take the lock (ie we can only
* have _one_ other CPU that looks at or modifies
* the list).
*/
if (!list_empty_careful(&wait->task_list)) {
spin_lock_irqsave(&q->lock, flags);
list_del_init(&wait->task_list);
spin_unlock_irqrestore(&q->lock, flags);
}
}
我们先看看 prepare_to_wait 做了什么
*
* Note: we use "set_current_state()" _after_ the wait-queue add,
* because we need a memory barrier there on SMP, so that any
* wake-function that tests for the wait-queue being active
* will be guaranteed to see waitqueue addition _or_ subsequent
* tests in this thread will see the wakeup having taken place.
*
* The spin_unlock() itself is semi-permeable and only protects
* one way (it only protects stuff inside the critical region and
* stops them from bleeding out - it would still allow subsequent
* loads to move into the critical region).
*/
void
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
__add_wait_queue(q, wait);
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(prepare_to_wait);
prepare_to_wait 做了 __add_wait_queue(q, wait);而
finish_wait 做了 list_del_init(&wait->task_list);
list_del_init() 函数用于将一个节点从链表中移除,并初始化这个节点。函数将节点 传入 __list_del_entry() 函数,移除将节点从链表中移除,然后调用 INIT_LIST_HEAD() 函数初始化这个节点
这个时候我们再看看 wake_up_interruptible 的原理
/*
* The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
* wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
* number) then we wake all the non-exclusive tasks and one exclusive task.
*
* There are circumstances in which we can try to wake a task which has already
* started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
* zero in this (rare) case, and we handle it by continuing to scan the queue.
*/
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags, void *key)
{
wait_queue_t *curr, *next;
list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags;
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
首先翻译一下:
在某些情况下,我们可以尝试唤醒已经开始运行但未处于TASK_RUNNING状态的任务。 在这种(罕见)情况下,try_to_wake_up()返回零,我们通过继续扫描队列来处理它。
- 唤醒已经开始运行但未处于TASK_RUNNING状态
- 唤醒已经开始运行但处于TASK_RUNNING状态
即 q->task_list 为空时,且处于TASK_RUNNING,就不会去执行 try_to_wake_up,遇到罕见的情形,理论上应该也是在唤醒途中,发现任务已经被执行就停止了唤醒操作。
threadA
for(;;)
{
wait_event_interruptible
do something
}
threadB
wake_up_interruptible
伪代码的解释如下:
- threadA 在 等待被唤醒
- threadB 唤醒了 threadA
- threadA 清空了整个task_list
- thread A 在做do something
- threadB 在 threadA 重新循环进入wait_event_interruptible前执行wake_up_interruptible 都是无效的操作
总结:
wait_event_interruptible 等到条件并被wakeup执行时,再次执行 wake_up_interruptible 时并不会有什么唤醒的动作,因为q->task_list链表上空的,即此次的 wake_up_interruptible 是无效的。
PS :
- TASK_INTERRUPTIBLE 可中断的睡眠状态。是指被信号中断:signal
- prepare_to_wait -> schedule 后就进入了睡眠,被唤醒的前提wake_up 和 condition为true