date: 2014-11-02 13:16linux
在《进程的调度与切换》一节中,咱们提到,强制调度的两个条件:安全
调度时机:系统调用返回到用户空间前夕,以及中断或者异常服务程序返回到用户空间前夕。可能会有人担忧以下两种状况:其一若是进程“躲在”“安全地带”内核空间中不出来,调度器岂不仅能干着急?好在内核的设计与实现避免了这个问题。其二:若是进程在用户空间运行,既不调用系统调用函数,也没有中断与异常发生,岂不是也没法进行强转调度?别忘了,系统的时钟中断在默默地坚守着哩。函数
必要条件:当前进程的need_resched字段必须非0。该字段必须由内核去设置,为了让调度器有效运转起来,内核必须“瞅准时机”“见缝插针”地设置该字段。设置的时机包括:this
时钟中断服务程序do_timer_interrupt()中调用do_timer(),对单CPU结构后者调用update_process_times()来调整当期进程与时间相关的一些运行参数,代码在<kernel/timer.c>中:spa
/* * Called from the timer interrupt handler to charge one tick to the current * process. user_tick is 1 if the tick is user time, 0 for system. */ void update_process_times(int user_tick) { struct task_struct *p = current; int cpu = smp_processor_id(), system = user_tick ^ 1; update_one_process(p, user_tick, system, cpu); if (p->pid) { if (--p->counter <= 0) { p->counter = 0; p->need_resched = 1; } if (p->nice > 0) kstat.per_cpu_nice[cpu] += user_tick; else kstat.per_cpu_user[cpu] += user_tick; kstat.per_cpu_system[cpu] += system; } else if (local_bh_count(cpu) || local_irq_count(cpu) > 1) kstat.per_cpu_system[cpu] += system; }
<kerne./sched.c> /* * Wake up a process. Put it on the run-queue if it's not * already there. The "current" process is always on the * run-queue (except when the actual re-schedule is in * progress), and as such you're allowed to do the simpler * "current->state = TASK_RUNNING" to mark yourself runnable * without the overhead of this. */ inline void wake_up_process(struct task_struct * p) { unsigned long flags; /* * We want the common case fall through straight, thus the goto. */ spin_lock_irqsave(&runqueue_lock, flags); p->state = TASK_RUNNING; if (task_on_runqueue(p)) goto out; add_to_runqueue(p); reschedule_idle(p); out: spin_unlock_irqrestore(&runqueue_lock, flags); }
可见,唤醒一个进程只是将它的状态改成TASK_RUNNING而后加入可运行队列。设计
reschedule_idle()判断被唤醒的进程是否比当前进程更有资格运行,若是是,则设置need_resched字段,代码以下:rest
/* * the 'goodness value' of replacing a process on a given CPU. * positive value means 'replace', zero or negative means 'dont'. */ static inline int preemption_goodness(struct task_struct * prev, struct task_struct * p, int cpu) { return goodness(p, cpu, prev->active_mm) – goodness(prev, cpu, prev->active_mm); } static void reschedule_idle(struct task_struct * p) { #ifdef CONFIG_SMP ... #else /* UP */ int this_cpu = smp_processor_id(); struct task_struct *tsk; tsk = cpu_curr(this_cpu); if (preemption_goodness(tsk, p, this_cpu) > 1) tsk->need_resched = 1; #endif }
用户登陆到系统后,第一个进程的使用调度政策为SCHED_OTHER,即无实时要求的交互式引用。其后,经过fork建立子进程时,则将此调度政策遗传给子进程。但在子进程中能够经过sched_setscheduler()来改变调度政策或者调用sched_setparam()函数来改变实时调度政策的优先级。它们的原型为:code
int sched_setscheduler(pid_t pid, int policy, struct sched_param *); int sched_setparam(pid_t pid, struct sched_param *);
结构体sched_param表示调度参数,只有一个成员sched_priority表示实时优先级,取值范围为[0, 99]。对于SCHED_OTHER政策来讲,该值必须为0。队列
struct sched_param { int sched_priority; };
这两个系统调用内核中都是调用setscheduler(),代码在<linux/sched.c>中,代码比较简单,这里不赘述。这里说明一点:若是pid所表明的进程在可执行队列中,那么这两个系统调用会将它们挪到可执行队列的队首,使得再下次调度时占优,而后将当前进程的need_resched字段置1,当即启动一次调度。进程
至于sched_yield(),使当前进程为其余进程“让路”,但当前进程并无睡眠,也没有改变当前进程在可执行队列中的位置。sched_yield()只是经过设置当前进程的need_resched字段来启动一次调度。注意,只有在当前进程的调度政策为SCHED_OTHER时,才会在调度政策上设置SCHED_YIELD标志,使得下一次调度将不会再调度该进程。但在下一次调度以后,在__schedule_tail函数中会清除SCHED_YIELD标志,还进程“自由之身”。
与主动调度不一样,强制调度在适当的时机将当前进程的need_resched字段置1,而后“眼巴巴”地等待调度时间的到来。也就是发现有调度的必要到调度真正发生有一个延迟,叫作调度延迟(dispatch latency)。