强制调度
进程的强制调度是通过设置目标进程的task_struct中的need_resched来完成的。
什么时候要发生强制调度呢?有三种情况:
1) 进程运行的时间过长,这是通过时钟中断服务程序来保证。
2) 唤醒一个睡眠的进程,发现这个进程比当前进程更有资格运行。
3) 一个进程通过系统调用改变调度策略。会立即引发调度。
在设置了need_resched之后,到真正的触发schedule(),到目标进程开始被调度起来,之间有一段不确定的延迟,"dispatch latency"。
第一种情况:时钟中断update_process_times
do_timer_interrupt() -> do_timer() -> update_process_times() 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; }
1) if (--p->counter <= 0) { 将当前正在执行的进程的时间片减1,如果小于0,则设置need_resched
第二种情况:唤醒睡眠的进程wake_up_process
inline void wake_up_process(struct task_struct * p) { unsigned long flags; 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); }
唤醒的操作是: 1) 把进程的状态改成TASK_RUNNING。 2) 加入runqueue队列。 3) 调用reschedule_idle
唤醒一个进程后,主动的触发调度
static void reschedule_idle(struct task_struct * p) { struct task_struct *tsk; tsk = cpu_curr(this_cpu); if (preemption_goodness(tsk, p, this_cpu) > 1) tsk->need_resched = 1; }
如果当前进程的权重低于被唤醒的进程,则把被唤醒进程的need_resched置位。
第三种情况:进程主动让出CPU, sched_setscheduler()和sched_yield()
sched_setscheduler
asmlinkage long sys_sched_setscheduler(pid_t pid, int policy, struct sched_param *param) { return setscheduler(pid, policy, param); }
asmlinkage long sys_sched_setparam(pid_t pid, struct sched_param *param) { return setscheduler(pid, -1, param); } static int setscheduler(pid_t pid, int policy, struct sched_param *param) { struct sched_param lp; struct task_struct *p; int retval; retval = -EINVAL; if (!param || pid < 0) goto out_nounlock; retval = -EFAULT; if (copy_from_user(&lp, param, sizeof(struct sched_param))) goto out_nounlock; read_lock_irq(&tasklist_lock); spin_lock(&runqueue_lock); p = find_process_by_pid(pid); retval = -ESRCH; if (!p) goto out_unlock; if (policy < 0) policy = p->policy; else { retval = -EINVAL; if (policy != SCHED_FIFO && policy != SCHED_RR && policy != SCHED_OTHER) goto out_unlock; } retval = -EINVAL; if (lp.sched_priority < 0 || lp.sched_priority > 99) goto out_unlock; if ((policy == SCHED_OTHER) != (lp.sched_priority == 0)) goto out_unlock; retval = -EPERM; if ((policy == SCHED_FIFO || policy == SCHED_RR) && !capable(CAP_SYS_NICE)) goto out_unlock; if ((current->euid != p->euid) && (current->euid != p->uid) && !capable(CAP_SYS_NICE)) goto out_unlock; retval = 0; p->policy = policy; p->rt_priority = lp.sched_priority; if (task_on_runqueue(p)) move_first_runqueue(p); current->need_resched = 1; out_unlock: spin_unlock(&runqueue_lock); read_unlock_irq(&tasklist_lock); out_nounlock: return retval; }
这个函数很简单,找到pid的进程,然后设置policy,然后把当前进程的need_resched置位。在系统调用返回的时候会发生一次调度。 另外,目标进程已经在runqueue列表中了,就把这个进程移动到runqueue的头部。
sched_yield
asmlinkage long sys_sched_yield(void) { int nr_pending = nr_running; nr_pending--; if (nr_pending) { if (current->policy == SCHED_OTHER) current->policy |= SCHED_YIELD; current->need_resched = 1; } return 0; }
如果当前的runqueue只有一个一进程,则直接返回。 否则,如果当前进程的调度策略是SCHED_OTHER,则设置策略SCHED_YIELD。 否则,把当前正在跑的进程的need_resched置位。