2017-01-11

文来自本人的旧博客 blog.163.com/awaken_ing/blog/static/1206131972016124113539444/

0. 引言

The lost wake-up problem 请参考 http://www.linuxjournal.com/article/8144
本篇主要解释为何修改后的代码没有问题. 修改后的代码为:

1  set_current_state(TASK_INTERRUPTIBLE);
2  spin_lock(&list_lock);
3  if(list_empty(&list_head)) {
4         spin_unlock(&list_lock); //如果这里面让出cpu?
//如果在这个点被生产者唤醒会如何?
5         schedule();
6         spin_lock(&list_lock);
7  }
8  set_current_state(TASK_RUNNING);
9
10 /* Rest of the code ... */
11 spin_unlock(&list_lock);

1. 先解释linux journal上的内容

The change is that whenever a wakeupprocess is called for a process whose state is TASKINTERRUPTIBLE or TASKUNINTERRUPTIBLE, and the process has not yet called schedule(), the state of the process is changed back to TASK_RUNNING.

本文假定调度器使用的是CFS. 假定在第4行和第5行之间 进程B调用 wakeupprocess().
linux-3.10.86/kernel/sched/core.c

wake_up_process
{
return try_to_wake_up(p, TASK_NORMAL,0);
}

#define TASK_NORMAL        (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)

try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
    int success = 0;
    if (! (p->state & state) )
        goto out; //not our case
    ...

    if (p->on_rq && ttwu_remote(p, wake_flags)) //our case
        goto stat;
    ...
out:
...
    return success;
}

ttwuremote -> ttwudowakeup -> p->state = TASKRUNNING;
进程TASK_RUNNING下调用schedule() :

__schedule
{
if(prev->state &&...)
    deactivate_task   //not our case
}

0==TASKRUNNING, 所以,
这个if (prev->state)等效于if ( prev->state != TASK
RUNNING )
所以, TASKRUNNING调用schedule()不会调用到deactivatetask().

deactivate_task 都完成啥动作???

2. 再看看另外一种情况

在第4行spinunlock()中, 如果是个抢占的时机, 那么不是直接调用schedule(), 而是调用 preemptschedule()

preempt_schedule
{
...
    add_preempt_count_notrace(PREEMPT_ACTIVE);
    __schedule();
    sub_preempt_count_notrace(PREEMPT_ACTIVE);
...
}

__schedule
{
    if (prev->state && !( preempt_count() & PREEMPT_ACTIVE )) {
        if(...)
        {
            deactivate_task
            prev->on_rq = 0;
        }

    }
    put_prev_task(rq, prev);    
    next = pick_next_task(rq);
}

这里的!( preemptcount() & PREEMPTACTIVE )就不会满足, 就不会执行deactivate_task().

3. 重新挂到红黑树上

由于当前运行的进程在被选中运行时, 就被移出红黑树了. 上面两种情况中, se节点又是如何重新放到rbtree上的呢? 看putpreventity() :

put_prev_entity(...)
{
    if(prev->on_rq){
        __enqueue_entity(cfs_rq, prev);
    }
}

所以, 是这里的 putpreventity()将节点重新放到rbtree上.

4. 防范

既然容易出现上面的问题, 那么, 内核是如何处理这个问题的呢? 答案是内核提供了宏_waitevent, 可参考其写法:

#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)

preparetowait(... , TASKUNINTERRUPTIBLE) 会设置进程的状态为TASKUNINTERRUPTIBLE,
finishwait()设置状态为TASKRUNNING.

本文地址: https://awakening-fong.github.io/posts/scheduler/scheduler_01_lost_wake-up

转载请注明出处: https://awakening-fong.github.io


若无法评论, 请打开JavaScript, 并通过proxy.


blog comments powered by Disqus