邮件列表每天都能让我学到新东西,感谢他!有朋友问PREEMPT_ACTIVE有什么用,我给出了最简单的回答,就是避免被抢占的进程被无情的赶出运行队列。这个回答显然不能让那位朋友满意...
进程一旦调用了schedule,如果再次被调度运行,那么有下面几种可能:
1.状态为TASK_RUNNING,处于运行队列,那么它肯定有机会再运行;
2.处于睡眠队列,那么一旦条件满足被唤醒,那么它就会运行。那么如果一个进程被抢占的话,而且它不在运行队列,那么怎么再让它运行呢?答案是它不能运行了。为了避免这种情况,就必须避免处于非TASK_RUNNING的进程被抢占的进程不被赶出运行队列,也就是下面的代码,schedule的代码:
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);
}
也许有人会问,怎么会有不是TASK_RUNNING的进程而且被抢占的,这个问题实在难以回答,可是记住,进程状态和其所在的队列没有关系,设置进程状态和抢占总是有可能有间隙的。我们看看下面的代码:
for (;;) { /
1: prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); /
2: if (condition) /
3: break; /
4: schedule(); /
}
如果在1中被抢占,恰恰在设置完进程为TASK_UNINTERRUPTIBLE的时候被抢占,本来马上就要测试条件是否满足了,结果又被加入睡眠队列去睡眠了,如果没有PREEMPT_ACTIVE,那么在schedule中就会被移出运行队列,如果只有这一次唤醒机会,那么就永远唤不醒这个进程了,如果本次从schedule回来条件不满足,那么在下面的schedue中就会被移出运行队列,这不是抢占的职责,如果非要怎么做就会出错,在dequeue_task中由array->queue已经为空了,在第二次真正出队的时候就会由于空指针引用而出错(这其实不会发生,因为只要从schedue回来,进程的状态肯定是TASK_RUNNING,仅仅是一个例子)。因此必须保证在将进程从运行队列移除的时候,它必须在运行队列,否则移个鸟啊!实际上PREEMPT_ACTIVE的作用就是防止将处于非TASK_RUNNING状态的进程并且没有在任何睡眠队列的进程移出运行队列,总之必须保证进程在一个队列中或者可以被唤醒,被抢占的进程是不能被唤醒的,如果它还不在运行队列中,那么它将永远不能再运行了。那么PREEMPT_ACTIVE是怎么保证被抢占的进程不会被移除运行队列呢?就是在preempt_schedule实现的:
asmlinkage void __sched preempt_schedule(void)
{
struct thread_info *ti = current_thread_info();
if (likely(ti->preempt_count || irqs_disabled()))
return;
do {
add_preempt_count(PREEMPT_ACTIVE); //设置PREEMPT_ACTIVE位,一直到下面的sub_preempt_count(PREEMPT_ACTIVE),这期间不能再抢占这个进程,不过再抢占也没有意义,如果非要抢占,出了下面的sub_preempt_count(PREEMPT_ACTIVE)也不迟
schedule();
sub_preempt_count(PREEMPT_ACTIVE); //抢占完毕后清除之
barrier();
} while (unlikely(test_thread_flag(TIF_NEED_RESCHED)));
}