去年读wait_event相关代码的时候就发现:在启用强制抢占(Force Preemption)的Linux内核中,wait_event可能存在竞争条件(Race 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)
|
void fastcall
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);
/*
* don't alter the task state if this is just going to
* queue an async wait queue callback
*/
if (is_sync_wait(wait))
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags);
}
|
如果当前进程被唤醒而呈TASK_RUNNING态,这个时候代码回到__wait_event宏中for循环的顶端,接着调用函数prepare_to_wait,prepare_to_wait调用set_current_state将当前进程的状态设置为TASK_UNINTERRUPTIBLE,恰巧这个时候抢占发生了,schedule被调用,当前进程因为状态不是TASK_RUNNING态而被移除运行队列(runqueue),那么这次唤醒将被丢失,更糟糕一点儿,唤醒事件如果仅能出现一次,那么这个进程可能会被永久地阻塞。
多么令人恐怖的事情啊!
本着谨慎的原则,在写这篇blog之前google了一下,在LKML中找到一篇相关的,证明了我理解不够全面,实际上
竞争并不存在。
具体原因是:在抢占(内核抢占)发生的时候,schedule并不是被直接调用的,而是通过preempt_schedule,preempt_schedule_irq和__cond_resched间接调用的,这三个例程都会在调用schedule之前给当前的抢占计数加上PREEMPT_ACTIVE:
add_preempt_count(PREEMPT_ACTIVE);
...
schedule();
... sub_preempt_count(PREEMPT_ACTIVE);
|
而schedule函数内部会检查PREEMPT_ACITVE位是否被设置,只有当此位没有被设置的时候才会将不是TASK_RUNNING状态的进程移除运行队列:
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
switch_count = &prev->nvcsw;
if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&
unlikely(signal_pending(prev))))
prev->state = TASK_RUNNING;
else {
if (prev->state == TASK_UNINTERRUPTIBLE)
rq->nr_uninterruptible++;
deactivate_task(prev, rq);
}
}
|
因此,
被抢占的进程始终会回到被抢占的点继续执行,无论它被抢占的时候状态为何。这样,刚才自己主观臆测的竞争也就不复存在了...
阅读(2876) | 评论(0) | 转发(0) |