发博文
Free Gentux

xiaosuo.blog.chinaunix.net

Free & Open   
个人资料
  • 博客访问:825157
  • 博文数量:392
  • 博客积分:10093
  • 博客等级:上将
  • 关注人气: 8
  • 注册时间:2005-03-21 00:59:15
订阅我的博客
  • 订阅
  • 订阅到鲜果
  • 订阅到抓虾
  • 订阅到Google
字体大小: 博文
wait_event中莫须有的竞争 (2007-12-26 23:36)
分类: 内核编程

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


因此,被抢占的进程始终会回到被抢占的点继续执行,无论它被抢占的时候状态为何。这样,刚才自己主观臆测的竞争也就不复存在了...

我的更多文章
前一篇:圣诞快乐
亲,您还没有登录,请[登录][注册]后再进行评论