Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1999049
  • 博文数量: 369
  • 博客积分: 10093
  • 博客等级: 上将
  • 技术积分: 4271
  • 用 户 组: 普通用户
  • 注册时间: 2005-03-21 00:59
文章分类

全部博文(369)

文章存档

2013年(1)

2011年(2)

2010年(10)

2009年(16)

2008年(33)

2007年(146)

2006年(160)

2005年(1)

分类: LINUX

2007-12-26 23:36:03

去年读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);
        }
    }


因此,被抢占的进程始终会回到被抢占的点继续执行,无论它被抢占的时候状态为何。这样,刚才自己主观臆测的竞争也就不复存在了...
阅读(2819) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~