• Linux中的进程调度(二)


    近期主要研究linux kernel中的进程调度算法。 在2.4及2.4以前的版本中,基本上都是根据优先级来选择下一个被调入的进程。算法时间复杂度为O(n)(因为要遍历所有进程,来决定哪个被选中)。当进程数比较多时,些操作费时较大。 在2.6版本刚推出时,采用著名的O(1)调度算法,每个优先级有一个单独的可运行队列,此队列又分为active和expired两个,通过指针的交换等巧妙操作,使选择下一个进程操作的时间复杂度降到了常数时间,O(1)调度算法因此得名。 在2.6.23版本的内核中,又推出了CFS(complete fair scheduler),抛弃了以前优先级和时间片是固定的简单映射方式,采用虚拟运行时间,同时用红黑树维护可运行队列。 下面来具体分析其实现(代码以2.6.24为准)。 先来看新创建一个进程时的动作。 在do_fork()函数后,根据clone_flags复制完父进程相关资源后,将会执行wake_up_new_task(p,clone_flags),将新创建的进程加入可执行队列(这里是红黑树),然后重新进行一次调度。相关代码如下
    if (!(clone_flags & CLONE_STOPPED))
    wake_up_new_task(p, clone_flags);
    我们顺着调用路线往下走,进入到wake_up_new_task中去。
    /*
    * wake_up_new_task - wake up a newly created task for the first time.
    *
    * This function will do some initial scheduler statistics housekeeping
    * that must be done for every newly created context, then puts the task
    * on the runqueue and wakes it.
    */
    void fastcall wake_up_new_task(struct task_struct *p, unsigned long clone_flags)
    {
    unsigned long flags;
    struct rq *rq;
    
    rq = task_rq_lock(p, &flags);
    BUG_ON(p->state != TASK_RUNNING);
    update_rq_clock(rq);
    
    p->prio = effective_prio(p);
    
    if (!p->sched_class->task_new || !current->se.on_rq) {
    activate_task(rq, p, 0);
    } else {
    /*
    * Let the scheduling class do new task startup
    * management (if any):
    */
    p->sched_class->task_new(rq, p);
    inc_nr_running(p, rq);
    }
    check_preempt_curr(rq, p);
    task_rq_unlock(rq, &flags);
    }
    首先,将进程p所在的CPU的可执行队列加锁(自旋锁),然后调用update_rq_clock(rq),更改一些此队列的统计信息(这些信息不是针对某个进程,而是针对这整个队列),然后,通过effective_prio(p)算出进程的优先级(虽然在CFS调度器中优先级不直接与时间片进行映射,但是还是会作为权重来区分进程的重要性),此函数如下
    /*
    * Calculate the current priority, i.e. the priority
    * taken into account by the scheduler. This value might
    * be boosted by RT tasks, or might be boosted by
    * interactivity modifiers. Will be RT if the task got
    * RT-boosted. If not then it returns p->normal_prio.
    */
    static int effective_prio(struct task_struct *p)
    {
    p->normal_prio = normal_prio(p);
    /*
    * If we are RT tasks or we were boosted to RT priority,
    * keep the priority unchanged. Otherwise, update priority
    * to the normal priority:
    */
    if (!rt_prio(p->prio))
    return p->normal_prio;
    return p->prio;
    }
    可见,先通过normal_prio(p)得到p的normal_prio,进入此函数
    /*
    * Calculate the expected normal priority: i.e. priority
    * without taking RT-inheritance into account. Might be
    * boosted by interactivity modifiers. Changes upon fork,
    * setprio syscalls, and whenever the interactivity
    * estimator recalculates.
    */
    static inline int normal_prio(struct task_struct *p)
    {
    int prio;
    
    if (task_has_rt_policy(p))
    prio = MAX_RT_PRIO-1 - p->rt_priority;
    else
    prio = __normal_prio(p);
    return prio;
    }
    这里需要区分实时进程与非实时进程,task_has_rt_policy()代码如下
    static inline int rt_policy(int policy)
    {
    if (unlikely(policy == SCHED_FIFO) || unlikely(policy == SCHED_RR))
    return 1;
    return 0;
    }
    static inline int task_has_rt_policy(struct task_struct *p)
    {
    return rt_policy(p->policy);
    }
    如果进程所属的调度策略是SCHED_FIFO或者SCHED_RR(这两种实时进程的区别最大之处就是有没有时间片的概念,RR=round robin),这里用了unlikely宏,将此宏展开后,是gcc编译器专门的一个优化,也就是说,如果if条件很大概率下为真,那么这部分代码在编译时会放到较前的位置。可见,kernel的设计者考虑的何等细致。 回到刚才的normal_prio, 如果通过task_has_rt_policy()发现进程是一个实时进程,那么,返回MAX_RT_PRIO-1-p->rt_priority,(对于实时进程,rt_priority值越大,优先级越高) 此值将会赋给p->normal_prio(明明是实时优先级,为什么还要赋给normal_prio呢?先把网上找到的一段话放到这里,以后慢慢研究) prio和normal_prio为动态优先级,static_prio为静态优先级。static_prio是进程创建时分配的优先级,如果不人为的更 改,那么在这个进程运行期间不会发生变化。 normal_prio是基于static_prio和调度策略计算出的优先级。prio是调度器类考虑的优先级,某些情况下需要暂时提高进程的优先级 (实时互斥量),因此有此变量,对于优先级未动态提高的进程来说这三个值是相等的。以上三个优先级值越小,代表进程的优先级有高。一般情况下子进程的静态 优先级继承自父进程,子进程的prio继承自父进程的normal_prio。 如果发现进程是一个非实时进程,那么,返回__normal_prio(),这个函数的代码如下
    /*
    * __normal_prio - return the priority that is based on the static prio
    */
    static inline int __normal_prio(struct task_struct *p)
    {
    return p->static_prio;
    }
    也就是说,对于非实时进程,返回的就是它的静态优先级 好了,绕了这么远分析返回值是什么,再回过头去看返回值给了谁
    p->prio = effective_prio(p);
    看来,进程调度时,是以prio为准进行调度的。 今天先分析到这,将优先级确定后的动作下回再分析~
  • 相关阅读:
    学习笔记10-用户和组
    学习笔记9-环境变量
    学习笔记8-检测磁盘空间
    学习笔记7-监测程序
    学习笔记6-权限管理
    【数学】矩阵的逆
    【数学】矩阵
    【数学】Polya定理
    【图论】必经点和必经边
    【图论】点双连通分量
  • 原文地址:https://www.cnblogs.com/yangce/p/2910088.html
Copyright © 2020-2023  润新知