进程调度

    目前通用操作系统,都会提供两大抽象。分别是进程和虚拟内存。

    通过这两种抽象,让每一个程序都好像拥有一个cpu处理器和完整的内存。



    当Linux系统中运行多个进程时,目前运行的进程被堵塞(比如需要等待某个条件才能再运行)时,系统就需要在其他的进程中寻找一个,继续运行下去,如果实在找不到可运行的进程,那系统也不是没有办法,Linux还可以选择idle(no jobs to work)任务运行。


    容易想到,进程时分状态的。有的在运行,有的在等待运行,有的还需要满足某些条件才能运行。。。等等。

    进程描述符中的state域描述了进程的当前状态。state有五个可能的值,每个进程必然处于其中一个状态。这五个值分别是:

  1.   TASK_RUNNING
  2.  TASK_INTERRUPTIBLE
  3.  TASK_UNINTERRUPTIBLE
  4.  _TASK_TRACED
  5.  _TASK_STOPPED

    进程调度程序,针对的是 state值处于 TASK_RUNNING的进程。 TASK_RUNNING意味着进程时可以执行的。

    Linux系统的进程调度函数,从91年开源发布到现在,历经多次更改修正。目前通用的调度策略是CFS.  我手头的Linux4.6.4源码当中的帮助文档是这样介绍CFS的。

   CFS stands for "Completely Fair Scheduler," and is the new "desktop" process scheduler implemented by Ingo Molnar and merged in Linux 2.6.23.  It is the replacement for the previous vanilla scheduler's SCHED_OTHER interactivity code.(CFS意思是完全公平调度,在Linux2.6.23版本中引入该调度函数,代替了.....)

80% of CFS's design can be summed up in a single sentence: CFS basically models an "ideal, precise multi-tasking CPU" on real hardware.(大体上,可以说CFS设计的目的是让硬件看起来像是:  理想中的完美的的多任务处理器)

"Ideal multi-tasking CPU" is a (non-existent  :-)) CPU that has 100% physical power and which can run each task at precise equal speed, in parallel, each at 1/n r_running speed.  For example: if there are 2 tasks running, then it runs each at 50% physical power --- i.e., actually in parallel.(解释ideal multi-tasking CPU的意思)

On real hardware, we can run only a single task at once, so we have to introduce the concept of "virtual runtime."  The virtual runtime of a task specifies when its next timeslice would start execution on the ideal multi-tasking CPU described above.  In practice, the virtual runtime of a task is its actual runtime normalized to the total number of running tasks.(如此完美的cpu并不存在,所以需要抽象化来逼近我们的理想----通过virtual runtime 这个概念来实现。virtual runtime虚拟运行时间存放进程的虚拟运行时间,该运行时间的计算是经过了所有可运行进程总数的标准化)。



         通过这个帮助文档可知,Linux4.6.4的进程调度算法,叫做CFS,该算法的目的实现 an ideal, precise multi-tasking cpu on real hardware。但是,这样的硬件是不太可能存在的,所以就需要通过一个抽象来逼近我的目的。

        这样的抽象过程我们已经见过无数次了,由于直接引用物理内存地址非常容易引起程序崩溃,所以我们就抽象出了一个中间层----虚拟内存。

        由于多个程序在程序上运行,会存在各种问题,如运行顺序出错,侵占资源等等,我们就抽象出了进程的概念。

       回到我们现在遇到的问题,由于CFS的目的是要达到进程调度的效果是要好像系统具备一个理想中的完美的多任务处理器。但这个完美的处理器显然是不存在的,所以,就用一个virtual runtime变量来帮助我们记录进程的虚拟运行时间。虚拟运行时间可以帮助我们比较CFS模型所追求的“理想多任务处理器”。

        CFS使用vruntime变量来记录一个程序到底运行了多长时间,以及它还应该再运行多久。

       依照文件路径linux-4.6.4/kernel/sched/fair.c,可以找到文件fair.c,当中有函数 update_curr(), 该函数实现了所需的记录功能。

       代码如下:


/*
 * Update the current task's runtime statistics.
 * 更新目前任务运行时间的统计数据
 */
static void update_curr(struct cfs_rq *cfs_rq)
{
    struct sched_entity *curr = cfs_rq->curr;//CFS使用调度器实体结构来追踪进程运行记账 struct_sched_entity
    u64 now = rq_clock_task(rq_of(cfs_rq));
    u64 delta_exec; //linux 2.6 该行为 unsigned long delta_exec

    if (unlikely(!curr))
        return;
    //获取从最后一次修改负载后当前任务所占用的运行总时间
    delta_exec = now - curr->exec_start;
    if (unlikely((s64)delta_exec <= 0))
        return;

    curr->exec_start = now;

    schedstat_set(curr->statistics.exec_max,
              max(delta_exec, curr->statistics.exec_max));

    curr->sum_exec_runtime += delta_exec;
    schedstat_add(cfs_rq, exec_clock, delta_exec);

    curr->vruntime += calc_delta_fair(delta_exec, curr);
    update_min_vruntime(cfs_rq);

    if (entity_is_task(curr)) {
        struct task_struct *curtask = task_of(curr);

        trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
        cpuacct_charge(curtask, delta_exec);
        account_group_exec_runtime(curtask, delta_exec);
    }

    account_cfs_rq_runtime(cfs_rq, delta_exec);
}



   



         若存在一个完美的多任务处理器,那么所有可运行进程的vruntime值将一致。实际上没有这样的处理器,所以CFS试图利用一个简单的规则去均衡进程的虚拟运行时间。当CFS需要选择下一个运行进程时,它会挑选一个具有最小vruntime的进程。

        这就是CFS调度算法的核心:选择具有最小vruntime的任务。

        帮助文档中说明了CFS使用红黑树来组织可运行进程的:CFS maintains a time-ordered rbtree, where all runnable tasks are sorted by the p->se.vruntime key. CFS picks the "leftmost" task from this tree and sticks to it.

          既利用vruntime的值来按顺序组织红黑树,CFS要挑选一个vruntime最小的值,所以每次就去红黑树的最左边挑选即可。因此CFS的进程选择课程可以简单的描述为“运行rbtree树中最左边叶节点所代表的那个进程”

          选择算法的源码也是在fair.c文件中,在该文件中搜索函数 _pick_next_entity()即可。

static struct sched_entity *__pick_next_entity(struct sched_entity *se)
{
    struct rb_node *next = rb_next(&se->run_node);

    if (!next)
        return NULL;

    return rb_entry(next, struct sched_entity, run_node);
}

   



   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值