windows编程中有CreatEevent SetEvent WaitForSingleObject相关的event机制,来实现非阻塞的等待及事件的通知。
linux有同样类似的实现:
wait_event(wait_queue_head_t wq, condition) 和 wake_up(wait_queue_head_t * x).
/* wait_event - sleep until a condition gets true
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
* @condition evaluates to true. The @condition is checked each time
* the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*/
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
在来看
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)
这里DEFINE_WAIT创建一个当前进程的wait_queue_t结构, prepare_to_wait将它加入之前我们传入的wait_queue_head_t wq(这个要在使用前调用 init_waitqueue_head(&wq)初始化过)同时将当前进程状态设置成TASK_UNINTERRUPTIBLE,代码实现如下:
/*
* 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);
}
由于之前设置了TASK_UNINTERRUPTIBLE, 这样当调用到schedule()的时候,当前进程会被移除调度队列。直到内核来唤醒它。
当我们认为condition有可能变化的时候,需要调用wake_up来唤醒进程,进程会在__wait_event循环中判断条件是否满足。如果满足则跳出循环继续执行,如果不满足(不是真,这里条件可以是一个表达式或者函数或者变量),则继续进入睡眠。
下面看下wake_up的实现:
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
/**
* __wake_up - wake up threads blocked on a waitqueue.
* @q: the waitqueue
* @mode: which threads
* @nr_exclusive: how many wake-one or wake-many threads to wake up
* @key: is directly passed to the wakeup function
*
* It may be assumed that this function implies a write memory barrier before
* changing the task state if and only if any tasks are woken up.
*/
void __wake_up(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, void *key)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr_exclusive, 0, key);
spin_unlock_irqrestore(&q->lock, flags);
}
可以看出wake_up 设置的nr_exclusive是1 ,这样只会唤醒一个进程的。同时TASK_NORMAL
#define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
它代表会唤醒处于这两个状态的进程。wake_up有很多相关的函数,这其中wait_event也是类似的对应的存在相关函数。
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL) //这个唤醒一个进程,包括INTERRUPTIBLE和UNINTERRUPTIBLE
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)//唤醒 nr个进程,包括INTERRUPTIBLE和UNINTERRUPTIBLE
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)//唤醒队列(wq)中所有进程, 包括INTERRUPTIBLE和UNINTERRUPTIBLE
#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL) //同wake_up 只不过是在已经去的wq的spinlock的情况下调用
//下面的是只针对TASK_INTERRUPTIBLE状态的进程才唤醒
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)