linux唤醒抢占 ============== 当在try_to_wake_up和wake_up_process和wanke_up_new_task中唤醒进程时,内核使用全局check_preemp_curr查看是否进程可以抢占当前进程。每个调度器 都会实现check_preempt_cuur, 在全局的check_preempt_cuur会调用进程所调度器类check_preempt_curr进行抢占检查 linux进程的睡眠 ---------------- 在linux中,仅等待CPU时间的进程称为就绪进程,他们被放置在一个运行队列,一个就绪进程的状态标志为TASK_RUNNING. 一旦一个运行中的进程时间片用完, linux内核的调度器会剥夺这个进程对CPU的控制权,并且从运行队列中选择一个合适的进程投入运行.当然一个进程也可以主动释放CPU的使用权,schedule()是 一个调度函数它可以被一个进程主动调用,从而调度其他进程占用CPU linux中进程睡眠状态有两种 1) 一种是可中断的睡眠状态,其状态标志位TASK_INTERRUPTIBLE. 可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如产生一个硬件中断,释放进程正在等待的资源可以唤醒进程的条件 2) 另一种是不可中断的睡眠状态,其状态标志位TASK_UNINTERRUPTIBLE。与不可中断的睡眠状态区别在于它不响应信号的唤醒 进程一般都是调用schedule的方法进入睡眠状态的,下面的代码演示了如何让正在运行的进程进入睡眠状态 :: sleeping_task = current; set_curreent_state(TASK_INTERRUPTIBLE); schedule(); func1(); .... linux进程的唤醒 ---------------- - wake_up_process :: int wake_up_process(struct task_struct *p) { return try_to_wake_up(p, TASK_NORMAL, 0); } 在调用了wake_up_process以后,这个睡眠进程的状态会被设置为TASK_RUNNING,而且调度器会把它加入到运行队列中去,当然,这个进程只有在下次被调度器调度到的时候才能真正投入运行 - try_to_wake_up :: static int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) { unsigned long flags; int cpu, success = 0; preempt_disable(); if (p == current) { /* * We're waking current, this means 'p->on_rq' and 'task_cpu(p) * == smp_processor_id()'. Together this means we can special * case the whole 'p->on_rq && ttwu_remote()' case below * without taking any locks. * * In particular: * - we rely on Program-Order guarantees for all the ordering, * - we're serialized against set_special_state() by virtue of * it disabling IRQs (this allows not taking ->pi_lock). */ if (!(p->state & state)) goto out; success = 1; cpu = task_cpu(p); trace_sched_waking(p); p->state = TASK_RUNNING; trace_sched_wakeup(p); goto out; } /* * If we are going to wake up a thread waiting for CONDITION we * need to ensure that CONDITION=1 done by the caller can not be * reordered with p->state check below. This pairs with mb() in * set_current_state() the waiting thread does. */ raw_spin_lock_irqsave(&p->pi_lock, flags); smp_mb__after_spinlock(); if (!(p->state & state)) goto unlock; trace_sched_waking(p); /* We're going to change ->state: */ success = 1; cpu = task_cpu(p); /* * Ensure we load p->on_rq _after_ p->state, otherwise it would * be possible to, falsely, observe p->on_rq == 0 and get stuck * in smp_cond_load_acquire() below. * * sched_ttwu_pending() try_to_wake_up() * STORE p->on_rq = 1 LOAD p->state * UNLOCK rq->lock * * __schedule() (switch to task 'p') * LOCK rq->lock smp_rmb(); * smp_mb__after_spinlock(); * UNLOCK rq->lock * * [task p] * STORE p->state = UNINTERRUPTIBLE LOAD p->on_rq * * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in * __schedule(). See the comment for smp_mb__after_spinlock(). */ smp_rmb(); if (p->on_rq && ttwu_remote(p, wake_flags)) goto unlock; #ifdef CONFIG_SMP /* * Ensure we load p->on_cpu _after_ p->on_rq, otherwise it would be * possible to, falsely, observe p->on_cpu == 0. * * One must be running (->on_cpu == 1) in order to remove oneself * from the runqueue. * * __schedule() (switch to task 'p') try_to_wake_up() * STORE p->on_cpu = 1 LOAD p->on_rq * UNLOCK rq->lock * * __schedule() (put 'p' to sleep) * LOCK rq->lock smp_rmb(); * smp_mb__after_spinlock(); * STORE p->on_rq = 0 LOAD p->on_cpu * * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in * __schedule(). See the comment for smp_mb__after_spinlock(). */ smp_rmb(); /* * If the owning (remote) CPU is still in the middle of schedule() with * this task as prev, wait until its done referencing the task. * * Pairs with the smp_store_release() in finish_task(). * * This ensures that tasks getting woken will be fully ordered against * their previous state and preserve Program Order. */ smp_cond_load_acquire(&p->on_cpu, !VAL); p->sched_contributes_to_load = !!task_contributes_to_load(p); p->state = TASK_WAKING; if (p->in_iowait) { delayacct_blkio_end(p); atomic_dec(&task_rq(p)->nr_iowait); } cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags); if (task_cpu(p) != cpu) { wake_flags |= WF_MIGRATED; psi_ttwu_dequeue(p); set_task_cpu(p, cpu); } #else /* CONFIG_SMP */ if (p->in_iowait) { delayacct_blkio_end(p); atomic_dec(&task_rq(p)->nr_iowait); } #endif /* CONFIG_SMP */ ttwu_queue(p, cpu, wake_flags); unlock: raw_spin_unlock_irqrestore(&p->pi_lock, flags); out: if (success) ttwu_stat(p, cpu, wake_flags); preempt_enable(); return success; } - wake_up_new_task 之前进入睡眠状态的可以通过try_to_wake_up和wake_up_process完成唤醒,而我们fork新创建的进程在完成自己的创建工作后,可以通过wake_up_new_task完成唤醒工作. - check_preempt_cuur wake_up_new_task中唤醒进程时,内核使用全局check_preempt_cuur检查是否进程可以抢占当前运行的进程 :: void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags) { const struct sched_class *class; if (p->sched_class == rq->curr->sched_class) { rq->curr->sched_class->check_preempt_curr(rq, p, flags); } else { for_each_class(class) { if (class == rq->curr->sched_class) break; if (class == p->sched_class) { resched_curr(rq); break; } } } /* * A queue event has occurred, and we're going to schedule. In * this case, we can save a useless back to back clock update. */ if (task_on_rq_queued(rq->curr) && test_tsk_need_resched(rq->curr)) rq_clock_skip_update(rq); } 无效唤醒 -------- 几乎在所有的情况下,进程都会在检查了某些条件之后发现条件不满足才进入睡眠,可以有时候进程却会判断条件为真后开始睡眠,如果这样的话进程就会无限期的 休眠下去,这就是所谓的无效唤醒问题 在操作系统中,当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效 唤醒恰恰就是由于竞争条件导致的 A进程 :: spin_lock(&list_lock); if(list_empty((&list_head))) { spin_unlock(&list_lock); set_current_state(TASK_INTERRUPTIBLE); schedule(); spin_lock(&list_lock); } /* rest of the code ... */ spin_unlock(&list_lock); B进程 :: spin_lock(&list_lock); list_add_tail(&list_head, new_node); spin_unlock(&list_lock); wake_up_process(A); 这里会出现一个问题,当A进程执行到spin_unlock(&list_lock);之后,B进程被另外一个CPU调度投入运行在这个时间片内B进执行完了它所有的指令,因此它试图唤醒A进程,而此时的A进程还没有 进入睡眠,所以唤醒操作无效。而A进程之后将自己的状态设置为TASK_INTERRUPTIBLE状态,错过了唤醒时机,A进程会无限期的睡眠下去 这样修改以后可以避免无效唤醒的问题了 A进程 :: set_current_state(TASK_INTERRUPTIBLE); spin_lock(&list_lock); if(list_empty(&list_head)) { spin_unlock(&list_lock); schedule(); spin_lock(&list_lock); } set_current_state(TASK_RUNNING); /* rest of the code ... */ spin_unlock(&list_lock); 在linux操作系统中,内核的稳定性至关重要,为了避免在linux操作系统内核中出现无效唤醒问题,linux内核在需要进程睡眠的时候应该使用类似如下的操作 :: /* q 是我们希望睡眠的等待队列 */ DECLARE_WAITQUEUE(wait, current); add_wait_queue(q, &wait); set_current_state(TASK_INTERRUPTIBLE); /* 或TASK_INTERRUPTIBLE */ while(!condition) schedule(); set_current_state(TASK_RUNNING); remove_wait_queue(q, &wait);