内核代码阅读(25) - 强制调度

强制调度

进程的强制调度是通过设置目标进程的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置位。
上一篇:内核代码阅读(19) - 系统调用trap


下一篇:内核代码阅读(4) - 页面映射的数据结构