在进程执行过程中,有时候需要等待某个条件满足而进行进程阻塞。
常用的一种方法就是让调用者进程暂时挂起,直到目标进程返回结果后,再唤醒等待的进程。
wait_event_interruptible()定义如下:
#define wait_event_interruptible(wq, condition) \
({ \
int _ret = 0; \
if(!condition) \
_wait_event_interruptible(wq, condition, _ret); \
_ret;
})
也就是说,如果condition满足的话就直接返回0;否则,调用 _wait_event_interruptible进入可中断的挂起状态。
#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)
1. prepare_to_wait()只是进行一些链表操作,以确保自己在等待队列中,不会漏掉事件;
2. 进程睡眠的位置为schedule()函数;
3. 进程在确定本身在队列后,再次检查条件,如果不检查,可能条件已经满足,直接去睡眠,可能就再也不会被唤醒;
4. 如果条件不满足就调用schedule()去睡眠,进程的状态在prepare_to_wait()里设置为TASK_INTERRUPTIBLE,所以后续调度的时候就看不到此进程,因为无法参与调度,此时称为休眠
5. 当条件满足后,会存在其他部分(其他进程、内核等)来唤醒等待队列上的进程,具体是唤醒等待队列上的所有进程还是只唤醒第一个,完全取决于唤醒者,
6. 一次唤醒所有进程并不一定是合理的,在多个进程是等待互斥资源的时候,所有等待队列中的进程可以分为互斥、非互斥进程
7. 互斥进程:等待的资源是互斥访问的;互斥进程由内核有选择的唤醒,等待队列项的flag字段为1;非互斥进程:等待的资源是可多进程同时访问的。非互斥进程在事件发生时,总是被内核唤醒,等待队列元素的flag字段为0。
8. 唤醒者通常调用__wake_up_common(),这样,依次取下等待队列中的__wait_queue_t结构, 调用该睡眠进程设置的func函数,即这里的autoremove_wake_function(), 将该进程的状态重新设置为RUNNING。
注意,此时该睡眠进程并不会立刻执行,只有等到下次调度的时候,该进程才有机会运行,即醒来了。醒来是从schedule()回来,继续运行__wait_event()
总结一下, 睡眠是自己设置好进程状态(TASK_UNINTERRUPTIBLE,等等),加入等待队列, 并调用schedule()去睡眠。 睡眠是自己的动作。唤醒是别人发现条件满足,调用__wake_up_common(),将睡眠进程从等待队列取下, 调用该睡眠进程设置的唤醒func,重新设置该睡眠进程为RUNNING。 从而可以在下次调度时运行。 唤醒是别人的动作。
代码由condition控制的for()循环中,所以当由shedule()返回时(当然是被wake_up之后,通过其他进程的schedule()而再次调度本进程),如果condition不满足,本进程将自动再次被设置为TASK_INTERRUPTIBLE状态,接下来执行schedule()的结果是再次被从runqueue队列中删除。这时候就需要再次通过wake_up重新添加到runqueue队列中。
如此反复,直到condition为真的时候被wake_up. 成功地唤醒一个被wait_event_interruptible()的进程,需要满足:
1) 调用wake_up()。
2)condition为真
特别注意:
如果要被wait_event_interruptible的当前进程有nonblocked pending signals, 那么会直接返回-ERESTARTSYS,当前进程不会被wait_event_interruptible 和从runqueue队列中删除。
宏定义_wait_event_interruptible里有个for死循环,跳出循环的条件是condition已经满足要求。首先将自己设置为TASK_INTERRUPTIBLE,也就是可中断挂起状态,然后进行schedule调度。可想而知,因为已经不是处于可运行状态,所以将不再分配到CPU时间,直到有调用wakeup将其唤醒。醒来后还是得先检查condition是否已经满足,否则将再次处于可中断挂起状态。
仔细分析wait_event_interruptible(),其逻辑实现为:
1、函数首先将进程的状态设置为TASK_INTERRUPTIBLE,然后调用schedule();
2、系统调度函数schedule()会将处于TASK_INTERRUPTIBLE状态的进程从队列runqueue中删除;
3、删除后,此进程将不再参与调度,除非通过其他函数(wake_up())将这个进程重新放入runqueue队列中。
wait_event_interruptible()和 wake_up的等效代码
wait_event_interruptible(wq, condition) /*等效没有考虑返回值*/
{
if (!(condition))
{
wait_queue_t _ _wait;
init_waitqueue_entry(&_ _wait, current);
add_wait_queue(&wq, &_ _wait);
for (;;)
{
set_current_state(TASK_INTERRUPTIBLE);
if (condition)
break;
schedule(); /* implicit call: del_from_runqueue(current)*/
}
current->state = TASK_RUNNING;
remove_wait_queue(&wq, &_ _wait);
}
}
void wake_up(wait_queue_head_t *q)
{
struct list_head *tmp;
wait_queue_t *curr;
list_for_each(tmp, &q->task_list)
{
curr = list_entry(tmp, wait_queue_t, task_list);
wake_up_process(curr->task);
/* implicit call: add_to_runqueue(curr->task);*/
if (curr->flags)
break;
}
}