您的位置:新葡亰496net > 电脑系统 > 内核源码分析之进程调度机制,linux内核CFS进程调

内核源码分析之进程调度机制,linux内核CFS进程调

发布时间:2019-06-18 11:22编辑:电脑系统浏览(73)

    在架航空模型型中vruntime决定了经过被调解的先后顺序,在真正模型中决定被调解的先后顺序的参数是由函数entity_key决定的。   
    static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se)
    {
        return se->vruntime - cfs_rq->min_vruntime;
    }
    enqueue_task_fair---->enqueue_entity---->__enqueue_entity---->entity_key决定插入就绪队列的岗位。

    一、概述

    本篇来斟酌下cgroup对cpu的界定机制,前文提到过cgroup也是由此进度调解子系统来完毕限制cpu的指标,由此须要驾驭下进程调整子系统.

    一起公平级调动度CFS

    CFS(Completely Fair Scheduler)试图遵照对 CPU 时间的 “最大供给(gravest need)” 运转职责;这促进保障各个过程能够得到对 CPU 的公正共享。

    经过调解所运用到的数据结构:

    普普通通进度分为叁14个阶段,各类阶段对应一个权重值,权重值用二个整数来标示。权重值定义在数组prio_to_weight[40]中;普通进度的权重值最大为88761,最小为15。暗许意况下,普通进程的权重值为1024(由NICE_0_LOAD钦命)。weight是由进度的静态优先级static_prio决定的,静态优先级越高(static_prio值越小)weight值越大。普通进程的默许nice值为0,即暗中认可静态优先级为120,它的weight值为prio_to_weight[20],即1024。因此NICE_0_LOAD的值就 为1024。

    第一简介一下根本的陈设思路,
    CFS思路特别easy。就是依赖各样进度的权重分配施行时间(权重怎么来的后边再说)。
    进程的实行时间计算公式为:
    分红给进程的奉行时间 = 调治周期 * 进度权重 / 全部历程权重之和   (公式1)
    调治周期相当好精通。正是将全部介乎TASK_RUNNING态进度都调治二次的年华,
    差一些相同约等于O(1)调治算法中进行队列和过期队列切换三回的时辰
    (作者对O(1)调节算法看得不是老大熟,如有错误还望各位大虾指出)。
    举个样例。比如仅仅有四个进程A, B,权重分别为1和2,
    调节周期设为30ms,那么分配给A的CPU时间为
    30ms * (1/(1 2)) = 10ms
    而B的CPU时间为
     
    30ms * (2/(1 2)) = 20ms
    那么在那30ms中A将施行10ms。B将施行20ms。
    公允怎么显示吗?它们的实行时间并差别样阿?
    实质上公平是体方今其余一个量地点。叫做virtual runtime(vruntime)。它记录着进度已经奉行的刻钟,
    唯独并非直接记录,而是要依附进程的权重将实行时间放大也许减弱三个百分比。
    笔者们来看下从骨子里实施时间到vruntime的折算公式
    vruntime = 实际试行时间 * 1024 / 进度权重。 (公式2)
    为了不把大家搞晕。这里笔者平素写1024。实际上它也等于nice为0的经过的权重,代码中是NICE_0_LOAD。
    也便是说。全体进程都是nice为0的进程的权重1024当做标准。总括自身的vruntime加多快度。
    还以上面AB五个进度为例。B的权重是A的2倍,那么B的vruntime增加快度只是有A的二分之一。

    因为是介绍cgroup的文章,由此只介绍进程调节中与cgroup密切关系的一些,详细达成的经过调节完成可参看进度调节的连带资料.

    CFS初探

    CFS 调治程序行使安抚(appeasement)战术确定保证公平性。当有个别任务进入运行队列后,将记录当前时间,当某些进度等待 CPU 时,将对这些进度的 wait_runtime 值加二个数,那些数取决于运营队列当前的进度数。当实施那个总计时,也将思量不相同职务的开始时期级值。 将这么些职分调治到 CPU 后,它的 wait_runtime 值开首递减,当这一个值递减到任何任务成为红黑树的最右边职责时,当前任务将被并吞。通过这种艺术,CFS 努力贯彻一种理想 状态,即 wait_runtime 值为 0!

    CFS 维护职分运营时(相对于运转队列级石英钟,称为 fair_clock(cfs_rq->fair_clock)),它在有个别实际时间的片段内运营,由此,对于单个职务能够遵循优质的进程运转。

    比如,假如具备 4 个可运转的职分,那么 fair_clock 将依照实际时间进程的十分三日增。每一种职分将主见跟上那些速度。那是由分时多职分处理的量子化性格决定的。也正是说,在其余贰个时日段内唯有贰个职责能够运作;因而, 别的进度在时光上的亏欠将叠合(wait_runtime)。由此,一旦有个别职务进入调节,它将竭力凌驾它所欠下的光阴(并且要比所欠时间多一些,因为在穷追时间之内,fair_clock 不会终止计时)。

    粒度和延缓如何关联?

    关联粒度和延迟的粗略等式为: 

    gran = (lat/nr) - (lat/nr/nr)

    其中 gran = 粒度,

    lat = 延迟,而 

    nr = 运转中的任务数。

    加权职责引进了前期级。假诺我们有多少个任务:其中三个职分占用 CPU 的时间量是另三个任务的两倍,比例为 2:1。实践数学转换后,对于权重为 0.5 的天职,时间流逝的进度是原先的两倍。我们依据 fair_clock 对树进行排队。

    请小心,CFS 未有利用时间片(time slices),至少,未有事先利用。CFS 中的时间片有着可变的长短并且动态明确。

    1.就绪队列

    vruntime行走速度:
        系统鲜明:暗中同意权重值(1024)对应的长河的vruntime行走时间与实际运作时刻runtime是1:1的涉嫌。由于vruntime的行进速度和权重值成反比,那么其它进度的vruntime行走速度都由此以下四个参数总计获得:1、该进程的权重值2、暗中同意进程的权重值。
        比如权重为3096的经过的vruntime行走速度为:1024/3096 * (wall clock)。
        “真实石英钟速度”即为runtime(即 wall clock)行走的进度。

    今昔我们把公式第22中学的实际实践时间用公式1来替换。能够获取那样三个结实:
    vruntime = (调节周期 * 进度权重 / 全体历程总权重) * 1024 / 进度权重=调治周期 * 1024 / 全部历程总权重
    看来哪些形容没有?没有错,尽管经过的权重差异,然则它们的vruntime增速应该是同样的(这里所说的增速同样,是从宏观上来看的。从上一篇小说能够看出来。而在上一篇小说中说vruntime的增量不相同,是从公式分析获得的,算是局地深入分析,在公式第22中学,假如实际实践时间都以同等。非常醒目权重小的巩固的多。权根本的狠抓的小,笔者个人感觉就是虚拟挂钟的存在。转变了观念。才有了那个CFS,事实上依然依据权重来决定三个经过在四个调用周期内施行了多久,不过虚拟机械钟决定了怎么调整这些进度,这就是思量),与权重毫无干系。
    好,既然全体经过的vruntime增速宏观上看应该是平等时候推进的。
    那么就可见用那些vruntime来摘取实践的进度。什么人的vruntime值十分小就印证它已经占用cpu的时刻相当短,
    碰到了“不公道”对待,由此下多少个推行进程正是它。

    正文分为三个部分,首先介绍进程调治中的调节算法,在该基础上引进组调解,最终结合前面作品(cgroup原理简析:vfs文件系统)来证明上层通过echo pid >> tasks, echo n > cpu.shares等操作影响调节器对进度的调解,从而调节进程对cpu的应用,(内核源码版本3.10)

    运作时调优选项

    引进了至关首要的 sysctls 来在运作时对调节程序开始展览调优(以 ns 结尾的名目以飞秒为单位):

    sched_latency_ns:针对 CPU 密集型义务进行指标抢占延迟(Targeted preemption latency)。

    sched_batch_wakeup_granularity_ns:针对 SCHED_BATCH 的唤醒(Wake-up)粒度。

    sched_wakeup_granularity_ns:针对 SCHED_OTHE奥迪Q7 的晋升粒度。

    新葡亰496net,sched_compat_yield:由于 CFS 进行了改造,严重依赖 sched_yield() 的行为的应用程序能够供给不一样的品质,由此推荐启用 sysctls。

    sched_child_runs_first:child 在 fork 之后进展调解;此为私下认可设置。倘诺设置为 0,那么先调治 parent。

    sched_min_granularity_ns:针对 CPU 密集型职务奉行最低档别抢占粒度。

    sched_features:蕴涵各种与调度相关的性子的音信。

    sched_stat_granularity_ns:搜集调解程序总结音信的粒度。

    新葡亰496net 1

    系统中运转时参数的规范值

    水源为每一个cpu成立多少个历程就绪队列,该队列上的历程均由该cpu实践,代码如下(kernel/sched/core.c)。

        进度推行实践时期周期性调治器周期性地运营,其承受更新一些连锁数据,并不担任进度之间的切换:
        timer_tick()---->update_process_times---->schedule_tick()
        schedule_tick---->task_tick_fair---->entity_tick()---->update_curr()
        update_curr()函数实现存关数据的革新。
            update_curr()---->delta_exec = (unsigned long)(now - curr->exec_start)
                                  |-->__update_curr()
                                  |-->curr_exec_start = now;
        update_curr()函数只肩负总结delta_exec以及更新exec_start。其余专门的学业由__update_curr()函数完结:
            1、更新当前进度的骨子里运作时刻(抽象模型中的runtime)。
            2、更新当前进程的杜撰时间vruntime。
            3、更新cfs_rq->min_vruntime。
               在当下历程和下二个将要被调节的进度中精选vruntime相当的小的值。然后用该值和cfs_rq->min_vruntime比较,如果比min_vruntime大,则更新cfs_rq为的min_vruntime为所求出的值。

    那般不仅可以公平选择进程,又能有限支撑高优先级进度
    拿到较多的实行时间。
    这正是CFS的重要思考了。


    新的调解程序调节和测试接口

    新调治程序附带了贰个不胜棒的调整接口,还提供了运转时总结音信,分别在 kernel/sched_debug.c 和 kernel/sched_stats.h 中落到实处。要提供调治程序的运行时音信和调节和测试新闻,必要将一部分文书增多到 proc pseudo 文件系统:

    /proc/sched_debug:显示运营时调整程序可调优选项的当前值、CFS 总计音信和全数可用 CPU 的周转队列音讯。当读取那几个 proc 文件时,将调用 sched_debug_show() 函数并在 sched_debug.c 中定义。

    /proc/schedstat:为持有相关的 CPU 彰显特定于运作队列的计算音信以及 SMP 系统中一定于域的总结音信。kernel/sched_stats.h 中定义的 show_schedstat() 函数将管理 proc 条款中的读操作。

    /proc/[PID]/sched:突显与连锁调节实体有关的音信。在读取这些文件时,将调用 kernel/sched_debug.c 中定义的 proc_sched_show_task() 函数

    1 DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
    

    设想下当创制新历程大概经过唤醒时,vruntime在小心翼翼模型中的管理格局:
    I、新建进度
        进程的ideal_time长度和weight成正比,vruntime行走速度与weight值成反比。因而当每一种进程在period时间内,都实行了团结相应的ideal_time短期,那么她们的vruntime的增量相等。而nice为0的长河的vruntime行走速度等于runtime行走速度,所以每一个进度都运作它本身相应的ideal_runtime时间,其它进度的vruntime增量都等于nice值为0的进程的ideal_runtime。假诺开端情状下,它们的具有进度的vruntime值都等于0,那么当贰个进度运转完runtime的光阴为ideal_time,那么它的vruntime将为最大,只要任何进度的运作总时间未有直达分别对应的ideal_runtime值,那么它始终排在进度队列的终极。

    再补充一下放权力重的来源于,权重跟进度nice值之间有种种相应的关联,能够由此全局数组prio_to_weight来转换,
    nice值越大,权重越低

    1.经过调治

    CFS内部原理

    Linux 内的装有职务都由称为 task_struct 的职分结构意味着。该组织(以及任何相关内容)完整地讲述了任务并包括了职责的此时此刻事态、其仓库、进度标记、优先级(静态和动态)等等。能够在 ./linux/include/linux/sched.h 中找到那么些剧情以及有关协会。 不过因为不是有所任务都以可运转的,在 task_struct 中不会发觉别的与 CFS 相关的字段。 相反,会创立一个名称叫 sched_entity 的新组织来追踪调整音讯。

    新葡亰496net 2

    职分和红黑树的组织档案的次序关系

    树的根通过 rb_root 成分通过 cfs_rq 结构(在 ./kernel/sched.c 中)引用。红黑树的叶子不含有音信,但是里面节点代表三个或两个可运转的义务。红黑树的每一种节点都由 rb_node 表示,它只包涵子引用和父对象的颜色。 rb_node 包含在 sched_entity 结构中,该组织包含rb_node引用、负载权重以及种种总结数据。最主要的是,sched_entity 包涵 vruntime(64 位字段),它表示职责运维的时间量,并作为红黑树的目录。 最终,task_struct 位于顶上部分,它全部地描述任务并含有 sched_entity 结构。

    就 CFS 部分来讲,调节函数极其轻便。 在 ./kernel/sched.c 中,通用 schedule() 函数,它会先抢占当前运作职分(除非它经过 yield() 代码先抢占自个儿)。注意 CFS 未有真正的时刻切块概念用于抢占,因为抢占时间是可变的。当前运维职责(今后被私吞的天职)通过对 put_prev_task 调用(通过调治类)再次来到到红黑树。当schedule函数起头分明下贰个要调整的天职时,它会调用 pick_next_task函数。此函数也是通用的(在./kernel/sched.c 中),但它会透过调节器类调用 CFS 调整器。

    CFS调整算法的骨干是选项具有最小vruntine的职分。运维队列选择红黑树格局存放,在那之中节点的键值就是可运营进程的杜撰运营时刻。CFS调治器选用待运营的下贰个历程,是兼具进度中vruntime最小的充裕,他对应的正是在树中最左侧的卡片节点。达成选拔的函数为pick_next_task_fair,CFS 中的 pick_next_task 函数能够在 ./kernel/sched_fair.c(称为pick_next_task_fair())中找到。 此函数只是从红黑树中拿到最左端的任务并重返相关sched_entity。通过此引用,贰个简单易行的 task_of()调用鲜明再次来到的task_struct 引用。通用调解器最后为此任务提供管理器。

    static struct task_struct *pick_next_task_fair(struct rq *rq)

    {

    struct task_struct *p;

    struct cfs_rq *cfs_rq = &rq->cfs;

    struct sched_entity *se;

    if (unlikely(!cfs_rq->nr_running))

    return NULL;

    do {/*此循环为了缅怀组调节*/

    se = pick_next_entity(cfs_rq);

    set_next_entity(cfs_rq, se);/*安装为近期运作进度*/

    cfs_rq = group_cfs_rq(se);

    } while (cfs_rq);

    p = task_of(se);

    hrtick_start_fair(rq, p);

    return p;

    }

    实质工作调用__pick_next_entity完成。

    /*函数自己并不会遍历数找到最左叶子节点(即正是兼备进度中vruntime最小的足够),因为该值已经缓存在rb_leftmost字段中*/

    static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq)

    {

    /*rb_leftmost为保存的红黑树的最左侧的节点*/

    struct rb_node *left = cfs_rq->rb_leftmost;

    if (!left)

    return NULL;

    return rb_entry(left, struct sched_entity, run_node);

    }

    概念了三个struct rq结构体数组,每种数组成分是一个妥当队列,对应贰个cpu。上边看下struct rq结构体(kernel/sched/sched.h):

        对于新历程,task_fork_fair()->place_entity(cfs_rq, se, 1),其intial参数为1。新历程的vruntime值被安装为min_vruntime sched_vslice(cfs_rq, se), sched_vslice()函数可总计出nice值为0的经过的ideal_runtime。其效果是将新加入的进程的暗记为“它在period长日子内早已运营它对应的ideal_time长时间”,那么新加盟进程在讨论上(全数进度都推行它对应的ideal_runtime时间,未有生出睡眠、进度终止等独特别情报况)唯有静观其变period之后本事被调整。
        sched_vslice(cfs_rq, se)---->calc_delta_fair(sched_slice(cfs_rq, se), se), sched_slice()计算新建进度的ideal_runtime,calc_delta_fair()将ideal_runtime转换成vruntime。

    以下来解析代码。网络一度有不行多cfs的篇章。由此笔者计划换二个主意来写,选用几个点来张开情景分析,
    包括进度创设时。进度被唤起,主动调治(schedule),时钟中断。

    我们驾驭linux下的进度会有多样情状,已就绪状态的经过在就绪队列中(struct rq),当cpu需求加载新任务时,调整器会从妥帖队列中选取叁个最优的长河加载实施.

    第一的 CFS 数据结构

    对此各种 CPU,CFS 使用按期间排序的红黑(red-black)树。

    红黑树是一种自平衡二叉找寻树,这种数据结构可用于贯彻关全面组。对于各样运维中的进度,在红黑树上都有三个节点。红黑树上位于最右侧的经过表示将张开下三遍调整的历程。红黑树相比复杂,但它的操作具备独具特殊的优越条件的最差境况(worst-case)运营时,并且在实操中极度快速:它能够在 O(log n) 时间内找出、插入和删除 ,其中 n 表示树元素的数额。叶节点意义比十分小并且不带有数据。为节本省部存款和储蓄器,偶然利用单个哨兵(sentinel)节点实践全体叶节点的剧中人物。内部节点到叶节点的具有引用都针对哨兵节点。

    该树方法能够优秀运转的缘故在于:

    1.红黑树可以平素维持平衡。

    2.由于红黑树是二叉树,查找操作的大运复杂度为对数。不过,除了最左侧查找以外,很难实施其它查找,并且最左侧的节点指针始终被缓存。

    3.对此绝大大多操作,红黑树的施行时间为 O(log n),而从前的调解程序通过装有一定优先级的开始的一段时期级数组使用 O(1)O(log n) 行为持有可衡量的推移,然而对于不小的职务数非亲非故首要。Molnar 在品尝这种树方法时,首先对这点进展了测试。

    4.红黑树可通过中间存款和储蓄达成 — 即没有需求采用外部分配就能够对数据结构举行维护。

    让我们询问一下达成这种新调节程序的有个别重大数据结构。

    新葡亰496net 3新葡亰496net 4

    II、睡眠进度被提示
        将经过的vruntime值设置为cfs_rq->min_vruntime值,然后再举办一下补给:将vruntime减去与sysctl_sched_latencyd相关的三个数值。进度进入睡眠状态时cfs_rq->min_vruntime就高出或等于该进程的vruntime值,它在睡眠进度中vruntime值是不改换的,不过cfs_rq->min_vruntime的值却是单调扩展的,进度醒来后补偿的量由sysctl_sched_latency给出,不管进度面前遭遇的失之偏颇对待大照旧小,一律只补充这么多。

    介绍代码在此以前先介绍一下CFS相关的组织
    首先个是调治实体sched_entity,它意味着贰个调解单位。在组调治关闭的时候能够把她等同为进度。
    每个task_struct中都有四个sched_entity,进度的vruntime和权重都保存在这么些结构中。
    那就是说任何的sched_entity怎么协会在联合吧?红黑树。全体的sched_entity以vruntime为key
    (实际上是以vruntime-min_vruntime为单位,难道是防御溢出?反正结果是平等的)插入到红黑树中,
    同样时候缓存树的最左側节点。也正是vruntime最小的节点,那样能够急速选中vruntime最小的经过。
    注意仅仅有等待CPU的就绪态进度在那棵树上,睡眠进程和正在实践的进度都不在树上。
    自家从ibm developer works上偷过来一张图来呈现一下它们的关联:
    汗。图片上传功用被关闭了。先盗链三个重操旧业。别怪笔者没品哈。。。

    那么调解器依据规则来选出这一个最优的进度呢?那又引出了经过优先级的定义,不难的话,linux将经过分为一般进程和实时进程七个大类,用二个数值范围0-139来表示优先级,值越小优先级越高,当中0-99意味着实时进度,100-139(对使用户层nice值-20-19)表示日常进度,实时进程的早期级总是跨越日常进度.

    struct task_struct 的变化

    CFS 去掉了 struct prio_array,并引进调解实体(scheduling entity)和调节类 (scheduling classes),分别由 struct sched_entity 和 struct sched_class 定义。因此,task_struct 包蕴关于 sched_entity 和 sched_class 那二种结构的音讯:

    struct task_struct {

    /* Defined in 2.6.23:/usr/include/linux/sched.h */....

    -  struct prio_array *array;

      struct sched_entity se;

      struct sched_class *sched_class;  

     ....   ....

    };

      1 struct rq {
      2     /* runqueue lock: */
      3     raw_spinlock_t lock;
      4 
      5     /*
      6      * nr_running and cpu_load should be in the same cacheline because
      7      * remote CPUs use both these fields when doing load calculation.
      8      */
      9     unsigned int nr_running;
     10 #ifdef CONFIG_NUMA_BALANCING
     11     unsigned int nr_numa_running;
     12     unsigned int nr_preferred_running;
     13 #endif
     14     #define CPU_LOAD_IDX_MAX 5
     15     unsigned long cpu_load[CPU_LOAD_IDX_MAX];
     16     unsigned long last_load_update_tick;
     17 #ifdef CONFIG_NO_HZ_COMMON
     18     u64 nohz_stamp;
     19     unsigned long nohz_flags;
     20 #endif
     21 #ifdef CONFIG_NO_HZ_FULL
     22     unsigned long last_sched_tick;
     23 #endif
     24     int skip_clock_update;
     25 
     26     /* capture load from *all* tasks on this cpu: */
     27     struct load_weight load;
     28     unsigned long nr_load_updates;
     29     u64 nr_switches;
     30 
     31     struct cfs_rq cfs;
     32     struct rt_rq rt;
     33     struct dl_rq dl;
     34 
     35 #ifdef CONFIG_FAIR_GROUP_SCHED
     36     /* list of leaf cfs_rq on this cpu: */
     37     struct list_head leaf_cfs_rq_list;
     38 
     39     struct sched_avg avg;
     40 #endif /* CONFIG_FAIR_GROUP_SCHED */
     41 
     42     /*
     43      * This is part of a global counter where only the total sum
     44      * over all CPUs matters. A task can increase this counter on
     45      * one CPU and if it got migrated afterwards it may decrease
     46      * it on another CPU. Always updated under the runqueue lock:
     47      */
     48     unsigned long nr_uninterruptible;
     49 
     50     struct task_struct *curr, *idle, *stop;
     51     unsigned long next_balance;
     52     struct mm_struct *prev_mm;
     53 
     54     u64 clock;
     55     u64 clock_task;
     56 
     57     atomic_t nr_iowait;
     58 
     59 #ifdef CONFIG_SMP
     60     struct root_domain *rd;
     61     struct sched_domain *sd;
     62 
     63     unsigned long cpu_capacity;
     64 
     65     unsigned char idle_balance;
     66     /* For active balancing */
     67     int post_schedule;
     68     int active_balance;
     69     int push_cpu;
     70     struct cpu_stop_work active_balance_work;
     71     /* cpu of this runqueue: */
     72     int cpu;
     73     int online;
     74 
     75     struct list_head cfs_tasks;
     76 
     77     u64 rt_avg;
     78     u64 age_stamp;
     79     u64 idle_stamp;
     80     u64 avg_idle;
     81 
     82     /* This is used to determine avg_idle's max value */
     83     u64 max_idle_balance_cost;
     84 #endif
     85 
     86 #ifdef CONFIG_IRQ_TIME_ACCOUNTING
     87     u64 prev_irq_time;
     88 #endif
     89 #ifdef CONFIG_PARAVIRT
     90     u64 prev_steal_time;
     91 #endif
     92 #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
     93     u64 prev_steal_time_rq;
     94 #endif
     95 
     96     /* calc_load related fields */
     97     unsigned long calc_load_update;
     98     long calc_load_active;
     99 
    100 #ifdef CONFIG_SCHED_HRTICK
    101 #ifdef CONFIG_SMP
    102     int hrtick_csd_pending;
    103     struct call_single_data hrtick_csd;
    104 #endif
    105     struct hrtimer hrtick_timer;
    106 #endif
    107 
    108 #ifdef CONFIG_SCHEDSTATS
    109     /* latency stats */
    110     struct sched_info rq_sched_info;
    111     unsigned long long rq_cpu_time;
    112     /* could above be rq->cfs_rq.exec_clock   rq->rt_rq.rt_runtime ? */
    113 
    114     /* sys_sched_yield() stats */
    115     unsigned int yld_count;
    116 
    117     /* schedule() stats */
    118     unsigned int sched_count;
    119     unsigned int sched_goidle;
    120 
    121     /* try_to_wake_up() stats */
    122     unsigned int ttwu_count;
    123     unsigned int ttwu_local;
    124 #endif
    125 
    126 #ifdef CONFIG_SMP
    127     struct llist_head wake_list;
    128 #endif
    129 };
    

    实际模型总计:
        a)进度在就绪队列中用键值key来排序,它未有保留在其他变量中,而是在急需时由函数entity_key()总括得出。它相当于
            key = task->vruntime - cfs_rq->min_vruntime
        b)种种进度有例外的入眼(优先品级),越首要的进度权重值weight(task.se.load.weight)越大。
        c)各种进度vruntime行走的快慢和weight值成反比。权重值为1024(NICE_0_LOAD)的进程vruntime行走速度和runtime一样。
        d)每一种进程每趟获得CPU使用权最多推行与该进程对应的ideal_runtime长期。该时长和weight值成正比,它从未用变量来保存,而是须求运用sched_slice()函数总括得出。
        e)ideal_runtime总结的原则是period,它也并未有用变量来保存,而是由__sched_period()计算得出。

     

    不相同优先级档案的次序的进程当然要动用差异的调整战略,普通进度使用完全公平调治(cfs),实时进程使用实时调节(rt),这里根本完成上利用了一个临近面向对象的章程,抽象出叁个调整类(struct sched_class)申明同一的钩子函数,完全公平级调动度实例(fair_sched_class)和实时调解实例(rt_sched_class)各自实现这么些钩子.

    struct sched_entity

    运营实体结构为sched_entity,该组织包蕴了完整的新闻,用于落到实处对单个职务或职务组的调节。它可用于贯彻组调节。调治实体或然与经过未有提到。全体的调解器都不可能不对经过运转时刻做记账。CFS不再不时间片的定义,然则她也务必保险每一种进度运转的年华记账,因为他索要保障每一种进度只在公正分配给她的管理器时间内运维。CFS使用调整器实体结构来最终运营记账。

    新葡亰496net 5

    sched_entity 结构体简单介绍

    完成记账效用,由系统沙漏周期调用

    static void update_curr(struct cfs_rq *cfs_rq)

    {

    struct sched_entity *curr = cfs_rq->curr;

    u64 now = rq_of(cfs_rq)->clock;/*now计时器*/

    unsigned long delta_exec;

    if (unlikely(!curr))

    return;

    /*

    * Get the amount of time the current task was running

    * since the last time we changed load (this cannot

    * overflow on 32 bits):

    */

    /*获取从最终二次修改负载后当前义务所据有的运作总时间*/

    /*即总结当前进度的实施时间*/

    delta_exec = (unsigned long)(now - curr->exec_start);

    if (!delta_exec)/*一旦此番未有实行过,不用再行更新了*/

    return;

    /*基于当下可运行进度总的数量对运作时刻举办加权测算*/

    __update_curr(cfs_rq, curr, delta_exec);

    curr->exec_start = now;/*将exec_start属性置为now*/

    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);

    }

    }

    struct rq

     

    新葡亰496net 6

    相应的,就绪队列里也就分为八个子队列,三个维护普通进度,称之为cfs队列(cfs_rq),三个护卫实时进度,称之为rt队列(rt_rq).

    struct sched_class

    该调节类类似于二个模块链,支持内核调解程序工作。各样调治程序模块供给贯彻 struct sched_class建议的一组函数。

    新葡亰496net 7

    sched_class 结构体简要介绍

    函数功效表达:

    enqueue_task:当有些职务进入可运营景况时,该函数将获取调用。它将调治实体(进度)放入红黑树中,并对 nr_running 变量加 1。

    dequeue_task:当有个别职责退出可运维意况时调用该函数,它将从红黑树中去掉对应的调整实体,并从 nr_running 变量中减 1。

    yield_task:在 compat_yield sysctl 关闭的状态下,该函数实际上实行先出队后入队;在这种场地下,它将调整实体放在红黑树的最右端。

    check_preempt_curr:该函数将检查当前运转的天职是不是被私吞。在事实上抢占正在周转的职分在此之前,CFS 调治程序模块将实行公平性测试。这将使得唤醒式(wakeup)抢占。

    pick_next_task:该函数选择接下去要运转的最合适的长河。

    load_balance:每种调整程序模块实现七个函数,load_balance_start() 和load_balance_next(),使用那多个函数完成三个迭代器,在模块的 load_balance 例程中调用。内核调节程序行使这种艺术完成由调整模块管理的长河的负载平衡。

    set_curr_task:当任务修改其调整类或修改其任务组时,将调用那个函数。

    task_tick:该函数常常调用自 time tick 函数;它恐怕滋生进程切换。那将使得运维时(running)抢占。

    task_new:内核调治程序为调节模块提供了保管新职务运维的机遇。CFS 调解模块使用它举行组调治,而用于实时职分的调节模块则不会选取这些函数。

    该结构体是地面cpu全数进度组成的服服帖帖队列,在linux内核中,进度被分成普通进度和实时进度,这两种过程的调解计策是见仁见智的,由此在31-32行能够看到rq结构体中又内嵌了struct cfs_rq cfs和struct rt_rq rt多少个子就绪队列,分别来公司普通进度和实时进程(普通进度将利用完全公平级调动度战术cfs,而实时进程将选拔实时调节计谋),第33行struct dl_rq dl调治空闲进度,一时半刻不研讨。所以,假若大家研讨的是平日进度的调节,需求关注的正是struct cfs_rq cfs队列;假如商讨的是实时进程,就只关切struct rt_rq rt队列。

    经过的预先等第调节了其权重值,task_struct中与事先级相关数据成员:
        a)static_prio,指普通进程的静态优先级(实时进程没用该参数),值越小则优先级越高。静态优先级是经过运营时分配的优先级。它可以用nice()可能sched_setscheduler()系统调用改造,不然在运营期间平昔维持一定。

     

    别的,即便调解器最后调治的目的是经过,但在此地用一个调节实体(struct sched_entity||struct sched_rt_entity)表示一个要被调解的对象.
    对于一般的历程调治来讲,多少个调治实体对象内嵌在task_struct中,对于组调整(cgroup)来讲,其内嵌在task_group中.

    运转队列中CFS 有关的字段

    对此每个运营队列,都提供了一种结构来保存相关红黑树的新闻。

    struct cfs_rq {

    struct load_weight load;/*运营负载*/

    unsigned long nr_running;/*运作进度个数*/

    u64 exec_clock;

    u64 min_vruntime;/*封存的小大运营时刻*/

    struct rb_root tasks_timeline;/*运维队列树根*/

    struct rb_node *rb_leftmost;/*封存的红黑树最左边的

    节点,这一个为最小运行时刻的节点,当进度

    选取下贰个来运营时,直接选取那个*/

    struct list_head tasks;

    struct list_head *balance_iterator;

    /*

    * 'curr' points to currently running entity on this cfs_rq.

    * It is set to NULL otherwise (i.e when none are currently running).

    */

    struct sched_entity *curr, *next, *last;

    unsigned int nr_spread_over;

    #ifdef CONFIG_FAIR_GROUP_SCHED

    struct rq *rq; /* cpu runqueue to which this cfs_rq is attached */

    /*

    * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in

    * a hierarchy). Non-leaf lrqs hold other higher schedulable entities

    * (like users, containers etc.)

    *

    * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This

    * list is used during load balance.

    */

    struct list_head leaf_cfs_rq_list;

    struct task_group *tg; /* group that "owns" this runqueue */

    #ifdef CONFIG_SMP

    /*

    * the part of load.weight contributed by tasks

    */

    unsigned long task_weight;

    /*

    *  h_load = weight * f(tg)

    *

    * Where f(tg) is the recursive weight fraction assigned to

    * this group.

    */

    unsigned long h_load;

    /*

    * this cpu's part of tg->shares

    */

    unsigned long shares;

    /*

    * load.weight at the time we set shares

    */

    unsigned long rq_weight;

    #endif

    #endif

    };

    1.1见怪不怪进程的就绪队列struct cfs_rq(kernel/sched/sched.h)

           注意:关于a),注意本文的末梢加多的批注。

     

    cfs队列用红黑树组织调治实体,cfs调节算法总是挑三拣四最左侧的调节实体.

    新葡亰496net 8新葡亰496net 9

        b)rt_priority,表示实时进度的优先级(普通进度没用该参数),它的值介于[0~99]之间。rt_priority的值越大其优先级越高。
        c)normal_prio,由于static_prio和rt_priority与事先级的关联性不平等,因而用normal_prio统一下“单位”,统一成:normal_prio值越小则进程优先级越高。由此,normal_prio也足以知道为:统一了单位的“静态”优先级。
        d)prio,在系统中应用prio剖断进程优先级,prio是经过的动态优先级,其代表经过的得力优先级。对于实时过程来讲,有效优先级prio就杰出它的normal_prio。普通进度能够有的时候提升优先级,通过改换prio完结,动态优先级的压实不影响进程的静态优先级。父进程的动态优先级不会遗传给子进程,子进程的动态优先级prio开始化为父进程的静态优先级。

     

    rt队列的构造是近乎rt_queue[100][list]如此的构造,这里99对应实时经过的0-九十九个优先级,二维是链表,也正是依附实时进程的优先级,将经过挂载相应的list上.

    内核 2.6.24 中的变化

    新本子中不再追赶全局石英钟(fair_clock),任务之间将互动追赶。将引入各类任务(调整实体)的机械钟 vruntime(wall_time/task_weight),并且将使用类似的平分时间初步化新职分的手表。其余入眼更动将震慑重大数据结构。上面展现了 struct sched_entity 中的预期变动:

    新葡亰496net 10

    2.6.24 版本中 sched_entity 结构的预期变动

    新葡亰496net 11

    2.6.24 版本中 cfs_rq 结构的预料变动

    新葡亰496net 12

    2.6.24本子中新丰硕的 task_group 结构

    种种职务都盯住它的运作时,并根据该值对义务拓展排队。那代表运营最少的天职将放在树的最左侧。同样,通过对时间加权划分优先级。每个任务在上面包车型的士小运段内力求获得纯粹调治:

    sched_period = (nr_running > sched_nr_latency) ? sysctl_sched_latency : ((nr_running * sysctl_sched_latency) / sched_nr_latency)

    其中 sched_nr_latency = (sysctl_sched_latency / sysctl_sched_min_granularity)。那意味着,当可运维职分数当先 latency_nr 时,将线性延长调整周期。sched_fair.c 中定义的 sched_slice() 是开始展览那一个总结的义务。由此,倘使每一个可运营职务运维与 sched_slice() 等价的时刻,那么将消费的时刻为 sched_period,每个职分将运营与其权重成比例的时间量。其它,在任何时刻,CFS 都答应超前运维 sched_period,因为最终实践调整的天职就要这一个期限内再也运转。

    故此,当一个新任务变为可运维处境时,对其岗位有严酷的须求。在颇具其余职分运维之前,此职分不可能运作;不然,将损坏对那个职务作出的允诺。然则,由于该职责真正实行了排队,对运作队列的额外权重将压编别的具有任务的岁月片,在 sched_priod 的终极释放一点职分,刚好满意新职分的供给。那几个新的职分就被放在这一个职位。

     1 /* CFS-related fields in a runqueue */
     2 struct cfs_rq {
     3     struct load_weight load;
     4     unsigned int nr_running, h_nr_running;
     5 
     6     u64 exec_clock;
     7     u64 min_vruntime;
     8 #ifndef CONFIG_64BIT
     9     u64 min_vruntime_copy;
    10 #endif
    11 
    12     struct rb_root tasks_timeline;
    13     struct rb_node *rb_leftmost;
    14 
    15     /*
    16      * 'curr' points to currently running entity on this cfs_rq.
    17      * It is set to NULL otherwise (i.e when none are currently running).
    18      */
    19     struct sched_entity *curr, *next, *last, *skip;
    20 
    21 #ifdef    CONFIG_SCHED_DEBUG
    22     unsigned int nr_spread_over;
    23 #endif
    24 
    25 #ifdef CONFIG_SMP
    26     /*
    27      * CFS Load tracking
    28      * Under CFS, load is tracked on a per-entity basis and aggregated up.
    29      * This allows for the description of both thread and group usage (in
    30      * the FAIR_GROUP_SCHED case).
    31      */
    32     unsigned long runnable_load_avg, blocked_load_avg;
    33     atomic64_t decay_counter;
    34     u64 last_decay;
    35     atomic_long_t removed_load;
    36 
    37 #ifdef CONFIG_FAIR_GROUP_SCHED
    38     /* Required to track per-cpu representation of a task_group */
    39     u32 tg_runnable_contrib;
    40     unsigned long tg_load_contrib;
    41 
    42     /*
    43      *   h_load = weight * f(tg)
    44      *
    45      * Where f(tg) is the recursive weight fraction assigned to
    46      * this group.
    47      */
    48     unsigned long h_load;
    49     u64 last_h_load_update;
    50     struct sched_entity *h_load_next;
    51 #endif /* CONFIG_FAIR_GROUP_SCHED */
    52 #endif /* CONFIG_SMP */
    53 
    54 #ifdef CONFIG_FAIR_GROUP_SCHED
    55     struct rq *rq;    /* cpu runqueue to which this cfs_rq is attached */
    56 
    57     /*
    58      * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
    59      * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
    60      * (like users, containers etc.)
    61      *
    62      * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
    63      * list is used during load balance.
    64      */
    65     int on_list;
    66     struct list_head leaf_cfs_rq_list;
    67     struct task_group *tg;    /* group that "owns" this runqueue */
    68 
    69 #ifdef CONFIG_CFS_BANDWIDTH
    70     int runtime_enabled;
    71     u64 runtime_expires;
    72     s64 runtime_remaining;
    73 
    74     u64 throttled_clock, throttled_clock_task;
    75     u64 throttled_clock_task_time;
    76     int throttled, throttle_count;
    77     struct list_head throttled_list;
    78 #endif /* CONFIG_CFS_BANDWIDTH */
    79 #endif /* CONFIG_FAIR_GROUP_SCHED */
    80 };
    

    注:

    现今開始分情景深入分析CFS。

    rt调整算法总是先选优先级最高的调整实体.

    优先级和CFS

    CFS 不直接使用优先级而是将其看作允许职务施行的年华的衰减周密。 低优先级职责具备越来越高的衰减周详,而高优先级任务具备相当的低的衰减周全。 这象征与高优先级职务比较,低优先级任务允许职分实践的岁月成本得越来越快。 那是八个一矢双穿的缓慢解决方案,能够制止维护按优先级调节的运作队列。

    struct cfs_rq

    出于在好几境况下供给暂且升高进度的优先级,因而不但须要静态优先级和普通优先级,还索要动态优先级prio;

     

    这里先贴出这几个概念的struct,图1显得了她们之间的关系.

    CFS 组调度

    考虑三个两用户示例,用户 A 和用户 B 在一台机器上运转作业。用户 A 唯有四个作业正在运维,而用户 B 正在运作 48 个作业。组调解使 CFS 能够对用户 A 和用户 B 进行公平级调动度,而不是对系统中运维的 四二十个作业进展公平级调动度。种种用户各具备 八分之四 的 CPU 使用。用户 B 使用自身 二分之一的 CPU 分配运营他的 48 个作业,而不会占用属于用户 A 的其它 一半 的 CPU 分配。

    CFS 调治模块(在 kernel/sched_fair.c 中完结)用于以下调节攻略:SCHED_NORMAL、SCHED_BATCH 和 SCHED_IDLE。对于 SCHED_RR 和 SCHED_FIFO 计策,将利用实时调解模块(该模块在 kernel/sched_rt.c 中实现)。

    CFS 另贰个妙不可言的地方是组调节 概念(在 2.6.24 内核中引进)。组调整是另一种为调整带来公平性的主意,越发是在拍卖发生过多别样职责的天职时。 假如三个生出了非常的多任务的服务器要并行化进入的再三再四(HTTP服务器的卓著架构)。不是装有职分都会被统一公平对待,CFS 引进了组来管理这种表现。发生任务的服务器进度在全路组中(在三个档案的次序结构中)共享它们的虚构运维时,而单个职责保持其和谐单独的虚拟运维时。那样单个任务会接受与组大约一样的调治时间。我们会发觉 /proc 接口用于管理进度档次结构,让大家对组的产生艺术有一起的操纵。使用此安排,我们能够跨用户、跨进度或其变体分配公平性。

    cfs_rq就绪队列是以红黑树的花样来公司调解实体。第12行tasks_timeline成员正是红黑树的树根。第13行rb_leftmost指向了红黑树最左侧的左孩子(下一个可调解的实业)。第19行curr指向当下正运营的实体,next指向将被提示的经过,last指向唤醒next进度的经过,next和last用法前边会提到。第55行rq指向了该cfs_rq就绪队列所属的rq队列。

    参谋《深切Linux内核架构》p70-76、 p_288-290;

    二、创造进度 

    struct rq {
        unsigned int nr_running;    //就绪进程的总数目
        struct load_weight load;    //当前队列的总负荷
        struct cfs_rq cfs;          //完全公平队列
        struct rt_rq rt;            //实时队列
        struct task_struct *curr, *idle, *stop; //curr指向当前正在执行的task,
        u64 clock;                  //该队列自身的时钟(实际时间,调度算法还有个虚拟时间的概念)
        ...
    };
    

    调节类和域

    与 CFS 一同引进的是调治类概念。各个义务都属于四个调节类,那决定了职务将什么调治。 调解类定义三个通用函数集(通过sched_class),函数集定义调节器的表现。举个例子,每种调节器提供一种艺术, 增多要调治的天职、调出要运转的下多个职分、提要求调治器等等。每种调节器类都在一对一而再接的列表中相互相连,使类能够迭代(举个例子,要启用给定管理器的剥夺)。一般结构如下图所示。注意,将任务函数加入队列或剥退出阵容列只需从一定调解结构中参加或移除职务。 函数 pick_next_task 接纳要实践的下三个职责(取决于调治类的具体政策)。

    新葡亰496net 13

    调节类视图

    可是绝不忘了调解类是天职结构本人的一片段,那点简化了职务的操作,无论其调整类具体怎么兑现。比如, 以下函数用 ./kernel/sched.c 中的新职务抢占当前运营职责(个中 curr 定义了当下运作职分, rq 代表 CFS 红黑树而 p 是下贰个要调整的天职):

    static inline void check_preempt( struct rq *rq, struct task_struct *p ){ 

     rq->curr->sched_class->check_preempt_curr( rq, p );

    }

    如若此职责正利用公平级调动度类,则 check_preempt_curr() 将解析为check_preempt_wakeup()。 我们能够在 ./kernel/sched_rt.c,/kernel/sched_fair.c 和 ./kernel/sched_idle.c 中查阅这个关系。

    调解类是调解产生变化的另一个风趣的地点,可是随着调节域的充实,作用也在扩展。 这么些域允许你出于负载平衡和隔离的指标将三个或五个计算机按档案的次序关系分组。 三个或多个计算机可以共享调治战略(并在在那之中间维持负载平衡)或落实独立的调整计策从而故意隔离任务。

    1.2实时进度的就绪队列struct rt_rq(kernel/sched/sched.h)

             linux内核的优先级承袭协议(pip)

    首先个情景选为进度创立时CFS相关变量的伊始化。
    咱俩通晓。Linux制造进度使用fork也许clone或然vfork等系列调用,终于都会到do_fork。

    稳妥队列,每一个cpu对应贰个妥贴队列,前面为描述方便,假定系统cpu核数为1.  

    2.6.24 中的组调节有如何退换

    在 2.6.24中,大家将能够对调整程序开始展览调优,从而达成对用户或组的公平性,而不是天职公平性。能够将职分拓展分组,形成两个实体,调治程序将一样对待那一个实体,继而公平对待实体中的职务。要启用这一个个性,在编写翻译内核时必要选用 CONFIG_FAIR_GROUP_SCHED。目前,只有 SCHED_NORMAL 和SCHED_BATCH 职责能够进行分组。

    能够利用八个单身的不二等秘书技对职务拓展分组,它们各自依据:

    1.用户 ID。

    2.cgroup pseudo 文件系统:那些选项使管理员能够依附必要创设组。有关越多细节,阅读内核源文书档案目录中的 cgroups.txt 文件。

    3.基本配置参数 CONFIG_FAIR_USER_SCHED 和 CONFIG_FAIR_CGROUP_SCHED 可扶助您实行选择。

    通过引入调解类并经过压实调节总结新闻来简化调节和测试,那一个新的调治程序特别扩大了调治功用。

    新葡亰496net 14新葡亰496net 15

             进程优先级翻盘问题的减轻  

    设若未有设置CLONE_STOPPED,则会进来wake_up_new_task函数,我们看看那些函数的重大多数

    struct cfs_rq {  //删减版
        struct load_weight load;  //该队列的总权重
        unsigned int nr_running, h_nr_running;  //该队列中的任务数
        u64 min_vruntime;    //一个虚拟时间,后面细说
        struct rb_root tasks_timeline;  //该cfs队列的红黑树,所有的进程用它来组织
        struct rb_node *rb_leftmost;  //指向红黑树最左边的一个节点,也就是下次将被调度器装载的
        struct sched_entity *curr, *next, *last, *skip; //curr指向当前正在执行的进程
        struct rq *rq;          //自己所属的rq
        struct task_group *tg;  //该cfs队列所属的task_group(task_group是实现cgroup的基础,后面再说)
        ...
    };
    

    其他调整器

    一而再钻探调解,您将开掘正在开垦中的调治器将会突破品质和扩展性的数不清。Con Kolivas 没有被她的 Linux 经验羁绊,他支付出了另贰个 Linux 调整器,其缩写为:BFS。该调解器听他们说在 NUMA 系统以及运动器具上存有越来越好的习性, 并且被引进了 Android 操作系统的一款衍生产品中。

     1 /* Real-Time classes' related field in a runqueue: */
     2 struct rt_rq {
     3     struct rt_prio_array active;
     4     unsigned int rt_nr_running;
     5 #if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED
     6     struct {
     7         int curr; /* highest queued rt task prio */
     8 #ifdef CONFIG_SMP
     9         int next; /* next highest */
    10 #endif
    11     } highest_prio;
    12 #endif
    13 #ifdef CONFIG_SMP
    14     unsigned long rt_nr_migratory;
    15     unsigned long rt_nr_total;
    16     int overloaded;
    17     struct plist_head pushable_tasks;
    18 #endif
    19     int rt_queued;
    20 
    21     int rt_throttled;
    22     u64 rt_time;
    23     u64 rt_runtime;
    24     /* Nests inside the rq lock: */
    25     raw_spinlock_t rt_runtime_lock;
    26 
    27 #ifdef CONFIG_RT_GROUP_SCHED
    28     unsigned long rt_nr_boosted;
    29 
    30     struct rq *rq;
    31     struct task_group *tg;
    32 #endif
    33 };
    

            为了在Linux中央银行使Priority Inheritance Protocol协议来减轻先行级反转难题,Linux中引进实时互斥量rt_mutex,在task_struc结构体中也引进了pi_waiters链表,要求小心的流水生产线为:

    [cpp] view plaincopy

    cfs就绪队列,用红黑树组织,这里有二个虚拟时间(vruntime)的概念,来担保在担保高优先级进度占用越多cpu的前提下,保险全部进度被公平的调整.

    展望

    对于 Linux 本事来讲,惟一不改变的正是定位的成形。前天,CFS 是 2.6 Linux 调解器; 前天或然就能是另多个新的调节器或一套可以被静态或动态调用的调整器。 CFS、OdysseySDL 以及水源背后的历程中还也许有众多潜在等待我们去切磋。

    struct rt_rq

             rt_mutex_slowlock() ----> __rt_mutex_slowlock() ---->

    1. /* 
    2.  * wake_up_new_task - wake up a newly created task for the first time. 
    3.  * 
    4.  * This function will do some initial scheduler statistics housekeeping 
    5.  * that must be done for every newly created context, then puts the task 
    6.  * on the runqueue and wakes it. 
    7.  */  
    8. void wake_up_new_task(struct task_struct *p, unsigned long clone_flags)  
    9. {  
    10.     .....  
    11.     if (!p->sched_class->task_new || !current->se.on_rq) {  
    12.         activate_task(rq, p, 0);  
    13.     } else {  
    14.         /* 
    15.          * Let the scheduling class do new task startup 
    16.          * management (if any): 
    17.          */  
    18.         p->sched_class->task_new(rq, p);  
    19.         inc_nr_running(rq);  
    20.     }  
    21.     check_preempt_curr(rq, p, 0);  
    22.     .....  
    23. }  
    struct rt_prio_array {  //实时队列用这个二位链表组织进程
        DECLARE_BITMAP(bitmap, MAX_RT_PRIO 1);  
        struct list_head queue[MAX_RT_PRIO];    // MAX_RT_PRIO=100 上面解释过了
    };
    
    struct rt_rq {
        struct rt_prio_array active;    //组织所有就绪进程
        unsigned int rt_nr_running;     //就绪进程数目
        int rt_throttled;               //禁止调度标记
        u64 rt_time;                    //当前队列累计运行时间
        u64 rt_runtime;                 //当前队列最大运行时间
        unsigned long rt_nr_boosted;
        struct rq *rq;                  //所属rq
        struct task_group *tg;          //所属cgroup
        ...
    };
    

    2.调整实体(include/linux/sched.h)

                     task_blocks_on_rt_mutex() ---->  __rt_mutex_adjust_prio()

     上面十三分if语句小编不亮堂哪些情况下会为真。笔者測试了一下。在地方五个支行各加一个计数器,
    想来为实在情状只有有2次(作者决不依照的推測是idle进程和init进度),而猜度为假的境况有近万次。
    由此我们唯有看之下的支行,假诺哪位前辈知道真相的话还望告诉自身一声,不少谢。

     

    2.1一般过程的调整实体sched_entity

                                                                       |--> rt_mutex_adjust_prio_chain()

    再以下就是检測是或不是能够产生抢占,假诺新历程能够抢占当前进度则进行进度切换。

    实时就绪队列,用二维链表组织.
    因为实时进度优先级总是超出普通进度,又不应用完全公平算法,极端气象下实时进程一向占着cpu,普通进度等不到cpu能源.
    因此完毕用rt_time,rt_runtime用来界定实时进程占用cpu能源,举个例子rt_time = 100 rt_runtime = 95,那多少个变量对应cgroup下的cpu.rt_period_us, cpu.rt_runtime_us.
    那就是说该rq下,全部的实时进度只可以占用cpu财富的95%,剩下的%5的财富留给普通进度使用.

     1 struct sched_entity {
     2     struct load_weight    load;        /* for load-balancing */
     3     struct rb_node        run_node;
     4     struct list_head    group_node;
     5     unsigned int        on_rq;
     6 
     7     u64            exec_start;
     8     u64            sum_exec_runtime;
     9     u64            vruntime;
    10     u64            prev_sum_exec_runtime;
    11 
    12     u64            nr_migrations;
    13 
    14 #ifdef CONFIG_SCHEDSTATS
    15     struct sched_statistics statistics;
    16 #endif
    17 
    18 #ifdef CONFIG_FAIR_GROUP_SCHED
    19     int            depth;
    20     struct sched_entity    *parent;
    21     /* rq on which this entity is (to be) queued: */
    22     struct cfs_rq        *cfs_rq;
    23     /* rq "owned" by this entity/group: */
    24     struct cfs_rq        *my_q;
    25 #endif
    26 
    27 #ifdef CONFIG_SMP
    28     /* Per-entity load-tracking */
    29     struct sched_avg    avg;
    30 #endif
    31 };
    

              __rt_mutex_adjust_prio调治了近些日子享有锁的进程的动态优先级(承继自等待队列中保有进程的参天优先级),rt_mutex_adjust_prio_chain()如若被调节的动态优先级的进程也在守候某些能源,那么也要链式地调治相应进度的动态优先级。

    咱俩一个二个函数来看
    p->sched_class->task_new相应的函数是task_new_fair:

    struct sched_entity {
        struct load_weight load;    //该调度实体的权重(cfs算法的关键 >> cgroup限制cpu的关键)
        struct rb_node run_node;    //树节点,用于在红黑树上组织排序
        u64 exec_start;             //调度器上次更新这个实例的时间(实际时间)
        u64 sum_exec_runtime;       //自进程启动起来,运行的总时间(实际时间)
        u64 vruntime;               //该调度实体运行的虚拟时间
        u64 prev_sum_exec_runtime;  //进程在上次被撤销cpu时,运行的总时间(实际时间)
        struct sched_entity *parent;//父调度实体
        struct cfs_rq *cfs_rq;      //自己所属cfs就绪队列
        struct cfs_rq *my_q;        //子cfs队列,组调度时使用,如果该调度实体代表普通进程,该字段为NULL
        ...
    };
    

    各种进度描述符中均隐含三个该结构体变量,内核使用该结构体来将惯常进程组织到使用完全公平级调动度战术的就绪队列中(struct rq中的cfs队列中,上边提到过),该结构体有七个职能,一是富含有经过调治的新闻(举例进程的运维时刻,睡眠时间等等,调治程序参谋这一个音讯决定是还是不是调治进度),二是选择该结构体来组织进程,第3行的struct rb_node类型结构体变量run_node是红黑树节点,因而struct sched_entity调节实体将被集体成红黑树的款型,同一时候意味着平日进度也被公司成红黑树的样式。第18-25行是和组调治有关的成员,要求开启组调整。第20行parent望文生义指向了近年来实体的上一级实体,前边再介绍。第22行的cfs_rq指向了该调治实体所在的稳妥队列。第24行my_q指向了本实体具备的伏贴队列(调整组),该调整组(包含组员实体)属于下贰个品级,和本实体不在同三个等级,该调解组中有所成员实体的parent域指向了本实体,那就解释了上边的parent,别的,第19行depth代表了此行列(调节组)的深浅,种种调解组都比其parent调治组深度大1。内核信赖my_q域达成组调节。

    有关Priority Inversion能够参照《Operating System Concepts》9_ed p217-218                                                                                                                       

    [cpp] view plaincopy

    一般进度使用完全公平算法来保管队列中的进度都可获取公平的调解机会,同不时间兼任高优先级的长河占用越多的cpu能源.

    2.2实时进程的调治实体 sched_rt_entity

    1. /* 
    2.  * Share the fairness runtime between parent and child, thus the 
    3.  * total amount of pressure for CPU stays equal - new tasks 
    4.  * get a chance to run but frequent forkers are not allowed to 
    5.  * monopolize the CPU. Note: the parent runqueue is locked, 
    6.  * the child is not running yet. 
    7.  */  
    8. static void task_new_fair(struct rq *rq, struct task_struct *p)  
    9. {  
    10.     struct cfs_rq *cfs_rq = task_cfs_rq(p);  
    11.     struct sched_entity *se = &p->se, *curr = cfs_rq->curr;  
    12.     int this_cpu = smp_processor_id();  
    13.     sched_info_queued(p);  
    14.     update_curr(cfs_rq);  
    15.     place_entity(cfs_rq, se, 1);  
    16.     /* 'curr' will be NULL if the child belongs to a different group */  
    17.     if (sysctl_sched_child_runs_first && this_cpu == task_cpu(p) &&  
    18.             curr && curr->vruntime < se->vruntime) {  
    19.         /* 
    20.          * Upon rescheduling, sched_class::put_prev_task() will place 
    21.          * 'current' within the tree based on its new key value. 
    22.          */  
    23.         swap(curr->vruntime, se->vruntime);  
    24.         resched_task(rq->curr);  
    25.     }  
    26.     enqueue_task_fair(rq, p, 0);  
    27. }  
    struct sched_rt_entity {
        struct list_head run_list;  //链表节点,用于组织实时进程
        struct sched_rt_entity  *parent;  //父调度实体  
        struct rt_rq        *rt_rq; //自己所属rt就绪队列
        struct rt_rq        *my_q;  //子rt队列,组调度时使用,如果该调度实体代表普通进程,该字段为NULL
        ...
    };
    
     1 struct sched_rt_entity {
     2     struct list_head run_list;
     3     unsigned long timeout;
     4     unsigned long watchdog_stamp;
     5     unsigned int time_slice;
     6 
     7     struct sched_rt_entity *back;
     8 #ifdef CONFIG_RT_GROUP_SCHED
     9     struct sched_rt_entity    *parent;
    10     /* rq on which this entity is (to be) queued: */
    11     struct rt_rq        *rt_rq;
    12     /* rq "owned" by this entity/group: */
    13     struct rt_rq        *my_q;
    14 #endif
    15 };
    

     这里有八个首要的函数,update_curr,place_entity。

    实时进度的调整算法是极粗略的,优先级高的可实时抢占优先级低的进度,直到进度本身放任cpu,不然可"一直"运维.(加了引号的直白)

    该结构体和上个结构体是相仿的,只可是用来集团实时进程,实时进程被集体到struct rq中的rt队列中,上面有涉嫌。每一种进程描述符中也饱含二个该结构体。该结构体中未有包蕴struct rb_node类型结构体变量,而在第1行出现了struct list_head类型结构体变量run_list,因而得以见到实时进程的妥当队列是双向链表格局,而不是红黑数的款式。

    当中update_curr在那边能够忽视。它是翻新进度的一些随时间变化的新闻。大家放手后边再看,
    place_entity是翻新新历程的vruntime值。以便把他插入红黑树。
    新历程的vruntime明确之后有一个揣测,满足上面多少个规范时,调换老爹和儿子进度的vruntime:
    1.sysctl安装了子进度优先试行
    2.fork出的子进程与父进程在同一个cpu上
    3.父进程不为空(这么些规格为啥会时有产生暂不鲜明,难道是fork第一个经过的时候?)
    4.父历程的vruntime小于子进度的vruntime
    多少个标准都还比較好掌握,说下第多个,由于CFS总是挑三拣四vruntime最小的经过施行,
    于是必须保障子进程vruntime比父进度小,笔者未有直接把子进度的vruntime设置为比较小的值,
    而是採用调换的法子,能够幸免通过fork新进程来多量并吞cpu时间,立即还要讲到。

    struct sched_class {
        const struct sched_class *next; //sched_class指针,用于将cfs类 rt类 idle类串起来
        // 全是函数指针,不同的类去各自实现,由调度器统一调用,
        void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
        void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
        void (*yield_task) (struct rq *rq);
        bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
        void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
        struct task_struct * (*pick_next_task) (struct rq *rq);
        void (*put_prev_task) (struct rq *rq, struct task_struct *p);
        void (*set_curr_task) (struct rq *rq);
        void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
        void (*task_fork) (struct task_struct *p);
        void (*switched_from) (struct rq *this_rq, struct task_struct *task);
        void (*switched_to) (struct rq *this_rq, struct task_struct *task);
        void (*prio_changed) (struct rq *this_rq, struct task_struct *task,int oldprio);
        unsigned int (*get_rr_interval) (struct rq *rq,
        struct task_struct *task);
        void (*task_move_group) (struct task_struct *p, int on_rq);
    };
    

    3.调度类(kernel/sched/sched.h)

    最后,调用enqueue_task_fair将新进度插入CFS红黑树中

    struct sched_class只是向调治器申明了一组函数,具体的完成是由逐一调解类(cfs rt)达成.大旨调解器只关怀在什么样机会调用那多少个函数指针,不珍贵具体完结.
    图1

     1 struct sched_class {
     2     const struct sched_class *next;
     3 
     4     void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
     5     void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
     6     void (*yield_task) (struct rq *rq);
     7     bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
     8 
     9     void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
    10 
    11     /*
    12      * It is the responsibility of the pick_next_task() method that will
    13      * return the next task to call put_prev_task() on the @prev task or
    14      * something equivalent.
    15      *
    16      * May return RETRY_TASK when it finds a higher prio class has runnable
    17      * tasks.
    18      */
    19     struct task_struct * (*pick_next_task) (struct rq *rq,
    20                         struct task_struct *prev);
    21     void (*put_prev_task) (struct rq *rq, struct task_struct *p);
    22 
    23 #ifdef CONFIG_SMP
    24     int  (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
    25     void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
    26 
    27     void (*post_schedule) (struct rq *this_rq);
    28     void (*task_waking) (struct task_struct *task);
    29     void (*task_woken) (struct rq *this_rq, struct task_struct *task);
    30 
    31     void (*set_cpus_allowed)(struct task_struct *p,
    32                  const struct cpumask *newmask);
    33 
    34     void (*rq_online)(struct rq *rq);
    35     void (*rq_offline)(struct rq *rq);
    36 #endif
    37 
    38     void (*set_curr_task) (struct rq *rq);
    39     void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
    40     void (*task_fork) (struct task_struct *p);
    41     void (*task_dead) (struct task_struct *p);
    42 
    43     void (*switched_from) (struct rq *this_rq, struct task_struct *task);
    44     void (*switched_to) (struct rq *this_rq, struct task_struct *task);
    45     void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
    46                  int oldprio);
    47 
    48     unsigned int (*get_rr_interval) (struct rq *rq,
    49                      struct task_struct *task);
    50 
    51 #ifdef CONFIG_FAIR_GROUP_SCHED
    52     void (*task_move_group) (struct task_struct *p, int on_rq);
    53 #endif
    54 };
    

    以下大家看下place_entity是怎么计算新进程的vruntime的。

    新葡亰496net 16
    如图1,体现了一一协会之间的关系,注意申明出来的调解组.

    根本注脚了叁个调整类sched_class的构造体类型,用来兑现区别的调治计策,能够看出该结构体成员都以函数指针,这几个指针指向的函数正是调整计谋的切实可行落实,全数和经过调整有关的函数都平素恐怕直接调用了这几个成员函数,来完结进度调整。其余,各个进程描述符中都包括二个针对性该组织体类型的指针sched_class,指向了所利用的调整类。上边我们看下完全公平级调动度计策类的概念和起头化(kernel/sched/fair.c)。

    [cpp] view plaincopy

    为了集中,上面探究时不思索有新历程入队,也许就绪队列的子机因为等待别的财富(IO)出队等情景,只以周期性调解(系统各种一段时间发生贰次tick中断,此时系统有机遇更新一些总计变量,同期总结当前历程是或不是已经运转了十足长日子,需求调治)为例表明调治器的行为.
    作者们知晓实时进度是会抢占普通进度的,所以当有实时进度进入就绪队列时,普通进程异常快就能够被撤除cpu,让给实时进程,由此实时进度和平常进程的抢占都是在实时进度入队时实时触发的,所以周期性调解不用思量这种意况.
    那正是说情形就变轻便了,周期调解只需调用当前task的sched_class.task_tick就足以了,假诺task是实时进程,相当于调用实时进度的调治类的task_tick完毕,当task是日常进度时同理.

    1 const struct sched_class fair_sched_class;
    
    1. static void  
    2. place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)  
    3. {  
    4.     u64 vruntime = cfs_rq->min_vruntime;  
    5.     /* 
    6.      * The 'current' period is already promised to the current tasks, 
    7.      * however the extra weight of the new task will slow them down a 
    8.      * little, place the new task so that it fits in the slot that 
    9.      * stays open at the end. 
    10.      */  
    11.     if (initial && sched_feat(START_DEBIT))  
    12.         vruntime  = sched_vslice(cfs_rq, se);  
    13.     if (!initial) {  
    14.         //先不看这里,  
    15.     }  
    16.     se->vruntime = vruntime;  
    17. }  

    ------cfs调整算法
    一起公平调整算法的主要利润是它能够确认保证高优先级的长河占用越来越多的cpu时间,当时又能保障全数进度公平的获得调整机会.
    位置也事关了虚拟时间的定义(vruntime),高优先级的经过获得了更加多的cpu时间(实际时间runtime),但从虚拟时间(vruntime)的维度来讲,种种进度跑的vruntime是千篇一律长的.
    怎么明白?待笔者举个不是很形象的尖栗:
    大家把A B C八个经过比为多少个太婆,把调解器视为多少个红领巾,红领巾的对象是同一时候把五个太婆一同扶到马路对面(完全公平嘛).
    新葡亰496net 17

    概念了三个大局的调解战略变量。初阶化如下:

     
    这里是计量进程的上马vruntime。

    cfs调解算法跟上边红领巾的例子是相仿的,能够把红领巾扶老曾外祖母走过的路程想象为vruntime,一个周期后,他对ABC都以持平的,扶她们走的里程都一律(vruntime时间长度),大家要是A奶奶腿脚特别不改变(A优先级高),那么固然扶A和扶BC走过的路程同样长,但是因为A慢呀,所以料定扶A要花更加长的岁月(实际时间).
    故此cfs调整算法的私人民居房就是,优先级高的进程vruntime增进的慢,ABC多少个经过或许都跑了10vruntime,可是BC花了十一个runtime,而A(优先级高)缺花了20runtime.

     1 const struct sched_class fair_sched_class = {
     2     .next            = &idle_sched_class,
     3     .enqueue_task        = enqueue_task_fair,
     4     .dequeue_task        = dequeue_task_fair,
     5     .yield_task        = yield_task_fair,
     6     .yield_to_task        = yield_to_task_fair,
     7 
     8     .check_preempt_curr    = check_preempt_wakeup,
     9 
    10     .pick_next_task        = pick_next_task_fair,
    11     .put_prev_task        = put_prev_task_fair,
    12 
    13 #ifdef CONFIG_SMP
    14     .select_task_rq        = select_task_rq_fair,
    15     .migrate_task_rq    = migrate_task_rq_fair,
    16 
    17     .rq_online        = rq_online_fair,
    18     .rq_offline        = rq_offline_fair,
    19 
    20     .task_waking        = task_waking_fair,
    21 #endif
    22 
    23     .set_curr_task          = set_curr_task_fair,
    24     .task_tick        = task_tick_fair,
    25     .task_fork        = task_fork_fair,
    26 
    27     .prio_changed        = prio_changed_fair,
    28     .switched_from        = switched_from_fair,
    29     .switched_to        = switched_to_fair,
    30 
    31     .get_rr_interval    = get_rr_interval_fair,
    32 
    33 #ifdef CONFIG_FAIR_GROUP_SCHED
    34     .task_move_group    = task_move_group_fair,
    35 #endif
    36 };
    

    它以cfs队列的min_vruntime为准绳,再增多进度在叁次调整周期中所增加的vruntime。
    此处并非计算进度应该进行的光阴。而是先把经过的已经推行时间设为贰个非常大的值。
    可是该进度明显还从未施行过啊,为啥要那样做吗?
    一旦新历程都能收获最小的vruntime(min_vruntime),那么新历程会首先个被调节试行。
    如此那般技士就会经过不断的fork新进度来让本人的先后平昔占有CPU。这明确是不成立的,
    那跟曾经采纳时间片的基础中老爹和儿子进程要平均父进度的时间片是二个道理。

    怎么落到实处啊?

    能够看到该结构体变量中等学校函授数成员大多,它们贯彻了不一样的效果,待会用到时大家再做深入分析。

    再解释下min_vruntime,那是每二个cfs队列四个的变量,它一般小于等于一切就绪态进度
    的微小vruntime。也会有两样。比方对睡眠进程打开时间补偿会导致vruntime小于min_vruntime。

    runtime = period * (se->load.weight / cfs_rq->load.weight) 公式1
    

    4.经过描述符task_struct(include/linux/sched.h)

    至于sched_vslice计算细节暂时不审美,大要上说正是把概述中提交的八个公式结合起来举个例子以下:
    sched_vslice = (调治周期 * 进度权重 / 全体历程总权重) * NICE_0_LOAD / 进度权重
    也正是算出进程应分配的其实cpu时间,再把它转载为vruntime。
    把这些vruntime加在进程上之后,就相当于以为新进程在这一轮调整中早就实行过了。

    period为一个调整周期,这里不关切它怎么总计,se->load.weight越大,该调治实体在七个调治周期内获取的莫过于时间越长.

     1 struct task_struct {
     2     volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
     3     .....
     4     int on_rq;
     5 
     6     int prio, static_prio, normal_prio;
     7     unsigned int rt_priority;
     8     const struct sched_class *sched_class;
     9     struct sched_entity se;
    10     struct sched_rt_entity rt;
    11 #ifdef CONFIG_CGROUP_SCHED
    12     struct task_group *sched_task_group;
    13 #endif
    14     struct sched_dl_entity dl;
    15     .....
    16     .....
    17     unsigned int policy;
    18     .....
    19     .....
    20     struct sched_info sched_info;
    21     .....
    22     .....
    23 };
    

    好了。到此地又可以回到wake_up_new_task(希望您还没晕,能想起回去:-)),
    看看check_preempt_curr(rq, p, 0);这几个函数就平素调用了check_preempt_wakeup

    vruntime = runtime * (orig_load_value / se->load.weight) 公式2
    

    只列出了和进度调治有关的分子。第6行八个变量代表了平凡进度的多少个优先级,第7行的变量代表了实时进度的事先级。关于进程优先级的概念,我们能够看看《深远领会linux内核架构》那本书的进度管理章节。第8-10行正是我们上面提到的那么些结构体在经过描述符中的定义。第17行的policy代表了脚下历程的调整战术,内核给出了宏定义,它可以在这几个宏中取值,关于详细的助教依然去看《深远驾驭linux内核架构》那本书的进度管理某些。policy取了什么样值,sched_class也应当取相应的调节类指针。

    [cpp] view plaincopy

    将orig_load_value是个常量(1024),可想而知,进度的预先级越高(se->load.weight越大),在runtime相同时其vruntime变化的越慢.
    咱俩将公式1带走公式2可得公式3:

    经过调解进程剖判:

    1. /* 
    2.  * Preempt the current task with a newly woken task if needed: 
    3.  */小编略去了一些不太重要的代码  
    4. static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int sync)  
    5. {  
    6.     struct task_struct *curr = rq->curr;  
    7.     struct sched_entity *se = &curr->se, *pse = &p->se; //se是现阶段历程。pse是新历程  
    8.     /* 
    9.      * Only set the backward buddy when the current task is still on the 
    10.      * rq. This can happen when a wakeup gets interleaved with schedule on 
    11.      * the ->pre_schedule() or idle_balance() point, either of which can 
    12.      * drop the rq lock. 
    13.      * 
    14.      * Also, during early boot the idle thread is in the fair class, for 
    15.      * obvious reasons its a bad idea to schedule back to the idle thread. 
    16.      */  
    17.     if (sched_feat(LAST_BUDDY) && likely(se->on_rq && curr != rq->idle))  
    18.         set_last_buddy(se);  
    19.     set_next_buddy(pse);  
    20.     while (se) {  
    21.         if (wakeup_preempt_entity(se, pse) == 1) {  
    22.             resched_task(curr);  
    23.             break;  
    24.         }  
    25.         se = parent_entity(se);  
    26.         pse = parent_entity(pse);  
    27.     }  
    28. }  
    vruntime  = period * orig_load_value / cfs_rq->load.weight  公式3
    

    经过调解过程分成两片段,一是对进程消息举办改造,首假如修改和调节相关的消息,例如进度的运行时刻,睡眠时间,进度的场地,cpu的负载等等,二是进度的切换。和进度调解相关的装有函数中,唯有schedule函数是用来拓展进度切换的,别的函数都是用来修改进度的调解音信。schedule函数大家在前边的博文中曾经查究过了,这里不再深入分析。对于其他函数,大家将安分守己其意义,逐类来深入分析。

     
    先是对此last和next三个字段给予证实。
    假诺那多个字段不为NULL,那么last指向近些日子被调节出去的经过,next指向被调治上cpu的历程。
    比如A正在实践,被B抢占。那么last指向A。next指向B。

    有鉴于此,进程的vruntime和优先级未有涉及,那样就达到了如约事先级分配实际时间,同样的vruntime呈现公平.

    1.scheduler_tick(kernel/sched/core.c )

    那七个指针有啥样用吗?
    当CFS在调解点选用下三个试行进程时,会预先照管那三个进程。大家前面会看到,这里只有要铭记在心。

    上面cfs_rq.min_vruntime总是保存当前cfs队列各类调治实体中细小的vruntime(不是太严厉,不影响).
    而红黑树各样sched_entity排序的key为(sched_entity->vruntime - cfs_rq.min_vruntime),这样优先级越高,同样实际时间的sched_entity->vruntime越小.
    对应红黑树的key越小,越邻近红黑树左边,cfs调解算法正是选项红黑树最左侧的sched_entity给cpu装载.

     1 void scheduler_tick(void)
     2 {
     3     int cpu = smp_processor_id();
     4     struct rq *rq = cpu_rq(cpu);
     5     struct task_struct *curr = rq->curr;
     6 
     7     sched_clock_tick();
     8 
     9     raw_spin_lock(&rq->lock);
    10     update_rq_clock(rq);
    11     curr->sched_class->task_tick(rq, curr, 0);
    12     update_cpu_load_active(rq);
    13     raw_spin_unlock(&rq->lock);
    14 
    15     perf_event_task_tick();
    16 
    17 #ifdef CONFIG_SMP
    18     rq->idle_balance = idle_cpu(cpu);
    19     trigger_load_balance(rq);
    20 #endif
    21     rq_last_tick_reset(rq);
    22 }
    

    <
    那五个指针仅仅使用三遍。就是在地点那么些函数退出后,重回用户空间时会触发schedule,
    在那边选取下八个调治进度时会优先挑选next,次优先挑选last。选拔完后。就能够清空那多少个指针。
    如此设计的来头是,在上边的函数中检測结果是力所能致抢占并不代表已经抢占,而单单是设置了调解标记,
    在最后触发schedule时抢占进度B并不一定是到头来被调整的历程(为啥?由于大家推断是不是能抢占
    的依靠是抢占进程B比试行进度A的vruntime小,但红黑树中只怕有比抢占进度B的vruntime越来越小的过程C,
    那样在调节时就能入选vruntime最小的C,而不是抢占进度B)。可是大家本来愿意优先调节B,
    鉴于大家正是为了实行B才设置了调治标记,所以这里用三个next指针指向B,以便给他个后门走,
    借使B实在不争气,vruntime太大。就依旧继续推行被私吞进度A比較合理,由此last指向被侵占进度。
    那是一个比next小一些的后门,假诺next近便的小路战败,就让被并吞进度A也走一遍后门,
    假使被私吞进度A也不争气。vruntime也太大,仅仅好从红黑树中挑二个vruntime最小的了。
    任凭它们活动是不是中标,一旦选出下三个进度,就应声清空那七个指针,不能够老开着那些后门吧。
    需求小心的是,schedule中清空那八个指针仅仅在2.6.29及随后的基业才有。以前的基石未有那句话。

    从头撸代码,当tick中断发生时,宗旨调解器调用cfs的task_tick函数task_tick_fair:

    该函数被石英钟中断管理程序调用,将日前cpu的负载情形记载到运维队列struct rq的一些成员中,并更新当前历程的时间片。第3行获得当前的cpu号,第4行取妥当前cpu的妥当队列(每一个cpu有三个)rq,第5行从就绪队列中获取当前运作进程的描述符,第10行更新就绪队列中的clock和clock_task成员值,代表当前的岁月,一般大家会用到clock_task。第11行进入当前进度的调解类的task_tick函数中,更新当前经过的年华片,不一致调整类的该函数完结差异,待会大家剖判下完全公平级调动度类的该函数。第12行更新就绪队列的cpu负载音信。第18行决断当前cpu是还是不是是空闲的,是的话idle_cpu重返1,不然重临0。第19行挂起SCHED_SOFTILacrosseQ软中断函数,去做周期性的负载平衡操作。第21行将新型的机械钟滴答数jiffies存入就绪队列的last_sched_tick域中。再来看下task_tick_fair函数(kernel/sched/fair.c):

    接下来调用wakeup_preempt_entity检測是或不是满意抢占条件。就算知足(再次来到值为1)
    则对近年来经过设置TIF_NEED_RESCHED标识。在剥离系统调用时会触发schedule函数实行进度切换,
    本条函数后边再说。

    static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
    {
        struct cfs_rq *cfs_rq;
        struct sched_entity *se = &curr->se;
    
        for_each_sched_entity(se) {     //组调度
            cfs_rq = cfs_rq_of(se);
            entity_tick(cfs_rq, se, queued);    //在entity_tick中更新cfs队列,当前调度实体时间相关的统计,并判断是否需要调度
        }
        ....
    }
    
    static void entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
    {
        ...
        update_curr(cfs_rq);    // 更新调度实体,cfs队列统计信息
        if (cfs_rq->nr_running > 1)
            check_preempt_tick(cfs_rq, curr);   // 判断是否需要调度
        ...
    }
    
     1 static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
     2 {
     3     struct cfs_rq *cfs_rq;
     4     struct sched_entity *se = &curr->se;
     5 
     6     for_each_sched_entity(se) {
     7         cfs_rq = cfs_rq_of(se);
     8         entity_tick(cfs_rq, se, queued);
     9     }
    10 
    11     if (numabalancing_enabled)
    12         task_tick_numa(rq, curr);
    13 
    14     update_rq_runnable_avg(rq, 1);
    15 }
    

    大家看看wakeup_preempt_entity(se, pse)。到底怎么估计后者是或不是能够抢占前者

    entity_tick函数首要的操作就是两局地,首先调用update_curr()去更新sched_entity cfs的runtimevruntime等信息.
    继而再调用check_preempt_tick()函数检查该cfs是不是须求再行调节,看下那多个函数.

    万一某些进程的调整类应用完全公平调治类的话,那么上个函数scheduler_tick第11行所试行的task_tick函数指针,就针对了本函数。能够回头看看完全公平级调动度对象的起始化,第24行的赋值语句.task_tick

    task_tick_fair。回到本函数,第4行取妥善前历程的通常调整实体,将指针存放到se中,第6-8行遍历当前调治实体的上顶尖实体,以及上上一级实体,由此及彼,然后在entity_tick函数中立异调整实体的运行时刻等音讯。在此地用循环来遍历的始末是当张开了组调解后,调整实体的parent域就存款和储蓄了它的上拔尖节点,该实体和它parent指向的实业不是一样等级,因而利用循环就把从脚下等第(组)到最顶层的品级遍历完了;如若未选用组帮忙,则循环只举办一次,仅对日前调治实体进行翻新。上边看下entity_tick的代码(kernel/sched/fair.c):

     1 static void
     2 entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
     3 {
     4     /*
     5      * Update run-time statistics of the 'current'.
     6      */
     7     update_curr(cfs_rq);
     8 
     9     /*
    10      * Ensure that runnable average is periodically updated.
    11      */
    12     update_entity_load_avg(curr, 1);
    13     update_cfs_rq_blocked_load(cfs_rq, 1);
    14     update_cfs_shares(cfs_rq);
    15 
    16 #ifdef CONFIG_SCHED_HRTICK
    17     /*
    18      * queued ticks are scheduled to match the slice, so don't bother
    19      * validating it and just reschedule.
    20      */
    21     if (queued) {
    22         resched_task(rq_of(cfs_rq)->curr);
    23         return;
    24     }
    25     /*
    26      * don't let the period tick interfere with the hrtick preemption
    27      */
    28     if (!sched_feat(DOUBLE_TICK) &&
    29             hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
    30         return;
    31 #endif
    32 
    33     if (cfs_rq->nr_running > 1)
    34         check_preempt_tick(cfs_rq, curr);
    35 }
    

    在该函数中对调解实体(进度)的运营时刻等消息进行翻新。第7行update_curr函数对现阶段经过的运作时刻进行更新,随后分析。 第21行倘使传进来的参数queued不为0的话,当前进度会被白白设置双重调治标记(允许被并吞了)。第33-34行如若当前cfs_rq队列等待调节的经过数量高出1,那么就实践check_preempt_tick函数检查当前进度的时间片是或不是用完,用完的话就须要调治其他进度来运维(具体来讲,假如当前历程“真实时间片”用完,该函数给当下进程设置need_resched标记,然后schedule程序就足以再一次在就绪队列中调治新的历程),下边深入分析update_curr函数(kernel/sched/fair.c):

     1 static void update_curr(struct cfs_rq *cfs_rq)
     2 {
     3     struct sched_entity *curr = cfs_rq->curr;
     4     u64 now = rq_clock_task(rq_of(cfs_rq));
     5     u64 delta_exec;
     6 
     7     if (unlikely(!curr))
     8         return;
     9 
    10     delta_exec = now - curr->exec_start;
    11     if (unlikely((s64)delta_exec <= 0))
    12         return;
    13 
    14     curr->exec_start = now;
    15 
    16     schedstat_set(curr->statistics.exec_max,
    17               max(delta_exec, curr->statistics.exec_max));
    18 
    19     curr->sum_exec_runtime  = delta_exec;
    20     schedstat_add(cfs_rq, exec_clock, delta_exec);
    21 
    22     curr->vruntime  = calc_delta_fair(delta_exec, curr);
    23     update_min_vruntime(cfs_rq);
    24 
    25     if (entity_is_task(curr)) {
    26         struct task_struct *curtask = task_of(curr);
    27 
    28         trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
    29         cpuacct_charge(curtask, delta_exec);
    30         account_group_exec_runtime(curtask, delta_exec);
    31     }
    32 
    33     account_cfs_rq_runtime(cfs_rq, delta_exec);
    34 } 
    

    该函数是翻新进度运转时刻最核心的八个函数。第3行获得当前的调节实体,第4行从就绪队列rq的clock_task成员中获取当前光阴,存入now中,该成员大家在scheduler_tick函数中涉及过。第10行用当下时刻减去进度在上次石英钟中断tick中的早先时间获得进程运行的岁月间隔,存入delta_exec中。第14行业前时间又改成进度新的启幕时间。第19行将经过运转的日子间隔delta_exec累加到调解实体的sum_exec_runtime成员中,该成员代表经过到如今截至运营了多久。第20行将经过运维的岁月间隔delta_exec也增进到公平级调动度就绪队列cfs_rq的exec_clock成员中。第22行calc_delta_fair函数很重大,它将经过试行的真人真事运维时刻转换到虚拟运营时刻,然后加上到调治实体的vruntime域中,进度的虚构时间极其重要,完全公平级调动度计谋就是借助该时间打开调治。关于进度的忠实时间和编造时间的概念,以及它们之间的更改算法,小说的末端会涉及,详细的源委咱们能够看看《浓厚领会linux内核架构》的进度管理章节。第23行更新cfs_rq队列中的最小虚拟运维时刻min_vruntime,该时间是就绪队列中全部进度包罗如今经过的已运维的相当的小虚拟时间,只可以单调递增,待会我们分析update_min_vruntime函数,该函数相比较关键。第25-30行,若是调治单位是进度的话(不是组),会更新进程描述符中的运作时刻。第33行更新cfs_rq队列的剩下运营时刻,并总结出希望运维时刻,须要的话能够对经过重新调治。下边我们先剖析update_min_vruntime函数,然后深入分析calc_delta_fair函数(kernel/sched/fair.c):

     1 static void update_min_vruntime(struct cfs_rq *cfs_rq)
     2 {
     3     u64 vruntime = cfs_rq->min_vruntime;
     4 
     5     if (cfs_rq->curr)
     6         vruntime = cfs_rq->curr->vruntime;
     7 
     8     if (cfs_rq->rb_leftmost) {
     9         struct sched_entity *se = rb_entry(cfs_rq->rb_leftmost,
    10                            struct sched_entity,
    11                            run_node);
    12 
    13         if (!cfs_rq->curr)
    14             vruntime = se->vruntime;
    15         else
    16             vruntime = min_vruntime(vruntime, se->vruntime);
    17     }
    18 
    19     /* ensure we never gain time by being placed backwards. */
    20     cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);
    21 #ifndef CONFIG_64BIT
    22     smp_wmb();
    23     cfs_rq->min_vruntime_copy = cfs_rq->min_vruntime;
    24 #endif
    25 } 
    

    每个cfs_rq队列均有三个min_vruntime成员,装的是就绪队列中全数进度包含最近进程已运维的虚拟时间中型小型小的的百般时间。本函数来更新那一个小时。第5-6行要是当前有经过正在实践,将最近进度已运行的虚拟时间保存在vruntime变量中。第8-17行若是就绪队列中有下二个要被调节的进程(由rb_leftmost指针指向),则跻身if体,第13-16行从近期进度和下个被调治进程中,选择最小的已运营虚拟时间,保存到vruntime中。第20行从当前队列的min_vruntime域和vruntime变量中,选最大的保留到行列的min_vruntime域中,实现更新。其实第13-17行是最根本的,保障了队列的min_vruntime域中存放的是就绪队列中幽微的虚拟运转时刻,而第20行的功效只是是确认保障min_vruntime域中的值单调递增,未有别的含义了。队列中的min_vruntime成员非常重要,用于在睡觉进程被升迁后以及新进度被创立好时,实行虚构时间补偿或许惩罚,后边会深入分析到。

    1 static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
    2 {
    3     if (unlikely(se->load.weight != NICE_0_LOAD))
    4         delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
    5 
    6     return delta;
    7 } 
    

    第3行判定当前进度nice值是或不是为0,要是是的话,直接回到真实运营时刻delta(nice0级其余进度实际运转时刻和编造运营时刻值非凡);借使不是的话,第4行将忠实时间转变到虚拟时间。下边大家深入分析__calc_delta函数(kernel/sched/fair.c):

     1 static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
     2 {
     3     u64 fact = scale_load_down(weight);
     4     int shift = WMULT_SHIFT;
     5 
     6     __update_inv_weight(lw);
     7 
     8     if (unlikely(fact >> 32)) {
     9         while (fact >> 32) {
    10             fact >>= 1;
    11             shift--;
    12         }
    13     }
    14 
    15     /* hint to use a 32x32->64 mul */
    16     fact = (u64)(u32)fact * lw->inv_weight;
    17 
    18     while (fact >> 32) {
    19         fact >>= 1;
    20         shift--;
    21     }
    22 
    23     return mul_u64_u32_shr(delta_exec, fact, shift);
    24 }
    

    该函数推行了二种算法:要么是delta_exec * weight / lw.weight,要么是(delta_exec * (weight * lw->inv_weight)) >> WMULT_SHIFT。总括的结果正是改造后的杜撰时间。至此,scheduler_tick函数大概就深入分析完了,当然还会有部分细节尚未分析到,进度调整那块极其混乱,要想把持有函数深入分析完特别耗时和活力,现在假使条分缕析到的话,再逐月补充。scheduler_tick函数主要更新进程的运作时刻,包括物理时间和虚拟时间。

    2.历程优先级设置的函数,进度的优先级和调整关系密切,通过上边解析能够看到,总计进程的虚构运维时刻要用到优先级,优先级决定进程权重,权重决定进度虚拟时间的充实速度,最终决定进度可运营时刻的长度。权重越大的历程能够实施的大运越长。从effective_prio函数初始(kernel/sched/core.c):

     1 static int effective_prio(struct task_struct *p)
     2 {
     3     p->normal_prio = normal_prio(p);
     4     /*
     5      * If we are RT tasks or we were boosted to RT priority,
     6      * keep the priority unchanged. Otherwise, update priority
     7      * to the normal priority:
     8      */
     9     if (!rt_prio(p->prio))
    10         return p->normal_prio;
    11     return p->prio;
    12 }
    

    该函数在进度创建时要么在用户的nice系统调用中都会被调用到,来设置进度的活动优先级(进度的三种优先级:活动优先级prio,静态优先级static_prio,普通优先级normal_prio),该函数设计的有早晚技能性,函数的重回值是用来安装进程的移动优先级,可是在函数体中也把经过的常备优先级设置了。第3行设置进度的平凡优先级,随后深入分析normal_prio函数。第9-11行,通过进度的活动优先级能够判明出该进程是否实时进度,如若是实时进度,则实践11行,重返p->prio,实时进度的移动优先级不改变。不然再次回到p->normal_prio,这申明普通进度的移位优先级等于它的一般性优先级。上面大家看看normal_prio函数(kernel/sched/core.c):

     1 static inline int normal_prio(struct task_struct *p)
     2 {
     3     int prio;
     4 
     5     if (task_has_dl_policy(p))
     6         prio = MAX_DL_PRIO-1;
     7     else if (task_has_rt_policy(p))
     8         prio = MAX_RT_PRIO-1 - p->rt_priority;
     9     else
    10         prio = __normal_prio(p);
    11     return prio;
    12 }
    

    该函数用来设置进程的常常优先级。第5行判别当前进度是还是不是悠闲进程,是的话设置进程的日常优先级为-1(-1是悠闲进度的优先级),不然的话第7行判定进度是否实时过程,是的话设置实时进程的平凡优先级为0-99(数字越小优先级越高),能够看出这块减去了p->rt_priority,相比离奇,这是因为实时进度描述符的rt_priority成员中优先存放了它自身的优先级(数字也是0-99,但在那边数字越大,优先级越高),由此往p->prio中倒换的时候,要求管理一下,MAX_RT_PRIO值为100,因此MAX_RT_PRAV4IO-1-(0,99)就倒换到了(99,0),这无非是个小技术。若是当前历程也不是实时进度(表达是经常进度喽),则第10行将经过的静态优先级存入prio中,然后回到(也正是说普通进度的平时优先级等于其静态优先级)。

    小结下,总共有三种进度:空闲进度,实时进程,普通进度;各个进度都带有二种优先级:活动优先级,普通优先级,静态优先级。空闲进度的平日优先级恒久-1,实时进程的平常优先级是遵照p->rt_priority设定的(0-99),普通进程的常备优先级是基于其静态优先级设定的(100-139)。

    3.进度权重设置的函数,下边大家关系,进度的先行级决定进度的权重。权重进而决定进度运维时刻的长短。大家先深入分析和权重相关的数据结构。

    struct load_weight(include/linux/sched.h)

    1 struct load_weight {
    2     unsigned long weight;
    3     u32 inv_weight;
    4 };
    

    各种进度描述符,调治实体,就绪对列中都涵盖一个该项目变量,用来保存各自的权重。成员weight中存放进程优先级所对应的权重。成员inv_weight中存放NICE_0_LOAD/weight的结果,这些结果乘以进度运行的光阴世隔delta_exec正是进程运维的杜撰时间。因此引进权重便是为了总计进程的虚构时间。在此处将中等的乘除结果保存下来,前边就不用再总计了,直接能够用。

    数组prio_to_weight[40](kernel/sched/sched.h)

     1 static const int prio_to_weight[40] = {
     2  /* -20 */     88761,     71755,     56483,     46273,     36291,
     3  /* -15 */     29154,     23254,     18705,     14949,     11916,
     4  /* -10 */      9548,      7620,      6100,      4904,      3906,
     5  /*  -5 */      3121,      2501,      1991,      1586,      1277,
     6  /*   0 */      1024,       820,       655,       526,       423,
     7  /*   5 */       335,       272,       215,       172,       137,
     8  /*  10 */       110,        87,        70,        56,        45,
     9  /*  15 */        36,        29,        23,        18,        15,
    10 };
    

    该数组是一般进程的优先级和权重对应涉及。数组成分是权重值,分别是先行级从100-139可能nice值从-20- 19所对应的权重值。nice值(-20- 19)是从用户空间看到的平常进程的优先级,和基本空间的优先级(100-139)一一对应。struct load_weight中的weight成员存纠就是那一个权重值。

    高级中学档结果数组prio_to_wmult[40] (kernel/sched/sched.h)

     1 static const u32 prio_to_wmult[40] = {
     2  /* -20 */     48388,     59856,     76040,     92818,    118348,
     3  /* -15 */    147320,    184698,    229616,    287308,    360437,
     4  /* -10 */    449829,    563644,    704093,    875809,   1099582,
     5  /*  -5 */   1376151,   1717300,   2157191,   2708050,   3363326,
     6  /*   0 */   4194304,   5237765,   6557202,   8165337,  10153587,
     7  /*   5 */  12820798,  15790321,  19976592,  24970740,  31350126,
     8  /*  10 */  39045157,  49367440,  61356676,  76695844,  95443717,
     9  /*  15 */ 119304647, 148102320, 186737708, 238609294, 286331153,
    10 };
    

    该数组成分正是上个数组成分被NICE_0_LOAD除的结果,一一对应。struct load_weight中的inv_weight成员存端即是那么些值。

    下边大家分析和权重设置相关的函数。从set_load_weight函数开端(kernel/sched/core.c):

     1 static void set_load_weight(struct task_struct *p)
     2 {
     3     int prio = p->static_prio - MAX_RT_PRIO;
     4     struct load_weight *load = &p->se.load;
     5 
     6     /*
     7      * SCHED_IDLE tasks get minimal weight:
     8      */
     9     if (p->policy == SCHED_IDLE) {
    10         load->weight = scale_load(WEIGHT_IDLEPRIO);
    11         load->inv_weight = WMULT_IDLEPRIO;
    12         return;
    13     }
    14 
    15     load->weight = scale_load(prio_to_weight[prio]);
    16     load->inv_weight = prio_to_wmult[prio];
    17 } 
    

    该函数用来安装进程p的权重。第3行将进度p的静态优先级转变来数组下标(减去100,从100-139--->0-39)。第4行取稳妥前经过的调节实体中的权重结构体指针,存入load中。第9-12行,要是当前进度是悠闲进度,则设置相应的权重和中间总结结果。第15-16行,设置实时进度和一般性进度的权重和中等总括结果。

    是因为就绪队列中也蕴藏权重结构体,所以也要对它们进行安装。使用以下函数(kernel/sched/fair.c):

    1 static inline void update_load_set(struct load_weight *lw, unsigned long w)
    2 {
    3     lw->weight = w;
    4     lw->inv_weight = 0;
    5 }
    

    该函数用来设置就绪队列的权重。

    1 static inline void update_load_add(struct load_weight *lw, unsigned long inc)
    2 {
    3     lw->weight  = inc;
    4     lw->inv_weight = 0;
    5 }
    

    当有进程进入就绪队列,使用该函数扩大就绪队列的权重。

    1 static inline void update_load_sub(struct load_weight *lw, unsigned long dec)
    2 {
    3     lw->weight -= dec;
    4     lw->inv_weight = 0;
    5 }
    

    当有进度从就绪队列移除时,使用该函数减弱就绪队列的权重。就绪队列的load_weight.inv_weight成员总是0,因为不会动用到就绪队列的该域。

    4.测算进程延迟周期的连锁函数。进度的推迟周恒生期货指数的是将装有进度实施一次的小时。当就绪队列中的进度数量不超越规定的时候,内核有一个稳住的推移周期供调节使用,不过当进度数量跨越规定今后,就须要对该固定延迟周期举办扩大(不然随着进度越多,种种进度分配的施行时间会越少)。下边看看sched_slice函数(kernel/sched/fair.c):

     1 static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
     2 {
     3     u64 slice = __sched_period(cfs_rq->nr_running   !se->on_rq);
     4 
     5     for_each_sched_entity(se) {
     6         struct load_weight *load;
     7         struct load_weight lw;
     8 
     9         cfs_rq = cfs_rq_of(se);
    10         load = &cfs_rq->load;
    11 
    12         if (unlikely(!se->on_rq)) {
    13             lw = cfs_rq->load;
    14 
    15             update_load_add(&lw, se->load.weight);
    16             load = &lw;
    17         }
    18         slice = __calc_delta(slice, se->load.weight, load);
    19     }
    20     return slice;
    21 }
    

    一贯看第18行,__calc_delta用来计量当前经过在延迟周期中可占的时刻(也就是“时间片”,正是前段时间历程可用的时日)。__calc_delta函数很强大,记得前边在entity_tick函数中也调用过该函数(entity_tick--->update_curr--->calc_delta_fair--->__calc_delta),当时采用该函数是为了将经过运营过的物理时间(真实时间)调换到虚拟时间;而在那边,我们用它来计算当前历程在延迟周期中可占的光阴(也等于“时间片”)。前边提过,__calc_delta中用到param1 * param2 / param3.weight那一个公式(param代表该函数接收的参数),当param2的值固定不改变(等于NICE_0_LOAD),param3(代表当前经过的权重)在退换时,该函数是用来调换真实时间和编造时间的;当param3(代表当前cfs_rq的权重,cfs_rq->load->weight)的值固定不变,param2在扭转(代表当前经过的权重)时,该函数则是用来测算当前历程应该运营的时刻。由此第18行总计结果slice便是时下进程应该运转的诚实时间。上边再看贰个函数sched_vslice(kernel/sched/fair.c):

    1 static u64 sched_vslice(struct cfs_rq *cfs_rq, struct sched_entity *se)
    2 {
    3     return calc_delta_fair(sched_slice(cfs_rq, se), se);
    4 }
    

    该函数用来将经过应该运营的安分守己时间转换到应该运营的虚拟时间,以供调整使用。能够看看该函数调用了cals_delta_fair来拓展时间转移,后面已分析过,不再赘言。

    5.增选下八个亟需调解的经过。所运用的函数是pick_next_task_fair,代码如下(kernel/sched/fair.c):

    新葡亰496net 18新葡亰496net 19

      1 static struct task_struct *
      2 pick_next_task_fair(struct rq *rq, struct task_struct *prev)
      3 {
      4     struct cfs_rq *cfs_rq = &rq->cfs;
      5     struct sched_entity *se;
      6     struct task_struct *p;
      7     int new_tasks;
      8 
      9 again:
     10 #ifdef CONFIG_FAIR_GROUP_SCHED
     11     if (!cfs_rq->nr_running)
     12         goto idle;
     13 
     14     if (prev->sched_class != &fair_sched_class)
     15         goto simple;
     16 
     17     /*
     18      * Because of the set_next_buddy() in dequeue_task_fair() it is rather
     19      * likely that a next task is from the same cgroup as the current.
     20      *
     21      * Therefore attempt to avoid putting and setting the entire cgroup
     22      * hierarchy, only change the part that actually changes.
     23      */
     24 
     25     do {
     26         struct sched_entity *curr = cfs_rq->curr;
     27 
     28         /*
     29          * Since we got here without doing put_prev_entity() we also
     30          * have to consider cfs_rq->curr. If it is still a runnable
     31          * entity, update_curr() will update its vruntime, otherwise
     32          * forget we've ever seen it.
     33          */
     34         if (curr && curr->on_rq)
     35             update_curr(cfs_rq);
     36         else
     37             curr = NULL;
     38 
     39         /*
     40          * This call to check_cfs_rq_runtime() will do the throttle and
     41          * dequeue its entity in the parent(s). Therefore the 'simple'
     42          * nr_running test will indeed be correct.
     43          */
     44         if (unlikely(check_cfs_rq_runtime(cfs_rq)))
     45             goto simple;
     46 
     47         se = pick_next_entity(cfs_rq, curr);
     48         cfs_rq = group_cfs_rq(se);
     49     } while (cfs_rq);
     50 
     51     p = task_of(se);
     52 
     53     /*
     54      * Since we haven't yet done put_prev_entity and if the selected task
     55      * is a different task than we started out with, try and touch the
     56      * least amount of cfs_rqs.
     57      */
     58     if (prev != p) {
     59         struct sched_entity *pse = &prev->se;
     60 
     61         while (!(cfs_rq = is_same_group(se, pse))) {
     62             int se_depth = se->depth;
     63             int pse_depth = pse->depth;
     64 
     65             if (se_depth <= pse_depth) {
     66                 put_prev_entity(cfs_rq_of(pse), pse);
     67                 pse = parent_entity(pse);
     68             }
     69             if (se_depth >= pse_depth) {
     70                 set_next_entity(cfs_rq_of(se), se);
     71                 se = parent_entity(se);
     72             }
     73         }
     74 
     75         put_prev_entity(cfs_rq, pse);
     76         set_next_entity(cfs_rq, se);
     77     }
     78 
     79     if (hrtick_enabled(rq))
     80         hrtick_start_fair(rq, p);
     81 
     82     return p;
     83 simple:
     84     cfs_rq = &rq->cfs;
     85 #endif
     86 
     87     if (!cfs_rq->nr_running)
     88         goto idle;
     89 
     90     put_prev_task(rq, prev);
     91 
     92     do {
     93         se = pick_next_entity(cfs_rq, NULL);
     94         set_next_entity(cfs_rq, se);
     95         cfs_rq = group_cfs_rq(se);
     96     } while (cfs_rq);
     97 
     98     p = task_of(se);
     99 
    100     if (hrtick_enabled(rq))
    101         hrtick_start_fair(rq, p);
    102 
    103     return p;
    104 
    105 idle:
    106     new_tasks = idle_balance(rq);
    107     /*
    108      * Because idle_balance() releases (and re-acquires) rq->lock, it is
    109      * possible for any higher priority task to appear. In that case we
    110      * must re-start the pick_next_entity() loop.
    111      */
    112     if (new_tasks < 0)
    113         return RETRY_TASK;
    114 
    115     if (new_tasks > 0)
    116         goto again;
    117 
    118     return NULL;
    119 }
    

    pick_next_task_fair

    该函数会被赋给公平级调动度类的pick_next_task成员(.pick_next_task = pick_next_task_fair),在schedule函数中会调用该函数选拔下一个亟需调用的进程,然后开始展览过程切换。第11-12行倘使当前稳当队列中的进度数量为0,则脱离函数。第25-49行对持有的调节组实行遍历,从中挑选下一个可调解的历程,而不只局限在现阶段队列的日前组。第26行取安妥前调节实体,第34-37行要是存在一个脚下调治实体(进度)并且正在运作,则更新进度的运作时刻,不然就绪队列cfs_rq.curr置为null,表示如今无进度运维。第47行获取下贰个调节实体,待会来深入分析该函数。第48行获取下个调治实体所具有的就绪队列my_q(代表二个调节组),即使调治组非空,则进入下二次巡回,在新的服服帖帖队列(调解组)中选用下二个可调节进程,直到有个别实体没有协和的就绪队列结束(遍历完全部的调整组了)。退出循环后,能够开掘此时的se是所遍历的尾声贰个调解组的下个可运维实体。第51行获得se对应的历程描述符,第58-77行,若是当前进度和下多少个经过(se所对应的进程)不是同二个以来,则试行if体,第59行将日前历程的调节实体指针装入pse中,第61-72行如若当前进程和下贰个调解的历程(se对应的历程)不属于同一调整组,则进入循环。否则,执行第75-76行,将方今历程放回就绪队列,将下个进度从安妥队列中拿出,那八个函数涉及到了就绪队列的出队和入队操作,我们在上面深入分析。第61-73的大循环依据当前实体和下个调节实体的节点深度拓展调治(同三个级其余进度本事切换)。上边看看pick_next_entity,put_prev_entity和set_prev_entity函数代码(kernel/sched/fair.c):

     1 static struct sched_entity *
     2 pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
     3 {
     4     struct sched_entity *left = __pick_first_entity(cfs_rq);
     5     struct sched_entity *se;
     6 
     7     /*
     8      * If curr is set we have to see if its left of the leftmost entity
     9      * still in the tree, provided there was anything in the tree at all.
    10      */
    11     if (!left || (curr && entity_before(curr, left)))
    12         left = curr;
    13 
    14     se = left; /* ideally we run the leftmost entity */
    15 
    16     /*
    17      * Avoid running the skip buddy, if running something else can
    18      * be done without getting too unfair.
    19      */
    20     if (cfs_rq->skip == se) {
    21         struct sched_entity *second;
    22 
    23         if (se == curr) {
    24             second = __pick_first_entity(cfs_rq);
    25         } else {
    26             second = __pick_next_entity(se);
    27             if (!second || (curr && entity_before(curr, second)))
    28                 second = curr;
    29         }
    30 
    31         if (second && wakeup_preempt_entity(second, left) < 1)
    32             se = second;
    33     }
    34 
    35     /*
    36      * Prefer last buddy, try to return the CPU to a preempted task.
    37      */
    38     if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
    39         se = cfs_rq->last;
    40 
    41     /*
    42      * Someone really wants this to run. If it's not unfair, run it.
    43      */
    44     if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)
    45         se = cfs_rq->next;
    46 
    47     clear_buddies(cfs_rq, se);
    48 
    49     return se;
    50 }
    

    该函数采用下一个调整的实业。第4行将红黑树的最左侧实体赋给left,第11-12行假设红黑树的最左侧实体为空或许当前实体运转的虚拟时间低于下一个实体(继续当前的实业),把当下实体赋给left,实际上left指向的是下一个要进行的长河(该进程要么照旧脚下进程,要么是下三个经过),第14行将left赋给se,第20-33行借使se进度是亟需跳过的经过(不可能被调解),施行if体,假诺se进度是眼下进度,则选取红黑数最左的长河赋给second,不然se进度已经是最左的进程,就把next指向的进程赋给second(next指向的是刚刚被提醒的经过),第32行将second再度赋给se,se是选抽取去的下个进度。第38-45,再一次判定,要么把last指向的进程赋给se,要么把next指向经过赋给se,内核更赞成于把last赋给se,因为last是提醒next进度的经过(一般就是当前经过),所以举行last就代表不用切换进度,成效最高。第47行清理掉next和last域。第31,38,44行使用到的wakeup_preempt_entity函数大家在进程唤醒那块再剖判。

     1 static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
     2 {
     3     /*
     4      * If still on the runqueue then deactivate_task()
     5      * was not called and update_curr() has to be done:
     6      */
     7     if (prev->on_rq)
     8         update_curr(cfs_rq);
     9 
    10     /* throttle cfs_rqs exceeding runtime */
    11     check_cfs_rq_runtime(cfs_rq);
    12 
    13     check_spread(cfs_rq, prev);
    14     if (prev->on_rq) {
    15         update_stats_wait_start(cfs_rq, prev);
    16         /* Put 'current' back into the tree. */
    17         __enqueue_entity(cfs_rq, prev);
    18         /* in !on_rq case, update occurred at dequeue */
    19         update_entity_load_avg(prev, 1);
    20     }
    21     cfs_rq->curr = NULL;
    22 } 
    

    该函数将方今调解实体放回就绪队列。第7-8行如若当前实体正在周转,更新其时间片。第17行将近年来实体参与到妥当队列中,待会分析__enqueue_entity函数。第21行将就绪队列的curr域置为null,因为近日经过早已放回就绪队列了,就代表近日向来不经过正在实行了,因而curr为空。

     1 static void
     2 set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
     3 {
     4     /* 'current' is not kept within the tree. */
     5     if (se->on_rq) {
     6         /*
     7          * Any task has to be enqueued before it get to execute on
     8          * a CPU. So account for the time it spent waiting on the
     9          * runqueue.
    10          */
    11         update_stats_wait_end(cfs_rq, se);
    12         __dequeue_entity(cfs_rq, se);
    13     }
    14 
    15     update_stats_curr_start(cfs_rq, se);
    16     cfs_rq->curr = se;
    17 #ifdef CONFIG_SCHEDSTATS
    18     /*
    19      * Track our maximum slice length, if the CPU's load is at
    20      * least twice that of our own weight (i.e. dont track it
    21      * when there are only lesser-weight tasks around):
    22      */
    23     if (rq_of(cfs_rq)->load.weight >= 2*se->load.weight) {
    24         se->statistics.slice_max = max(se->statistics.slice_max,
    25             se->sum_exec_runtime - se->prev_sum_exec_runtime);
    26     }
    27 #endif
    28     se->prev_sum_exec_runtime = se->sum_exec_runtime;
    29 }
    

    该函数将下多个被调解实体从安妥队列中抽出。第12行完毕抽取操作,待会解析该函数。第16行将收取的调治实体指针赋给就绪队列的curr,那么此时就有了正在运营的历程了。前边的代码更新当前过程的时间总结消息。

    6.就绪队列的入队和出队操作(kernel/sched/fair.c)。

     1 static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
     2 {
     3     struct rb_node **link = &cfs_rq->tasks_timeline.rb_node;
     4     struct rb_node *parent = NULL;
     5     struct sched_entity *entry;
     6     int leftmost = 1;
     7 
     8     /*
     9      * Find the right place in the rbtree:
    10      */
    11     while (*link) {
    12         parent = *link;
    13         entry = rb_entry(parent, struct sched_entity, run_node);
    14         /*
    15          * We dont care about collisions. Nodes with
    16          * the same key stay together.
    17          */
    18         if (entity_before(se, entry)) {
    19             link = &parent->rb_left;
    20         } else {
    21             link = &parent->rb_right;
    22             leftmost = 0;
    23         }
    24     }
    25 
    26     /*
    27      * Maintain a cache of leftmost tree entries (it is frequently
    28      * used):
    29      */
    30     if (leftmost)
    31         cfs_rq->rb_leftmost = &se->run_node;
    32 
    33     rb_link_node(&se->run_node, parent, link);
    34     rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
    35 }
    

    该函数完结入队操作。第3行获得就绪队列中红黑树的根节点,将树根指针保存在link中。第12行parent暂且指向树根。第13行获得树根节点的调治实体,保存在entry中。第18-22行,相比要入队的实体中的已运行虚拟时间和树根实体中的该消息,假设前者小的话,就要插入到树的左子树上(link指向树根的左孩子,再度进入循环,类似于递归),不然将在插入到树的右子树上(同上)。那块就将经过的调节计谋显示的不亦乐乎:依照进度已运维的杜撰时间来调整进程的调解,红黑树的左子树比右子树要先被调解,已运营的虚拟时间越小的长河越在树的左臂。第30-31行,要是入队的实业最后被插在左孩子上(该入队实体仍是就绪队列上最靠前的实业,下一次就能够被调用),那么就要让就绪队列的rb_leftmost指向入队实体。rb_leftmost指针始终对准下一次要被调治的实体(进度)。最终两行要给红黑数重新上色。

     1 static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
     2 {
     3     if (cfs_rq->rb_leftmost == &se->run_node) {
     4         struct rb_node *next_node;
     5 
     6         next_node = rb_next(&se->run_node);
     7         cfs_rq->rb_leftmost = next_node;
     8     }
     9 
    10     rb_erase(&se->run_node, &cfs_rq->tasks_timeline);
    11 }
    

    该函数实现出队操作。第3行判定要出队的实业是或不是红黑树最右侧的男女(rb_leftmost所指向的),借使不是的话,第10行直接删除该出队实体。不然推行if体,第6行找到出队实体的下一个实体(中序遍历的下二个节点,也正是当出队实体删除后,最左侧的男女),赋给next_node。第7行让rb_leftmost指向next_node。在剔除掉要出队实体后,下二个亟待被调节的实体也就安装好了。

    7.睡眠进度被晋升后抢占当前进度。当某些财富空出来后,等待该能源的进度就能被唤醒,唤醒后大概将在抢占当前历程,由此那块的函数也须求深入分析(kernel/sched/core.c)。

     1 static int
     2 try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
     3 {
     4     unsigned long flags;
     5     int cpu, success = 0;
     6 
     7     /*
     8      * If we are going to wake up a thread waiting for CONDITION we
     9      * need to ensure that CONDITION=1 done by the caller can not be
    10      * reordered with p->state check below. This pairs with mb() in
    11      * set_current_state() the waiting thread does.
    12      */
    13     smp_mb__before_spinlock();
    14     raw_spin_lock_irqsave(&p->pi_lock, flags);
    15     if (!(p->state & state))
    16         goto out;
    17 
    18     success = 1; /* we're going to change ->state */
    19     cpu = task_cpu(p);
    20 
    21     if (p->on_rq && ttwu_remote(p, wake_flags))
    22         goto stat;
    23 
    24 #ifdef CONFIG_SMP
    25     /*
    26      * If the owning (remote) cpu is still in the middle of schedule() with
    27      * this task as prev, wait until its done referencing the task.
    28      */
    29     while (p->on_cpu)
    30         cpu_relax();
    31     /*
    32      * Pairs with the smp_wmb() in finish_lock_switch().
    33      */
    34     smp_rmb();
    35 
    36     p->sched_contributes_to_load = !!task_contributes_to_load(p);
    37     p->state = TASK_WAKING;
    38 
    39     if (p->sched_class->task_waking)
    40         p->sched_class->task_waking(p);
    41 
    42     cpu = select_task_rq(p, p->wake_cpu, SD_BALANCE_WAKE, wake_flags);
    43     if (task_cpu(p) != cpu) {
    44         wake_flags |= WF_MIGRATED;
    45         set_task_cpu(p, cpu);
    46     }
    47 #endif /* CONFIG_SMP */
    48 
    49     ttwu_queue(p, cpu);
    50 stat:
    51     ttwu_stat(p, cpu, wake_flags);
    52 out:
    53     raw_spin_unlock_irqrestore(&p->pi_lock, flags);
    54 
    55     return success;
    56 }
    

    该函数会唤醒参数p内定的经过,将经过进入就绪队列中等待调解。第15行剖断p进度的状态码和传进来的状态码是不是一致,差别的话函数甘休(不等同则声明经过等待的规格未满意)。第19行收获要晋升进度p原先所在的cpu号。第37行设置要晋升进度p的情事为TASK_WAKING。第40行推行进程p的调整类中的task_waking函数,该函数指针指向了task_waking_fair函数,该函数根本是对睡觉进度的已运行虚拟时间开始展览填补,待会分析该函数。第42表现刚唤醒进度p选用贰个适中的就绪队列供其参预,重返就绪队列所在的cpu号。第43行要是经过p所在的原来cpu和为它选拔的cpu不是同贰个以来,第45行更加精雕细琢程p的cpu号。

     1 void wake_up_new_task(struct task_struct *p)
     2 {
     3     unsigned long flags;
     4     struct rq *rq;
     5 
     6     raw_spin_lock_irqsave(&p->pi_lock, flags);
     7 #ifdef CONFIG_SMP
     8     /*
     9      * Fork balancing, do it here and not earlier because:
    10      *  - cpus_allowed can change in the fork path
    11      *  - any previously selected cpu might disappear through hotplug
    12      */
    13     set_task_cpu(p, select_task_rq(p, task_cpu(p), SD_BALANCE_FORK, 0));
    14 #endif
    15 
    16     /* Initialize new task's runnable average */
    17     init_task_runnable_average(p);
    18     rq = __task_rq_lock(p);
    19     activate_task(rq, p, 0);
    20     p->on_rq = 1;
    21     trace_sched_wakeup_new(p, true);
    22     check_preempt_curr(rq, p, WF_FORK);
    23 #ifdef CONFIG_SMP
    24     if (p->sched_class->task_woken)
    25         p->sched_class->task_woken(rq, p);
    26 #endif
    27     task_rq_unlock(rq, p, &flags);
    28 }
    

     该函数用来唤醒刚创设好的长河,而上个函数是用来唤起睡眠中的进程。第13作为将唤起的进程p设置合适的cpu。第17行设置进程p的可运营时刻长短(类似“时间片”),第19行activate_task函数重要将唤起的进度p参预就绪队列,并更新队列的光阴,早先化进度p的光阴等,该函数中的调用关系差非常少为activate_task->enqueue_task->enqueue_task_fair(p->sched_class->enqueue_task)->enqueue_entity。第22行check_preempt_curr函数调用了check_preempt_wakeup函数,来检查唤醒过程是不是能抢占当前历程,上边解析该函数(kernel/sched/fair.c)。

     1 static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
     2 {
     3     struct task_struct *curr = rq->curr;
     4     struct sched_entity *se = &curr->se, *pse = &p->se;
     5     struct cfs_rq *cfs_rq = task_cfs_rq(curr);
     6     int scale = cfs_rq->nr_running >= sched_nr_latency;
     7     int next_buddy_marked = 0;
     8 
     9     if (unlikely(se == pse))
    10         return;
    11 
    12     /*
    13      * This is possible from callers such as move_task(), in which we
    14      * unconditionally check_prempt_curr() after an enqueue (which may have
    15      * lead to a throttle).  This both saves work and prevents false
    16      * next-buddy nomination below.
    17      */
    18     if (unlikely(throttled_hierarchy(cfs_rq_of(pse))))
    19         return;
    20 
    21     if (sched_feat(NEXT_BUDDY) && scale && !(wake_flags & WF_FORK)) {
    22         set_next_buddy(pse);
    23         next_buddy_marked = 1;
    24     }
    25 
    26     /*
    27      * We can come here with TIF_NEED_RESCHED already set from new task
    28      * wake up path.
    29      *
    30      * Note: this also catches the edge-case of curr being in a throttled
    31      * group (e.g. via set_curr_task), since update_curr() (in the
    32      * enqueue of curr) will have resulted in resched being set.  This
    33      * prevents us from potentially nominating it as a false LAST_BUDDY
    34      * below.
    35      */
    36     if (test_tsk_need_resched(curr))
    37         return;
    38 
    39     /* Idle tasks are by definition preempted by non-idle tasks. */
    40     if (unlikely(curr->policy == SCHED_IDLE) &&
    41         likely(p->policy != SCHED_IDLE))
    42         goto preempt;
    43 
    44     /*
    45      *  do not preempt non-idle tasks (their preemption
    46      * is driven by the tick):
    47      */
    48     if (unlikely(p->policy != SCHED_NORMAL) || !sched_feat(WAKEUP_PREEMPTION))
    49         return;
    50 
    51     find_matching_se(&se, &pse);
    52     update_curr(cfs_rq_of(se));
    53     BUG_ON(!pse);
    54     if (wakeup_preempt_entity(se, pse) == 1) {
    55         /*
    56          * Bias pick_next to pick the sched entity that is
    57          * triggering this preemption.
    58          */
    59         if (!next_buddy_marked)
    60             set_next_buddy(pse);
    61         goto preempt;
    62     }
    63 
    64     return;
    65 
    66 preempt:
    67     resched_task(curr);
    68     /*
    69      * Only set the backward buddy when the current task is still
    70      * on the rq. This can happen when a wakeup gets interleaved
    71      * with schedule on the ->pre_schedule() or idle_balance()
    72      * point, either of which can * drop the rq lock.
    73      *
    74      * Also, during early boot the idle thread is in the fair class,
    75      * for obvious reasons its a bad idea to schedule back to it.
    76      */
    77     if (unlikely(!se->on_rq || curr == rq->idle))
    78         return;
    79 
    80     if (sched_feat(LAST_BUDDY) && scale && entity_is_task(se))
    81         set_last_buddy(se);
    82 }
    

    第21-24行,假设张开了NEXT_BUDDY并且唤醒的长河不是新历程(而是睡眠进程),那么第22行就将cfs_rq的next指针指向唤醒的进程,并且安装标识。第36行一旦当前历程可以被私吞,函数直接回到。不然,第40-42行假如当前进度是悠闲进度并且被升迁的历程不是悠闲进度,则跳到preempt处,设置need_resched标识,实现抢占设置。第48行,即便被晋升进程是悠闲进度恐怕批管理进程,直接重返,那几个进度不能够抢占其他进度。第51行借使当前进程和被唤起进度不在同一流别(同多少个调节组),则寻觅它们的先世parent,把它们调度到均等级别,工夫举办虚构运维时刻的相比,进而决定是不是抢占。第54行,对现阶段经过和被唤醒进程的虚构运营时刻举办比较,能够抢占的话(唤醒进度的杜撰时间低于当前进程)试行if体,跳到preempt处形成抢占。不然持有都不满足的话,当前经过无法被占领,推行第64行再次回到,随后解析该函数。第80-81行若是打开了LAST_BUDDY,就将cfs_rq的last指针指向唤醒进度的经过。在pick_next_entity函数中,next和last所指的进程会早日就绪队列的left进度被选取。上边分析下wakeup_preempt_entity函数(kernel/sched/fair.c)。

     1 static int
     2 wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)
     3 {
     4     s64 gran, vdiff = curr->vruntime - se->vruntime;
     5 
     6     if (vdiff <= 0)
     7         return -1;
     8 
     9     gran = wakeup_gran(curr, se);
    10     if (vdiff > gran)
    11         return 1;
    12 
    13     return 0;
    14 }
    

    该函数是要确认保障在se实体在抢占curr实体时,curr实体已经运维过一段时间(具体而言,物理时间1ms),第9行wakeup_gran函数是将sysctl_sched_wakeup_granularity的值(1ms)转换来se实体的虚构时间,保存在gran中,第10行比较vdiff和gran大小,实际上是比较curr->vruntime 和 se->vruntime gran,因而即使想让curr实体多施行gran时间,技艺被私吞。

    谈到底我们再解析下 try_to_wake_up函数中第40行遗留的老大函数指针task_waking,该指针指向了task_waking_fair函数,代码如下(kernel/sched/fair.c):

     1 static void task_waking_fair(struct task_struct *p)
     2 {
     3     struct sched_entity *se = &p->se;
     4     struct cfs_rq *cfs_rq = cfs_rq_of(se);
     5     u64 min_vruntime;
     6 
     7 #ifndef CONFIG_64BIT
     8     u64 min_vruntime_copy;
     9 
    10     do {
    11         min_vruntime_copy = cfs_rq->min_vruntime_copy;
    12         smp_rmb();
    13         min_vruntime = cfs_rq->min_vruntime;
    14     } while (min_vruntime != min_vruntime_copy);
    15 #else
    16     min_vruntime = cfs_rq->min_vruntime;
    17 #endif
    18 
    19     se->vruntime -= min_vruntime;
    20     record_wakee(p);
    21 }
    

    该函数达成对睡觉进度的杜撰时间补偿。思索到睡眠时间长日子尚无运维,因而第19行从唤醒进度se的已运转虚拟时间中减去就绪队列的微乎其微虚拟时间,做点补偿,让其能够多运转一点岁月。

    8.新进度的管理函数(kernel/sched/fair.c):

     1 static void task_fork_fair(struct task_struct *p)
     2 {
     3     struct cfs_rq *cfs_rq;
     4     struct sched_entity *se = &p->se, *curr;
     5     int this_cpu = smp_processor_id();
     6     struct rq *rq = this_rq();
     7     unsigned long flags;
     8 
     9     raw_spin_lock_irqsave(&rq->lock, flags);
    10 
    11     update_rq_clock(rq);
    12 
    13     cfs_rq = task_cfs_rq(current);
    14     curr = cfs_rq->curr;
    15 
    16     /*
    17      * Not only the cpu but also the task_group of the parent might have
    18      * been changed after parent->se.parent,cfs_rq were copied to
    19      * child->se.parent,cfs_rq. So call __set_task_cpu() to make those
    20      * of child point to valid ones.
    21      */
    22     rcu_read_lock();
    23     __set_task_cpu(p, this_cpu);
    24     rcu_read_unlock();
    25 
    26     update_curr(cfs_rq);
    27 
    28     if (curr)
    29         se->vruntime = curr->vruntime;
    30     place_entity(cfs_rq, se, 1);
    31 
    32     if (sysctl_sched_child_runs_first && curr && entity_before(curr, se)) {
    33         /*
    34          * Upon rescheduling, sched_class::put_prev_task() will place
    35          * 'current' within the tree based on its new key value.
    36          */
    37         swap(curr->vruntime, se->vruntime);
    38         resched_task(rq->curr);
    39     }
    40 
    41     se->vruntime -= cfs_rq->min_vruntime;
    42 
    43     raw_spin_unlock_irqrestore(&rq->lock, flags);
    44 }
    

    该函数在do_fork--->copy_process函数中调用,用来设置新创造进度的虚构时间音信。第5行取妥当前的cpu号,第23作为新历程设置该cpu号。第29行将近日经过(父进度)的虚构运转时刻拷贝给新进度(子进程)。第30行的place_entity函数达成新进度的“时间片”总计以及虚拟时间惩罚,之后将新进程进入红黑数中,待会解析。第32行假诺设置了子进度早日父进程运转的标记并且当前经过不为空且当前经过已运行的虚拟时间比新进程小,则推行if体,第37行调换当前进程和新历程的杜撰时间(新进程的虚构时间变小,就排在了红黑树的左手,当前经过此前,后一次就能够被调整),第38行设置双重调整标识。第41行给新进度的虚构运营时刻减去队列的小小虚拟时间来做一些互补(因为在上面包车型地铁place_entity函数中给新进度的虚拟时间加了一回min_vruntime,所以在此间要减去),再来看看place_entity函数(kernel/sched/fair.c):

     1 static void
     2 place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
     3 {
     4     u64 vruntime = cfs_rq->min_vruntime;
     5 
     6     /*
     7      * The 'current' period is already promised to the current tasks,
     8      * however the extra weight of the new task will slow them down a
     9      * little, place the new task so that it fits in the slot that
    10      * stays open at the end.
    11      */
    12     if (initial && sched_feat(START_DEBIT))
    13         vruntime  = sched_vslice(cfs_rq, se);
    14 
    15     /* sleeps up to a single latency don't count. */
    16     if (!initial) {
    17         unsigned long thresh = sysctl_sched_latency;
    18 
    19         /*
    20          * Halve their sleep time's effect, to allow
    21          * for a gentler effect of sleepers:
    22          */
    23         if (sched_feat(GENTLE_FAIR_SLEEPERS))
    24             thresh >>= 1;
    25 
    26         vruntime -= thresh;
    27     }
    28 
    29     /* ensure we never gain time by being placed backwards. */
    30     se->vruntime = max_vruntime(se->vruntime, vruntime);
    31 }
    

    该函数完毕新进度的“时间片”总计和虚拟时间惩罚,并且将新历程进入就绪队列。第4行将就绪队列的min_vruntime值存入vruntime中,第12-13行,就算initial标识为1的话(表达当前测算的是新历程的时间),将计算出的新进度的虚构时间片加上到vruntime中,累加到原因是调节种类要有限帮衬先把就绪队列中的全数的长河试行三遍之后能力实施新进度,一会实际表明。第16-17行,如果当前计算的不是新历程(睡眠的经过),把一个延缓周期的长度sysctl_sched_latency(6ms)赋给thresh,第24行thresh减半,第26行睡眠进程的虚构运行时刻减去减半后的thresh,因为睡眠进程好长时间未运营,由此要拓展虚构时间补偿,把它已运行的虚构时间减小一些,使得它能多运转一会。第30行将设置好的虚拟时间保存到进度调解实体的vruntime域。上面解释下怎么要对新进度展开虚构时间惩罚,其实原因唯有三个,便是调节系统要确定保证将就绪队列中幸存的进度实践壹回之后再举行新进度,那么就务须使新进度的 vruntime=cfs_rq->min_vruntime 新进度的虚构时间片,技巧使得新进度插入到红黑树的左侧,最终参预调整,不然不或许担保具有进程在新历程在此之前推行。

    终极,解析下和调节相关的那些函数实行的空子

    面前在介绍那个函数的时候,基本上都关系了会在何地调用这个函数,最后,大家再系统总括一下:

    进程调治分为多个部分:一是进度消息的修改,二是经过切换。进度切换只有一个函数schedule,schedule的周转时机大家最终剖判。其余函数的运行时机如下:

    1.scheduler_tick函数是在种种石英钟中断中被调用,用来更新当前进度运营的小时。

    2.effective_prio函数是在创立一个新进程恐怕用户选取nice系统调用设置进度的预先级时调用,用来安装进程的在基础中优先级(不一致于nice值)。

    3.set_load_weight函数也是在开革新进度也许用户使用nice()设置进程的先行级时调用,用来设置进度的权重。该函数和第22中学的函数其实是配套使用的,当设置或许改变了一个历程的预先级时,要么将要为那一个历程设置或然退换该优先级对应的权重。

    4.sched_slice函数首即使在scheduler_tick->...->check_preempt_tick中调用(别的地点也许有),因而也是各类时钟周期调用一遍,用来测算当前经过应该进行的“时间片”,进而本领看清进度是还是不是早已赶上它的日子片,凌驾的话就要安装抢占标记,切换其余进度。

    5.pick_next_task_fair函数schedule中调用,用来采取下三个要被调节的历程,然后技术切换进度。它的进行时机就是schedule的举行时机,随后深入分析。

    6.__enqueue_entity/__dequeue_entity函数是在急需入就绪队列也许出就绪队列的地方被调用,由此选取它们的地点较多,举个例子schedule中调用(间接调用),就不一一解析了。

    7.try_to_wake_up/wake_up_new_task函数,前者在进度所等待的财富满意时被调用(一般在暂停内调用);后者是在创设好新进度后被调用。都是用来唤起进度的,前者唤醒睡眠进度,后者唤醒新进程并将经过进入就绪队列。

    8.task_fork_fair函数也是在新历程被创立好后调用,用来安装新进度的“时间片”等新闻,设置好之后新进程就足以被唤起了。所以该函数和wake_up_new_task函数调用时机是一律的。

    末尾,大家分析schedule函数的调用时机。该函数是唯一兑现进度切换的函数。

    在条分缕析schedule函数的调用时机在此以前,大家先为咱们介绍下“内核调控路线“的概念。所谓内核调节路线,正是由制动踏板大概万分引出的实施路线,说白了,施行中断大概特别管理程序时就高居内核调控路线中,此时意味着的也是时下进程。内核调控路线可以嵌套(也便是能够嵌套中断),但是不管嵌套多少,最后都要再次来到当前历程中,也正是说要从根本调节路线中回到,不能在基本调节路线中开始展览进度切换(因而停顿管理程序中分裂意调用能引起进度睡眠的函数)。谈到底,内核要么处在进度中,要么处在内核调控路线中(其实根本调控路线也表示当前进度,因为其有特殊性,所以大家单列出来谈),不会再有其他什么路径了。那么进程切换的空子,或然说schedule函数被调用的火候,也就只恐怕爆发于上述二种路子中。那么,1.当在经过中调用schedule函数时(正是ULK那本书上说的第一手调用),注脚当前历程因为等待财富照旧别的原因须求挂起,主动吐弃使用cpu,调用schedule函数切换成别的进度中;2.当在基本调节路线中调用schedule函数时(上面说过了,内核调节路线中不允许切换进程),其实是在基本调控路线再次回到进度时调用(该时机正是ULK上说的延迟调用),表达有更器重的长河等待执行,要求抢占当前经过,由此在暂停管理程序/至极处理程序再次来到时都要去检查当前进度能不可能被私吞,能够抢占的话将要调用schedule函数实行进度切换,包涵从系统调用中回到用户空间时也要检查(那是联合的,因为系统调用本人也是特别,因此从系统调用中回到约等于从那几个管理程序中回到,通过系统调用进入到内核态也足以说是基础调整路径,然则一般不这么叫)当前历程的并吞标记,能发生抢占的话就要去调用schedule函数。至此,进程切换的时机就深入分析完了。很好记的,要么是进程上下文爆发进度切换(主动调用schedule),要么是从中断重临时切换,由此老是中断再次来到时必须求检查是还是不是发生抢占,包含从系统调用重返也属于这种情形。

     

    迄今甘休,进程调解机制我们就解析完了(其实只剖析了CFS调整)。实时进度调解以后再深入分析!

     

    参照书籍:《深刻精通linux内核》

         《深切明白linux内核架构》

    仿效文章:blog.csdn.net/wudongxu/article/details/8574737

         blog.csdn.net/dog250/article/details/5302869

           chxxxyg.blog.163.com/blog/static/1502811932012912546208/

    [cpp] view plaincopy

    static void update_curr(struct cfs_rq *cfs_rq)
    {
        struct sched_entity *curr = cfs_rq->curr;
        u64 now = rq_of(cfs_rq)->clock_task;    // 获取当前时间,这里是实际时间
        unsigned long delta_exec;
    
        if (unlikely(!curr))
            return;
        delta_exec = (unsigned long)(now - curr->exec_start); // 计算自上个周期到当前时间的差值,也就是该调度实体的增量runtime
        if (!delta_exec)
            return;
    
        __update_curr(cfs_rq, curr, delta_exec);        //真正的更新函数
        curr->exec_start = now;    // exec_start更新成now,用于下次计算
        ...
        account_cfs_rq_runtime(cfs_rq, delta_exec);     //和cpu.cfs_quota_us  cpu.cfs_period_us相关
    }
    
    static inline void __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,unsigned long delta_exec)
    {
        unsigned long delta_exec_weighted;
        curr->sum_exec_runtime  = delta_exec;   //sum_exec_runtime保存该调度实体累计的运行时间runtime
        delta_exec_weighted = calc_delta_fair(delta_exec, curr);    //用runtime(delta_exec)根据公式2算出vruntime(delta_exec_weighted)
        curr->vruntime  = delta_exec_weighted;  //累加vruntime
        update_min_vruntime(cfs_rq);    //更新该cfs的min_vruntime
    }
    
    1. /* 
    2.  * Should 'se' preempt 'curr'. 
    3.  * 
    4.  *             |s1 
    5.  *        |s2 
    6.  *   |s3 
    7.  *         g 
    8.  *      |<--->|c 
    9.  * 
    10.  *  w(c, s1) = -1 
    11.  *  w(c, s2) =  0 
    12.  *  w(c, s3) =  1 
    13.  * 
    14.  */  
    15. static int  
    16. wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se)  
    17. {  
    18.     s64 gran, vdiff = curr->vruntime - se->vruntime;  
    19.     if (vdiff <= 0)  
    20.         return -1;  
    21.     gran = wakeup_gran(curr);  
    22.     if (vdiff > gran)  
    23.         return 1;  
    24.     return 0;  
    25. }  

    这几行代码照旧相比较明晰的,依照runtime 公式2算出vruntime,然后累加该sched_entity的vruntime.
    因为该sched_entity的vruntime扩展了,所以从前封存的cfs_rq->min_vruntime恐怕失效了,update_min_vruntime()函数担当检查并更新.
    同理,该sched_entity的vruntime可能不是该cfs红黑树中细小的了,因而地点调用了check_preempt_tick()轻巧是还是不是必要再一次调节.
    看下check_preempt_tick():

    以此函数再次来到-1表示新进度vruntime大于当前进度,当然不能够抢占。
    重回0表示就算新进程vruntime比当下历程小。可是未有小到调整粒度,一般也无法抢占
    回到1意味着新进度vruntime比前段时间进度小的超过了调治粒度,可以抢占。
    调解粒度是何许概念吗?那么些也卓殊好驾驭,仅仅是必须对眼下的定义作出一些调动,
    前方说每一回都简短选择vruntime最小的进程调整,事实上也不完全部都以如此。
    假若进度A和B的vruntime非常类似。那么A先实行了三个tick。vruntime比B大了,
    B又举行二个tick,vruntime又比A大了。又切换成A。那样就能在AB间频仍切换。对质量影响相当的大。
    就此只要当前历程的时辰没实用完,就单纯有当有进度的vruntime比当下进度小抢先调治粒度时。
    手艺举办进程切换。

    static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
    {
        unsigned long ideal_runtime, delta_exec;
        struct sched_entity *se;
        s64 delta;
    
        ideal_runtime = sched_slice(cfs_rq, curr);  //根据该sched_entity的权重以及公式1计算出期在本调度周期最大可运行的runtime
        delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;  //该周期已经运行的runtime
        if (delta_exec > ideal_runtime) {       // 超过了,则调用resched_task设置重新调度标记,(cgroup中的cpu.cfs_quota_us, cpu.cfs_period_us)
            resched_task(rq_of(cfs_rq)->curr);
            return;
        }
    
    
        if (delta_exec < sysctl_sched_min_granularity)
            return;
    
        se = __pick_first_entity(cfs_rq);       // 选取当前红黑树最左端的sched_entity
        delta = curr->vruntime - se->vruntime;  // 两个sched_entity的vruntime比较
    
        if (delta < 0)
            return;
    
        if (delta > ideal_runtime)      //这里是个BUG,vruntime不能和runtime比啊,改为 if (delta > calc_delta_fair(ideal_runtime, curr))
            resched_task(rq_of(cfs_rq)->curr);
    }
    

    函数方面凝视中万分图便是这几个意思,大家看下:
    横坐标表示vruntime。s1 s2 s3独家表示新进度,c表示近些日子历程,g表示调治粒度。
    s3明确能抢占c。而s1不容许抢占c。
    s2固然vruntime比c小。然而在调节粒度之内,是不是能抢占要看事态,像今后如此的场馆就无法抢占。

    check_preempt_tick也是相比较明晰的,首先该sched_entity占用cpu的实在时间无法超过依照其权值算出的份额.
    其次,如若红黑树最右侧的sched_entity的vruntime更小(delta > ideal_runtime *(orig_load_value / se->load.weight)参见公式2),那么发起调解.
    此间不直接决断delta > 0,应该是为着防止万一反复调节吧,cfs调治算法甘休.

    到此地,创立进度时的调节相关代码就介绍完了。

    ------实时调整算法,
    实时调整算法原理上没啥好说的,直接看代码吧.

     

    static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
    {
        struct sched_rt_entity *rt_se = &p->rt;
    
        update_curr_rt(rq);     // 更新runtime统计,并判断实时进程占用份额达到限制的话,设置调度标记
    
        if (p->policy != SCHED_RR)  // 如果不是SCHED_RR调度策略,直接返回
            return;
    
        if (--p->rt.time_slice)     // 每次tick中断-1
            return;
    
        p->rt.time_slice = sched_rr_timeslice;  // p->rt.time_slice 自减为0,该调度了,给起重新赋值后放入队列末尾
    
        for_each_sched_rt_entity(rt_se) {
            if (rt_se->run_list.prev != rt_se->run_list.next) {
                requeue_task_rt(rq, p, 0);          // 挂到对列尾,
                set_tsk_need_resched(p);        // 设置调度标记
                return;
            }
        }
    }
    
    static void update_curr_rt(struct rq *rq)
    {
        struct task_struct *curr = rq->curr;
        struct sched_rt_entity *rt_se = &curr->rt;
        struct rt_rq *rt_rq = rt_rq_of_se(rt_se);
        u64 delta_exec;
    
        delta_exec = rq->clock_task - curr->se.exec_start;  //增量runtime
    
        curr->se.sum_exec_runtime  = delta_exec;    //task_struct累加
    
        curr->se.exec_start = rq->clock_task;   //置为当前时间,用以下次计算差值
    
    
        for_each_sched_rt_entity(rt_se) {
            rt_rq = rt_rq_of_se(rt_se);
            if (sched_rt_runtime(rt_rq) != RUNTIME_INF) {   //组调度?
                rt_rq->rt_time  = delta_exec;   // 当前rt_rq的rt_time累加
                if (sched_rt_runtime_exceeded(rt_rq))   //该rt_rq运行时间是否超出限制
                    resched_task(curr);     //是就调度,
            }
        }
    }
    

     

    sched_rt_runtime_exceeded里的决断涉及到了用户层通过cgroup限制实时经过的cpu份额(cpu.rt_period_us, cpu.rt_runtime_us).看下:

     

    static int sched_rt_runtime_exceeded(struct rt_rq *rt_rq)
    {
        u64 runtime = sched_rt_runtime(rt_rq);
    
        if (rt_rq->rt_throttled)
            return rt_rq_throttled(rt_rq);
    
        if (runtime >= sched_rt_period(rt_rq))
            return 0;
    
        balance_runtime(rt_rq);     //去其他核上借点时间,不深究了,(cpu.rt_runtime_us / cpu.rt_period_us * cpus才是真正的最大份额)
        runtime = sched_rt_runtime(rt_rq);  //获取本周期最大执行时间
        if (runtime == RUNTIME_INF)
            return 0;
    
        if (rt_rq->rt_time > runtime) {     //当前运行时间已经超出,需要让出cpu
            struct rt_bandwidth *rt_b = sched_rt_bandwidth(rt_rq);
    
            /*
             * Don't actually throttle groups that have no runtime assigned
             * but accrue some time due to boosting.
             */
            if (likely(rt_b->rt_runtime)) {
    
                rt_rq->rt_throttled = 1;    //设置该rt_throttled为1,当新的周期开始时,rt_throttled重新被置0
    
            } else {
                rt_rq->rt_time = 0;
            }
    
            if (rt_rq_throttled(rt_rq)) {
                sched_rt_rq_dequeue(rt_rq); //将该rt_rq --> task_group(后面说) --> sched_rt_entity出队
                return 1;
            }
        }
    
        return 0;
    }
    

    三、唤醒进度
    咱俩再看看唤醒进度时的CFS动作。看下函数try_to_wake_up。相当短的函数,仅仅留几行代码

    到此实时调整也终结了,下边看下组调节.

    2.组调度
    上边探讨时直接用调解实体来代表经过的,那是因为,贰个调治实体只怕对应一个task_struct,也或许对应八个task_group,看下struct:

    struct task_struct {
        int on_rq;
        int prio, static_prio, normal_prio;
        unsigned int rt_priority;   
        const struct sched_class *sched_class;  //调度类
        struct sched_entity se;     // cfs调度实体
        struct sched_rt_entity rt;  //rt调度实体
        struct task_group *sched_task_group;
        ...
    }
    
    struct task_group {
        struct cgroup_subsys_state css;
    
        struct sched_entity **se;   //cfs调度实体
        struct cfs_rq **cfs_rq;     //子cfs队列
        unsigned long shares;       //
    
        atomic_t load_weight;       //
    
        struct sched_rt_entity **rt_se; // rt调度实体
        struct rt_rq **rt_rq;       //子rt队列
    
        struct rt_bandwidth rt_bandwidth;   //存储cpu.rt_period_us, cpu.rt_runtime_us
        struct cfs_bandwidth cfs_bandwidth; //存储cpu.cfs_quota_us, cpu.cfs_period_us
    };
    

    可见task_group和task_struct中都内嵌了调节实体,与膝下差异的是,task_group并不是最后的运作单位,它只是意味本group中的全数task在上层分配总的cpu财富,
    图2展示了task_group和其余多少个布局的关系.
    图2
    新葡亰496net 20
    结缘图1来看,调治算法在选到二个entity时,即便对应的是多少个group,那么就在该cgroup的子rq中重复选拔,知道选中三个实在进度,结合代码吧:

    static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
    {
        struct cfs_rq *cfs_rq;
        struct sched_entity *se = &curr->se;
    
        for_each_sched_entity(se) {     //组调度
            cfs_rq = cfs_rq_of(se);
            entity_tick(cfs_rq, se, queued);    //在entity_tick中更新cfs队列,当前调度实体时间相关的统计,并判断是否需要调度
        }
        ....
    }
    
    #define for_each_sched_entity(se) 
            for (; se; se = se->parent)
    

    这是地点的代码,展开for_each_sched_entity宏
    更新总计音讯时是循环的,即使该调治实体是平常进程,那么se->parent为NULL,假诺是某些group下的历程,那么循环境与发展展更新父group.

    static struct task_struct *pick_next_task_fair(struct rq *rq)
    {
        struct task_struct *p;
        struct cfs_rq *cfs_rq = &rq->cfs;
        struct sched_entity *se;
    
        if (!cfs_rq->nr_running)
            return NULL;
    
        do {        //循环处理
            se = pick_next_entity(cfs_rq);
            set_next_entity(cfs_rq, se);
            cfs_rq = group_cfs_rq(se);
        } while (cfs_rq);
    
        p = task_of(se);
        if (hrtick_enabled(rq))
            hrtick_start_fair(rq, p);
    
        return p;
    }
    

    调治器接纳下一个装载的经过时,pick_next_entity选出最左边的sched_entity,如若对应的是一个group,那么就从该cgroup的 sub cfs_rq继续选,知道选到一个进程,
    实时组调解也是相近的,有分别的是,实时group的权重,是该group下放权力重最大的长河的权重,因为实时进程优先级高的侵吞优先级低的呀.
    上边结合下前面文章(cgroup原理简析:vfs文件系统)来讲下通过echo

    [cpp] view plaincopy

    cgroup的操纵文件是何许界定该cgroup下的进程的吧.

    eg: echo 2048 >> cpu.shares
    vfs调用过成上篇作品说过了,不再另行,直接到cpu_shares_write_u64,cpu_shares_write_u六十三只是包装,实际专门的职业的是sched_group_set_shares()函数.

    int sched_group_set_shares(struct task_group *tg, unsigned long shares)
    {
        ....
        tg->shares = shares;            //将shares赋值到tg->shares
        for_each_possible_cpu(i) {      //一个cgroup在每个cpu上都有一个调度实体,
            struct rq *rq = cpu_rq(i);
            struct sched_entity *se;
            se = tg->se[i];
            for_each_sched_entity(se)
                update_cfs_shares(group_cfs_rq(se));    
        }
        ...
        return 0;
    }
    进到update_cfs_shares()函数:
    static void update_cfs_shares(struct cfs_rq *cfs_rq)
    {
        struct task_group *tg;
        struct sched_entity *se;
        long shares;
    
        tg = cfs_rq->tg;
        se = tg->se[cpu_of(rq_of(cfs_rq))];
        if (!se || throttled_hierarchy(cfs_rq))
            return;
    #ifndef CONFIG_SMP
        if (likely(se->load.weight == tg->shares))
            return;
    #endif
        shares = calc_cfs_shares(cfs_rq, tg);       //可以理解为shares = tg->share
    
        reweight_entity(cfs_rq_of(se), se, shares); //设置该task_group对应的sched_entity的load.weight
    }
    一些简单的判断,最终调用reweight_entity设置weight,
    static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, unsigned long weight)
    {
        ...
        update_load_set(&se->load, weight);
        ...
    }
    static inline void update_load_set(struct load_weight *lw, unsigned long w)
    {
        lw->weight = w;
        lw->inv_weight = 0;
    }
    

    echo 2048 >> cpu.shares便是安装cgroup->task_group->sched_entity.load.weight对应的值.
    结合公式1,退换了weight就最后影响到了该sched_entity占cpu的其实时间.
    注意该sched_entity不是有个别进度的,而是task_group(cgroup)的,所以最后结出是:该cgroup下的进程占用cpu之和万分该sched_entity.load.weight结合公式1算出来的cpu时间.

    eg: echo 1000000 >> cpu.rt_period_us   echo 950000 >> cpu.rt_period_us
    那边以实时进程看下,cpu.cfs_quota_us  cpu.cfs_period_us也是周边,不再看了.
    vfs-->cpu_rt_period_write_uint,那一个函数封装sched_group_set_rt_period()函数.

    static int sched_group_set_rt_period(struct task_group *tg, long rt_period_us)
    {
        u64 rt_runtime, rt_period;
    
        rt_period = (u64)rt_period_us * NSEC_PER_USEC;  //计算新的period
        rt_runtime = tg->rt_bandwidth.rt_runtime;       //原有的quota
    
        if (rt_period == 0)
            return -EINVAL;
    
        return tg_set_rt_bandwidth(tg, rt_period, rt_runtime);  //设置
    }
    
    
    vfs-->cpu_rt_runtime_write,这个函数封装sched_group_set_rt_runtime()函数,
    static int sched_group_set_rt_runtime(struct task_group *tg, long rt_runtime_us)
    {
        u64 rt_runtime, rt_period;
    
        rt_period = ktime_to_ns(tg->rt_bandwidth.rt_period);    //原有的period
        rt_runtime = (u64)rt_runtime_us * NSEC_PER_USEC;        //新的quota
        if (rt_runtime_us < 0)
            rt_runtime = RUNTIME_INF;
    
        return tg_set_rt_bandwidth(tg, rt_period, rt_runtime);  //设置
    }
    
    可见设置cpu.rt_period_us,cpu.rt_period_us时,都是除了新设的值,再取另外一个原有的值,一起调用tg_set_rt_bandwidth设置.
    static int tg_set_rt_bandwidth(struct task_group *tg, u64 rt_period, u64 rt_runtime)
    {
        ...
        tg->rt_bandwidth.rt_period = ns_to_ktime(rt_period);    //设置,将quota period存到task_group里
        tg->rt_bandwidth.rt_runtime = rt_runtime;
    
        for_each_possible_cpu(i) {
            struct rt_rq *rt_rq = tg->rt_rq[i];
    
            raw_spin_lock(&rt_rq->rt_runtime_lock);
            rt_rq->rt_runtime = rt_runtime;         //同步设置每个rt_rq->rt_runtime
            raw_spin_unlock(&rt_rq->rt_runtime_lock);
        }
        raw_spin_unlock_irq(&tg->rt_bandwidth.rt_runtime_lock);
    unlock:
        read_unlock(&tasklist_lock);
        mutex_unlock(&rt_constraints_mutex);
    
        return err;
    }
    
    1. /*** 
    2.  * try_to_wake_up - wake up a thread 
    3.  * @p: the to-be-woken-up thread 
    4.  * @state: the mask of task states that can be woken 
    5.  * @sync: do a synchronous wakeup? 
    6.  * 
    7.  * Put it on the run-queue if it's not already there. The "current" 
    8.  * thread is always on the run-queue (except when the actual 
    9.  * re-schedule is in progress), and as such you're allowed to do 
    10.  * the simpler "current->state = TASK_RUNNING" to mark yourself 
    11.  * runnable without the overhead of this. 
    12.  * 
    13.  * returns failure only if the task is already active. 
    14.  */  
    15. static int try_to_wake_up(struct task_struct *p, unsigned int state, int sync)  
    16. {  
    17.     int cpu, orig_cpu, this_cpu, success = 0;  
    18.     unsigned long flags;  
    19.     struct rq *rq;  
    20.     rq = task_rq_lock(p, &flags);  
    21.     if (p->se.on_rq)  
    22.         goto out_running;  
    23.     update_rq_clock(rq);  
    24.     activate_task(rq, p, 1);  
    25.     success = 1;  
    26. out_running:  
    27.     check_preempt_curr(rq, p, sync);  
    28.     p->state = TASK_RUNNING;  
    29. out:  
    30.     current->se.last_wakeup = current->se.sum_exec_runtime;  
    31.     task_rq_unlock(rq, &flags);  
    32.     return success;  
    33. }  

    整合方面说的实时调治算法,设置了rt_rq->rt_runtime也正是设置了实时进程在各类周期占用的cpu的最大比例.

    eg: echo pid >> tasks
    这一个操作分为两部。首先改动进程所属的cgroup,在此以前曾经说过了.其次调用各种子系统的attach钩子函数,这里也正是改造进程所在的周转队列。
    简轻便单的话就是退换多少个指针的值。让进度与原来的就绪队列断开。跑在cgroup的就绪队列中。
    如有错误,请指正。

     
    update_rq_clock正是翻新cfs_rq的闹钟,保持与系统时间共同。
    重点是activate_task,它将经过增添红黑树而且对vruntime做一些调动。
    然后用check_preempt_curr检查是否构成侵夺条件。假若能够抢占则设置TIF_NEED_RESCHED标识。

    由于check_preempt_curr讲过了,我们唯有顺着以下的依次走一次
       activate_task
    -->enqueue_task
    -->enqueue_task_fair
    -->enqueue_entity
    -->place_entity

    [cpp] view plaincopy

    1. static void activate_task(struct rq *rq, struct task_struct *p, int wakeup)  
    2. {  
    3.     if (task_contributes_to_load(p))  
    4.         rq->nr_uninterruptible--;  
    5.     enqueue_task(rq, p, wakeup);  
    6.     inc_nr_running(rq); //实施队列上的服服帖帖任务多了一个  
    7. }  
    8. static void enqueue_task(struct rq *rq, struct task_struct *p, int wakeup)  
    9. {  
    10.     sched_info_queued(p);  
    11.     p->sched_class->enqueue_task(rq, p, wakeup);  
    12.     p->se.on_rq = 1;  //被唤醒的任务会将on_rq设为1  
    13. }  
    14. /* 
    15.  * The enqueue_task method is called before nr_running is 
    16.  * increased. Here we update the fair scheduling stats and 
    17.  * then put the task into the rbtree: 
    18.  */  
    19. static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup)  
    20. {  
    21.     struct cfs_rq *cfs_rq;  
    22.     struct sched_entity *内核源码分析之进程调度机制,linux内核CFS进程调度策略。se = &p->se;  
    23.     for_each_sched_entity(se) {  
    24.         if (se->on_rq)  
    25.             break;  
    26.         cfs_rq = cfs_rq_of(se);  
    27.         enqueue_entity(cfs_rq, se, wakeup);  
    28.         wakeup = 1;  
    29.     }  
    30.     hrtick_update(rq);  
    31. }  
    32. static void  
    33. enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int wakeup)  
    34. {  
    35.     /* 
    36.      * Update run-time statistics of the 'current'. 
    37.      */  
    38.     update_curr(cfs_rq);  
    39.     account_entity_enqueue(cfs_rq, se);  
    40.     if (wakeup) {  
    41.         place_entity(cfs_rq, se, 0);  
    42.         enqueue_sleeper(cfs_rq, se);  
    43.     }  
    44.     update_stats_enqueue(cfs_rq, se);  
    45.     check_spread(cfs_rq, se);  
    46.     if (se != cfs_rq->curr)  
    47. 内核源码分析之进程调度机制,linux内核CFS进程调度策略。        __enqueue_entity(cfs_rq, se);  //把进度增添CFS红黑树  
    48. }  

     
    此处还需求再看三遍place_entity,前边纵然看过一回,可是第几个參数不相同等。
    当參数3为0的时候走的是还会有一条渠道,大家看下

    [cpp] view plaincopy

    1. static void  
    2. place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)  
    3. {  
    4.     u64 vruntime = cfs_rq->min_vruntime;  
    5.     /* 
    6.      * The 'current' period is already promised to the current tasks, 
    7.      * however the extra weight of the new task will slow them down a 
    8.      * little, place the new task so that it fits in the slot that 
    9.      * stays open at the end. 
    10.      */  
    11.     if (initial && sched_feat(START_DEBIT))  
    12.         vruntime  = sched_vslice(cfs_rq, se);  
    13.     if (!initial) {  
    14.         /* sleeps upto a single latency don't count. */  
    15.         if (sched_feat(NEW_FAIR_SLEEPERS)) {  
    16.             unsigned long thresh = sysctl_sched_latency;  
    17.             /* 
    18.              * convert the sleeper threshold into virtual time 
    19.              */  
    20.             if (sched_feat(NORMALIZED_SLEEPER))  
    21.                 thresh = calc_delta_fair(thresh, se);  
    22.             vruntime -= thresh;  
    23.         }  
    24.         /* ensure we never gain time by being placed backwards. */  
    25.         vruntime = max_vruntime(se->vruntime, vruntime);  
    26.     }  
    27.     se->vruntime = vruntime;  
    28. }  

     
    initial不等同时候两条渠道有啥样差异啊?路线1是新成立职责的时候对其vruntime实行发轫化,将它座落红黑树右端。
    而以下那条路是提示睡眠职责时的代码,大家着想二个职分睡眠了不短日子,它的vruntime就直接不会更新,
    这么当它醒来时vruntime会远远小于实行队列上的不论什么叁个职责,于是它会悠久据有CPU,那显明是不创造的。
    于是那要对唤醒职分的vruntime举香港行政局地调节,大家能够看出。这里是用min_vruntime减去三个thresh,
    以此thresh的估计进程便是将sysctl_sched_latency换算成进度的vruntime,而这么些sysctl_sched_latency
    正是暗中认可的调节周期。单核CPU上相似为20ms。之所以要减去一个值是为了对睡眠进度做一个补偿,
    能让它醒来时能够快捷的到CPU。
    个人感到那一个企划很聪明伶俐,曾经O(1)调节器有三个复杂的公式(于今本人也没能记住),
    用来区分交互式进度和CPU密集型进度,实际情况请參考ULK等书。而现在CFS无须再利用非常复杂的公式了。
    可是倘使常睡眠的长河,它被提示时料定会有极其小的vruntime。能够立时执行,省却了非常多特别情状的管理。
    一如以往时候还要注意那句凝视 ensure we never gain time by being placed backwards。
    自然这里是给由于长日子睡觉而vruntime远远低于min_vruntime的进程补偿的,不过多少进程仅仅睡眠极长时间,
    这么在它复苏后vruntime依然出乎min_vruntime,不能够让进度经过睡眠得到额外的实践时间。所以最后选用
    测算出的补充时间与经过原来vruntime中的相当的大者。

    到这里,place_entity就讲完了。

    可是小编有叁个标题,为啥总结thresh要用整个调治周期换算成vruntime?
    个人以为应该用(调解周期 * 进度权重 / 全部进程总权重)再折算成vruntime才说得有理阿,用任何调解周期
    是否补充太多了?

     

     

    四、进度调节schedule

    以下看下主动调解代码schedule。

    [cpp] view plaincopy

    1. /* 
    2.  * schedule() is the main scheduler function. 
    3.  */  
    4. asmlinkage void __sched schedule(void)  
    5. {  
    6.     struct task_struct *prev, *next;  
    7.     unsigned long *switch_count;  
    8.     struct rq *rq;  
    9.     int cpu;  
    10. need_resched:  
    11.     preempt_disable(); //在那当中被侵占大概出现故障。先禁止它!  
    12.     cpu = smp_processor_id();  
    13.     rq = cpu_rq(cpu);  
    14.     rcu_qsctr_inc(cpu);  
    15.     prev = rq->curr;  
    16.     switch_count = &prev->nivcsw;  
    17.     release_kernel_lock(prev);  
    18. need_resched_nonpreemptible:  
    19.     spin_lock_irq(&rq->lock);  
    20.     update_rq_clock(rq);  
    21.     clear_tsk_need_resched(prev); //清除须求调解的位  
    22.     //state==0是TASK_RUNNING,不等于0正是策画睡觉,寻常情状下应该将它移出实施队列  
    23.     //可是还要检查下是还是不是有时域信号过来。假如有时域信号而且经过处于可暂停睡眠就提示它  
    24.     //注意对于要求睡觉的进程。这里调用deactive_task将其移出队列而且on_rq也被清零  
    25.     //这个deactivate_task函数就不看了,极度easy  
    26.     if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {  
    27.         if (unlikely(signal_pending_state(prev->state, prev)))  
    28.             prev->state = TASK_RUNNING;  
    29.         else  
    30.             deactivate_task(rq, prev, 1);  
    31.         switch_count = &prev->nvcsw;  
    32.     }  
    33.     if (unlikely(!rq->nr_running))  
    34.         idle_balance(cpu, rq);  
    35.     //那多少个函数都以最首要,大家以下分析  
    36.     prev->sched_class->put_prev_task(rq, prev);  
    37.     next = pick_next_task(rq, prev);  
    38.     if (likely(prev != next)) {  
    39.         sched_info_switch(prev, next);  
    40.         rq->nr_switches ;  
    41.         rq->curr = next;  
    42.          *switch_count;  
    43.         //实现进程切换,不讲了,跟CFS不要紧  
    44.         context_switch(rq, prev, next); /* unlocks the rq */  
    45.         /* 
    46.          * the context switch might have flipped the stack from under 
    47.          * us, hence refresh the local variables. 
    48.          */  
    49.         cpu = smp_processor_id();  
    50.         rq = cpu_rq(cpu);  
    51.     } else  
    52.         spin_unlock_irq(&rq->lock);  
    53.     if (unlikely(reacquire_kernel_lock(current) < 0))  
    54.         goto need_resched_nonpreemptible;  
    55.     preempt_enable_no_resched();  
    56.     //这里新历程也说不定有TIF_NEED_RESCHED标识,假如新历程也亟须调整则再调治叁回  
    57.     if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))  
    58.         goto need_resched;  
    59. }  

     

    首先看put_prev_task,它等于put_prev_task_fair,后者基本上正是平昔调用put_prev_entity

    [cpp] view plaincopy

    1. static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)  
    2. {  
    3.     /* 
    4.      * If still on the runqueue then deactivate_task() 
    5.      * was not called and update_curr() has to be done: 
    6.      */  
    7.     //记得这里的on_rq吗?在schedule函数中假设进度境况不是TASK_RUNNING,  
    8.     //那么会调用deactivate_task将prev移出推行队列,on_rq清零。由此这里也是独自有当  
    9.     //prev进度还是在试行态的时候才要求更新vruntime等音讯。  
    10.     //假使prev进度由于被侵夺只怕是因为岁月到了而被调整出去则on_rq仍然为1  
    11.     if (prev->on_rq)  
    12.         update_curr(cfs_rq);  
    13.     check_spread(cfs_rq, prev);  
    14.     //这里也完全一样,仅仅有当prev进度仍在实行景况的时候才须求更新vruntime新闻  
    15.     //实际上正在cpu上执行的历程是不在红黑树中的,仅仅有在等待CPU的长河才在红黑树  
    16.     //由此这里将调治出的历程又三次扩张红黑树。

      on_rq并不意味在红黑树中,而是意味着在执市价况

        

    17.     if (prev->on_rq) {  

    18.         update_stats_wait_start(cfs_rq, prev);  
    19.         /* Put 'current' back into the tree. */  
    20.         //那个函数也不跟进去了,正是把经过以(vruntime-min_vruntime)为key增添到红黑树中  
    21.         __enqueue_entity(cfs_rq, prev);  
    22.     }  
    23.     //未有当前经过了。那一个当前经过将在pick_next_task中更新  
    24.     cfs_rq->curr = NULL;  
    25. }  

     

    再回到schedule中看看pick_next_task函数。基本上也正是平昔调用pick_next_task_fair

    [cpp] view plaincopy

    1. static struct task_struct *pick_next_task_fair(struct rq *rq)  
    2. {  
    3.     struct task_struct *p;  
    4.     struct cfs_rq *cfs_rq = &rq->cfs;  
    5.     struct sched_entity *se;  
    6.     if (unlikely(!cfs_rq->nr_running))  
    7.         return NULL;  
    8.     do {  
    9.         //那多少个函数是非同一般,选用下二个要运行的天职  
    10.         se = pick_next_entity(cfs_rq);  
    11.         set_next_entity(cfs_rq, se);  
    12.         cfs_rq = group_cfs_rq(se);  
    13.     } while (cfs_rq);  
    14.     p = task_of(se);  
    15.     hrtick_start_fair(rq, p);  
    16.     return p;  
    17. }  

     

    首要看下pick_next_entity和set_next_entity

    [cpp] view plaincopy

    1. static struct sched_entity *pick_next_entity(struct cfs_rq *cfs_rq)  
    2. {  
    3.     //__pick_next_entity便是一分区直属机关接大选择红黑树缓存的最左结点,也等于vruntime最小的结点  
    4.     struct sched_entity *se = __pick_next_entity(cfs_rq);  
    5.     //以下的wakeup_preempt_entity已经讲过。忘记的同学能够到地点看下  
    6.     //这里正是后面所说的优先照看next和last进度,仅仅有当__pick_next_entity选出来的进度  
    7.     //的vruntime比next和last都小超姚剧治粒度时才轮到它实践,不然正是next或许last  
    8.     if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, se) < 1)  
    9.         return cfs_rq->next;  
    10.     if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, se) < 1)  
    11.         return cfs_rq->last;  
    12.     return se;  
    13. }  
    14. static void  
    15. set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)  
    16. {  
    17.     /* 'current' is not kept within the tree. */  
    18.     //这里怎么动静下规范会为假?小编认为刚唤醒的长河恐怕不在rq上  
    19.     //不过回到地点去看了下,唤醒的历程也因此activate_task将on_rq置1了  
    20.     //新创设的进度on_rq也被置1,这里怎么状态会为假,想不出来  
    21.     //这里作者也測试了一下。在规则为真假的门道上各设置了叁个计数器  
    22.     //当条件为真经过了临近五捌仟0次的时候条件为假仅有一次。  
    23.     //所以大家能够以为差不离都会平素进入if语句块推行  
    24.     if (se->on_rq) {  
    25.         /*此地凝视是还是不是写错了?dequeued写成了enqueued? 
    26.          * Any task has to be enqueued before it get to execute on 
    27.          * a CPU. So account for the time it spent waiting on the 
    28.          * runqueue. 
    29.          */  
    30.         update_stats_wait_end(cfs_rq, se);  
    31.         //就是把结点从红黑树上取下来。

      后边说过,占用CPU的历程不在红黑树上

        

    32.         __dequeue_entity(cfs_rq, se);  

    33.     }  
    34.     update_stats_curr_start(cfs_rq, se);  
    35.     cfs_rq->curr = se;  //OK。在put_prev_entity中清空的curr在那边被更新  
    36.     //将进度实行总时间保存到prev_..中。那样经过这次调整的试行时间能够用以下公式总结:  
    37.     //进程本次试行已占领CPU时间 =  sum_exec_runtime - prev_sum_exec_runtime  
    38.     //这里sum_exec_runtime会在每便机械钟tick中立异  
    39.     se->prev_sum_exec_runtime = se->sum_exec_runtime;  
    40. }  

     
    到此schedule函数也讲完了。

    关于dequeue_task,dequeue_entity和__dequeue_entity三者差异
    前两个差不太多。不一样的那有个别本身也没看明显。。。紧假使它们都会将on_rq清零。
    自己感觉是当进度要相差TASK_RUNNING状态时调用。那五个函数能够将经过取下试行队列。

    而__dequeue_entity不会将on_rq清零,仅仅是将经过从红黑树上取下,
    自个儿以为一般用在进程将得到CPU的意况,那时须要将它从红黑树取下,可是还要保留在rq上。

     

     

    五、时钟中断

    接下去的气象便是石英钟中断,石英钟中断在time_init_hook中起初化,中断函数为timer_interrupt
    遵循比方以下门路
       timer_interrupt
    -->do_timer_interrupt_hook
    -->这里有多少个回调函数,在自己机器上測试调用的是tick_handle_oneshot_broadcast
    -->从tick_handle_oneshot_broadcast后边一部分历程怎么走的没搞通晓,不时间用kgdb跟踪一下
    -->反正最后是到了tick_handle_periodic
    -->tick_periodic
    -->update_process_times
    -->scheduler_tick 这中间跟CFS相关的主要正是翻新了cfs_rq的时钟
    -->通过回调函数调到task_tick_fair,没作什么事,直接进入了entity_tick
    -->entity_tick这一个函数看下

    [cpp] view plaincopy

    1. static void  
    2. entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)  
    3. {  
    4.     /* 
    5.      * Update run-time statistics of the 'current'. 
    6.      */  
    7.     update_curr(cfs_rq);  
    8.     //....无关代码  
    9.     if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))  
    10.         check_preempt_tick(cfs_rq, curr);  
    11. }  

    entity_tick函数便是翻新情形新闻,然后检測是还是不是满意抢占条件。
    眼下我们平素忽略update_curr,这里非看不可一下了

    [cpp] view plaincopy

    1. static void update_curr(struct cfs_rq *cfs_rq)  
    2. {  
    3.     struct sched_entity *curr = cfs_rq->curr;  
    4.     u64 now = rq_of(cfs_rq)->clock; //这个clock刚刚在scheduler_tick中改进过  
    5.     unsigned long delta_exec;  
    6.     /* 
    7.      * Get the amount of time the current task was running 
    8.      * since the last time we changed load (this cannot 
    9.      * overflow on 32 bits): 
    10.      */  
    11.     //exec_start记录的是上叁次调用update_curr的年月,我们用当下时间减去exec_start  
    12.     //就获得了从上次测算vruntime到近年来进程又实践的时日,用这几个时间换算成vruntime  
    13.     //然后加到vruntime上,这一切是在__update_curr中得了的  
    14.     delta_exec = (unsigned long)(now - curr->exec_start);  
    15.     __update_curr(cfs_rq, curr, delta_exec);  
    16.     curr->exec_start = now;   
    17.     if (entity_is_task(curr)) {  
    18.         struct task_struct *curtask = task_of(curr);  
    19.         cpuacct_charge(curtask, delta_exec);  
    20.         account_group_exec_runtime(curtask, delta_exec);  
    21.     }  
    22. }  
    23. /* 
    24.  * Update the current task's runtime statistics. Skip current tasks that 
    25.  * are not in our scheduling class. 
    26.  */  
    27. static inline void  
    28. __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,  
    29.           unsigned long delta_exec)  
    30. {  
    31.     unsigned long delta_exec_weighted;  
    32.     //前边说的sum_exec_runtime就是在此地计算的,它相当于进程从创设開始占用CPU的总时间  
    33.     curr->sum_exec_runtime  = delta_exec;   
    34.     //以下变量的weighted表示那个值是从实行时间思索权重因素换算来的vruntime。再写三次那些公式  
    35.     //vruntime(delta_exec_weighted) = 实际实施时间(delta_exe) * 1024 / 进度权重  
    36.     delta_exec_weighted = calc_delta_fair(delta_exec, curr);  
    37.     //将过程刚刚实行的日子换算成vruntime后立马加到进度的vruntime上。  
    38.     curr->vruntime  = delta_exec_weighted;  
    39.     //由于有经过的vruntime变了,因而cfs_rq的min_vruntime大概也要转变,更新它。  
    40.     //这些函数简单,就不跟进去了,就是先取tmp = min(curr->vruntime,leftmost->vruntime)  
    41.     //然后cfs_rq->min_vruntime = max(tmp, cfs_rq->min_vruntime)  
    42.     update_min_vruntime(cfs_rq);  
    43. }   

    OK。更新完CFS状态之后回来entity_tick中,那时需要检測是不是满意抢占条件。这里也是CFS的重中之重之中的三个

    [cpp] view plaincopy

    1. static void  
    2. check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)  
    3. {  
    4.     unsigned long ideal_runtime, delta_exec;  
    5.     //这里sched_slice跟上面讲过的sched_vslice非常象,只是sched_vslice换算成了vruntime,  
    6.     //而这里这些就是事实上时间,未有经过换算,再次回到值的正是此进度在贰个调解周期中应有试行的流年  
    7.     ideal_runtime = sched_slice(cfs_rq, curr);  
    8.     //上面提到过那一个公式了,总计进度已攻克的CPU时间。假使超越了相应并吞的时日(ideal_runtime)  
    9.     //则设置TIF_NEED_RESCHED标记,在退出机械钟中断的进程中会调用schedule函数实行进度切换  
    10.     delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;  
    11.     if (delta_exec > ideal_runtime)  
    12.         resched_task(rq_of(cfs_rq)->curr);  
    13. }  

     

     

     

     

    附:四个小測试

    看prio_to_weight数组,不相同nice值的进度权重差异比极大。最大权重是纤维护合法权益重的伍仟倍左右,
    是还是不是意味若是同有时间有八个进度A和B在系统中实行。A的nice为-20,B的为19,那么A分配的实施时间
    是B的4000倍啊?没有错。作者做了四个尝试,先进行比如以下顺序,

    [cpp] view plaincopy

    1. int main()  
    2. {  
    3.     errno = 0;  
    4.     if(fork()) {  
    5.         setpriority(PRIO_PROCESS, 0, -20);  
    6.     } else {  
    7.         setpriority(PRIO_PROCESS, 0, 19);  
    8.     }     
    9.     if(errno) {  
    10.         printf("failed/n");  
    11.         exit(EXIT_FAILURE);  
    12.     }     
    13.     printf("pid:%d/n", getpid());  
    14.     while(1);  
    15.     return 0;  
    16. }  

     
    下一场再插入举例以下模块

    [cpp] view plaincopy

    1. #define T1 (第贰个经过的pid)  
    2. #define T2 (第贰个进度的pid)  
    3. static int __init sched_test_init(void)  
    4. {  
    5.     struct task_struct *p;   
    6.     for_each_process(p) {  
    7.         if(p->pid == T1 || p->pid == T2)   
    8.             printk("%d runtime:%llu/n", p->pid, p->se.sum_exec_runtime);  
    9.     }     
    10.     return -1; //再次回到-1防护模块真正插入,大家只有供给打字与印刷出地方的音信就能够了。  
    11. }  

     
    再dmesg查看结果,我測试过四回。三遍设置nice分别为0和6,那么权重之比为
    1024 / 272 = 3.7647。结果进行时间之比为 146390068851 / 38892147894 = 3.7640
    能够看到结果一定周边,
    再有贰次设置nice分别为-20和19,权重之比为88761 / 15 = 5917.五千,
    结果实践时间之比为187800781308 / 32603290 = 5760.1788。也特别相近。
    可见见到,下边包车型大巴权重与实施时间成正比的定论是起家的。
    事实上,当作者实行一个nice为-20的程序后,整个系统很卡,差那么一点儿成了幻灯片,
    也表达nice值的比不上带来的差异很明朗。

    除此以外有一点值得一说,固然总体种类特别卡,不过对鼠标键盘的响应依旧要命快。
    本身打字的时候差不离不会有怎样延迟,那也作证。就算CFS没有经过复杂的经历公式区分
    交互式进度。不过它的统一筹划思路使他天生地对交互式进度的响应恐怕比O(1)调治还要好。

    (计算:1、在进程实行时,其vruntime稳固的增进,它在红黑树中年老年是向右移动。由于越重要的长河vruntime增添越慢,由此他们向右移动的快慢也越慢。那样其被调治的机会要当先次要进度,那刚好是我们必须的。2、假诺进度进入睡眠,则其vruntime保持不改变,由于每三个系列min_vruntime同一时间会增多(由于她们是干燥的),那么睡眠进程醒来后,在红黑树中的地方会更靠左。由于其键值变得更加小了。注意底层使用的是红黑树结构)

    本文由新葡亰496net发布于电脑系统,转载请注明出处:内核源码分析之进程调度机制,linux内核CFS进程调

    关键词: