精华内容
下载资源
问答
  • 基于公平调度的新能源调度方法.pdf
  • 完全公平调度算法

    2021-05-27 00:15:12
    Linux 进程管理之CFS调度器Linux 进程管理之CFS负载均衡1.算法介绍针对没有实时需求的普通进程,Linux内核使用完全公平调度器(Completely Fair Schedu...

    Linux 进程管理之CFS调度器

    Linux 进程管理之CFS负载均衡

    1.算法介绍

    针对没有实时需求的普通进程,Linux内核使用完全公平调度器(Completely Fair Scheduler,CFS)。普通进程的nice值(相对优先级,基准值是120)的取值范围是-20~19,值越小表示优先级越高,不同优先级的进程应该享受不同的待遇,优先级高的进程应该获得更多的处理器时间。为了兼顾进程优先级和公平性,完全公平调度算法引入了虚拟运行时间,如下。

    虚拟运行时间 = 实际运行时间 × nice 0对应的权重 / 进程的权重

    进程的权重是根据nice值转换得到的,nice值和权重的对应关系如下。

    kernel/sched/core.c
    const int sched_prio_to_weight[40] = {
     /* -20 */     88761,     71755,     56483,     46273,     36291,
     /* -15 */     29154,     23254,     18705,     14949,     11916,
     /* -10 */      9548,      7620,      6100,      4904,      3906,
     /*  -5 */      3121,      2501,      1991,      1586,      1277,
     /*   0 */      1024,       820,       655,       526,       423,
     /*   5 */       335,       272,       215,       172,       137,
     /*  10 */       110,        87,        70,        56,        45,
     /*  15 */        36,        29,        23,        18,        15,
    };
    

    nice 0对应的权重是1024,nice n-1的权重是nice n的权重的1.25倍,取1.25倍的依据是:假设系统只有两个进程,nice值相同,如果一个进程的nice值减一,优先级提升一级,那么在长度为T的时间段它获得的CPU时间是(T×55%),比另一个进程多(T×10%);如果一个进程的nice值加一,优先级降低一级,那么在长度为T的时间段它获得的CPU时间是(T×45%),比另一个进程少(T×10%)。举个例子,假设进程A和B的nice值都是0,两个进程的CPU份额都是50%,如果进程B的nice值加一,优先级降低一级,那么进程A的权重是1024,进程B的权重是1024/1.25≈820,进程A的CPU份额是1024/(1024+820)≈55%,进程B的CPU份额是820/(1024+820)≈45%,在长度为T的时间段进程B获得的CPU时间比进程A少(T×10%)。

    完全公平调度算法使用红黑树(一种平衡的二叉树)把进程按虚拟运行时间从小到大排序,每次调度时选择虚拟运行时间最小的进程。

    调度器选中进程以后分配的时间片是多少呢?

    调度周期:在某个时间长度可以保证运行队列中的每个进程至少运行一次,我们把这个时间长度称为调度周期。也称为调度延迟,因为一个进程等待被调度的延迟时间是一个调度周期。

    调度最小粒度:为了防止进程切换太频繁,进程被调度后应该至少运行一小段时间,我们把这个时间长度称为调度最小粒度。

    调度周期的默认值是6毫秒,调度最小粒度的默认值是0.75毫秒,如下所示,两者的单位都是纳秒。

    kernel/sched/fair.c
    unsigned int sysctl_sched_latency      = 6000000ULL;
    unsigned int sysctl_sched_min_granularity    = 750000ULL;
    

    如果运行队列中的进程数量太多,导致把调度周期sysctl_sched_latency平分给进程时的时间片小于调度最小粒度,那么调度周期取“调度最小粒度 × 进程数量”。

    进程的时间片的计算公式如下。

    进程的时间片 = 调度周期 × 进程的权重 / 运行队列中所有进程的权重总和

    按照这个公式计算出来的时间片称为理想的运行时间。可以看出,优先级高的进程的权重大,所以获得的时间片长。

    进程调度器周期性地检查当前进程的运行时间是否到达理想的运行时间,如果是,那么重新选择进程。

    在一个调度周期中,进程的虚拟运行时间 = 进程的时间片 × nice 0对应的权重 / 进程的权重

    = (调度周期 × 进程的权重 / 运行队列中所有进程的权重总和)× nice 0对应的权重 / 进程的权重

    = 调度周期 / 运行队列中所有进程的权重总和 × nice 0对应的权重

    =  调度周期 × nice 0对应的权重 / 运行队列中所有进程的权重总和

    可以看出,在每个调度周期中,优先级高的进程的实际运行时间多,但是每个进程的虚拟运行时间是相同的,所以完全公平调度算法的公平性体现在每个调度周期中给每个进程分配相同的虚拟运行时间。

    2.计算虚拟运行时间

    当进程调度器选中进程p的时候,使用p->se.exec_start记录开始运行时间,如下。

    __schedule() -> pick_next_task() -> class->pick_next_task() 
    -> pick_next_task_fair() -> set_next_entity() 
    -> update_stats_curr_start()
    
    
    kernel/sched/fair.c
    static inline void
    update_stats_curr_start(struct cfs_rq *cfs_rq, struct sched_entity *se)
    {
      se->exec_start = rq_clock_task(rq_of(cfs_rq));
    }
    

    从进程p切换到其他进程的时候,获取当前时间,减去进程p的开始运行时间,得到进程p的运行时间,然后把运行时间转换为虚拟运行时间,接着把虚拟运行时间累加到p->se.vruntime,如下。

    __schedule() -> pick_next_task() -> class->pick_next_task() 
    -> pick_next_task_fair() -> put_prev_task() 
    -> prev->sched_class->put_prev_task() -> put_prev_task_fair() 
    -> put_prev_entity() -> update_curr()
    
    
    kernel/sched/fair.c
    static void update_curr(struct cfs_rq *cfs_rq)
    {
      struct sched_entity *curr = cfs_rq->curr;
      u64 now = rq_clock_task(rq_of(cfs_rq));
      u64 delta_exec;
    
    
      ...
      delta_exec = now - curr->exec_start;
      ...
      curr->vruntime += calc_delta_fair(delta_exec, curr);
      ...
    }
    

    正常情况下进程的虚拟运行时间是一步一个脚印走出来的,但是有3种情况进程的虚拟运行时间是伪造的,以公平运行队列的最小虚拟运行时间为基础进行篡改。

    2.1.公平运行队列的最小虚拟运行时间

    每个处理器的公平运行队列维护一个min_vruntime字段,用来记录公平运行队列的最小虚拟运行时间,即公平运行队列中的所有进程的虚拟运行时间的最小值。这个字段的值是单调递增的。假设min_vruntime是公平运行队列中的所有进程的虚拟运行时间的最小值,那么cfs_rq.min_vruntime的计算方法如下,即取自己和min_vruntime的较大值。

    cfs_rq.min_vruntime = max(cfs_rq.min_vruntime, min_vruntime)

    在创建新进程、睡眠进程被唤醒和进程从一个处理器迁移到另一个处理器这些情况,会以公平运行队列的最小虚拟运行时间为基础设置进程的虚拟运行时间,使进程的虚拟运行时间和其他进程保持在合理的差距范围内。

    2.2.新进程的虚拟运行时间的初始值

    创建新进程,新进程的虚拟运行时间的初始值是多少?假如初始值是0,比老进程小很多,将会导致进程调度器在很长一段时间内总是选中它,老进程得不到调度,显然不合理。

    新进程的虚拟运行时间的初始值以它所在的公平运行队列的最小虚拟运行时间为基础设置,与老进程保持在合理的差距范围内。

    新进程的虚拟运行时间的初始值有两个控制参数,如下。

    (1)调度特性START_DEBIT:新进程的虚拟运行时间的初始值在公平运行队列的最小虚拟运行时间的基础上增加延迟时间,延迟时间是新进程自己的时间片,假装新进程在当前调度周期被调度过一次,因为当前调度周期已经承诺给公平运行队列中的所有进程,所以新进程应该不参加当前调度周期的调度。默认开启。这个调度特性是针对“某个进程持续调用fork()创建新进程”这种情况设计的,假设某个进程持续调用fork()创建了很多新进程,如果把新进程的虚拟运行时间的初始值设置为公平运行队列的最小虚拟运行时间,会导致已经在公平运行队列中的进程得不到调度。

    (2)sysctl_sched_child_runs_first:在调用fork()创建子进程以后,子进程先运行,父进程后运行。默认禁止,可以执行命令“echo 1 > /proc/sys/kernel/sched_child_runs_first”开启。

    新进程的虚拟运行时间的初始值的计算方法如下。

    (1)如果父进程属于公平调度类,那么把新进程的虚拟运行时间的初始值设置为父进程的虚拟运行时间。父进程是指调用函数fork()的进程,注意,父进程可能不属于公平调度类,可能是限期进程或者实时进程,如果父进程调用函数sched_setscheduler()设置调度策略时设置了标志SCHED_RESET_ON_FORK,那么创建的子进程使用默认的调度策略SCHED_NORMAL。

    (2)取公平运行队列的最小虚拟运行时间,如果开启了调度特性START_DEBIT,那么在这个基础上加上延迟时间。新进程的虚拟运行时间的初始值取第1步和第2步的较大值。

    (3)如果开启子进程先运行的特性,父进程属于公平调度类,并且父进程的虚拟运行时间小,那么交换父进程和子进程的虚拟运行时间。

    2.3.睡眠进程的虚拟运行时间

    进程睡眠一段时间,它的虚拟运行时间保持不变,其他进程在运行,它们的虚拟运行时间一直增加,当睡眠进程被唤醒的时候,它的虚拟运行时间和其他进程相比,可能有很大的差距,导致进程调度器在一段时间内总是选中它,其他进程得不到调度。

    当睡眠进程被唤醒的时候,重新设置它的虚拟运行时间,取下面两个时间的较大值。

    (1)进程开始睡眠时的虚拟运行时间。

    (2)以公平运行队列的最小虚拟运行时间为基础,给一定的补偿,补偿值是半个调度周期,即vruntime = cfs_rq.min_vruntime – (sysctl_sched_latency / 2)。调度特性GENTLE_FAIR_SLEEPERS用来控制睡眠进程的补偿值,默认开启,如果禁止,那么补偿值是一个调度周期,即vruntime = cfs_rq.min_vruntime - sysctl_sched_latency。

    根据上面的计算方法,可以得出下面的结论。

    (1)如果进程只是短暂睡眠,它的虚拟运行时间大于“cfs_rq.min_vruntime - 补偿值”,那么它的虚拟运行时间保持原样。

    (2)如果进程长时间睡眠,它的虚拟运行时间小于“cfs_rq.min_vruntime - 补偿值”,那么把它的虚拟运行时间修改为“cfs_rq.min_vruntime - 补偿值”。

    2.4.进程迁移

    在多处理器系统中,不同处理器的负载不同,负载重的处理器上进程多,每个进程的虚拟运行时间增加得慢,负载轻的处理器上进程少,每个进程的虚拟运行时间增加得快,假设处理器1有10个普通进程,处理器2只有1个普通进程,处理器1上每个进程的虚拟运行时间是处理器2上进程的虚拟运行时间的1/10。当从负载重的处理器迁移进程到负载轻的处理器的时候,迁移过来的进程的虚拟运行时间小很多,导致进程调度器在一段时间内总是选中它,对其他进程不公平。

    完全公平调度算法的解决方法如下。

    (1)当进程p退出一个处理器的公平运行队列的时候,把它的虚拟运行时间减去公平运行队列的最小虚拟运行时间,即p->se.vruntime = p->se.vruntime - cfs_rq.min_vruntime。

    (2)当进程p加入一个处理器的公平运行队列的时候,把它的虚拟运行时间加上公平运行队列的最小虚拟运行时间,即p->se.vruntime = p->se.vruntime + cfs_rq.min_vruntime。

    注意:当进程因为要睡眠而退出公平运行队列的时候,不会把它的虚拟运行时间减去公平运行队列的最小虚拟运行时间;当进程因为唤醒而加入公平运行队列的时候,不会把它的虚拟运行时间加上公平运行队列的最小虚拟运行时间。

    3.选择进程的算法

    完全公平调度算法通常选择虚拟运行时间最小的进程,但是选择算法还需要考虑下面的特殊情况。

    (1)刚唤醒或刚创建的进程抢占处理器,抢占的进程运行完以后把处理器归还给被抢占的进程,这样做的好处是利用缓存局部性原理,被抢占的进程的指令和数据可能还在处理器的缓存中。

    (2)某个处理器密集型的进程长时间占用处理器,良心发现,调用函数sched_yield()表示自愿让出处理器,调度器选择进程的时候应该跳过它。

    在公平运行队列的结构体中增加了下面这些成员。

    kernel/sched/sched.h
    struct cfs_rq {
      ...
      struct sched_entity *curr, *next, *last, *skip;
      ...
    };
    

    成员curr指向当前正在运行的调度实体。成员next指向要抢占处理器的调度实体,成员last指向被抢占的调度实体。成员skip指向自愿让出处理器的调度实体。

    综合所有情况,选择进程的算法如下(把虚拟运行时间最小的进程记为min)。

    (1)如果有进程要抢占处理器(即cfs_rq.next指向某个进程),并且它的虚拟运行时间和进程min的差值小于或等于限定值,那么选择要抢占处理器的进程。

    (2)在第1条规则不成立的情况下,如果存在被抢占的进程(即cfs_rq.last指向某个进程),并且它的虚拟运行时间和进程min的差值小于或等于限定值,那么选择被抢占的进程。

    (3)在前2条规则不成立的情况下,如果进程min自愿让出处理器(即cfs_rq.skip指向进程min),并且虚拟运行时间第二小的进程和它的差值小于或等于限定值,那么选择虚拟运行时间第二小的进程。

    (4)在前3条规则不成立的情况下,选择虚拟运行时间最小的进程min。

    前3条规则使用的限定值是“唤醒粒度 × nice 0对应的权重 / 进程min的权重”,即使用进程min的权重把唤醒粒度转换成的虚拟时间。

    唤醒粒度参数sysctl_sched_wakeup_granularity限定了刚唤醒的进程要抢占当前进程必须满足的条件:只有当刚唤醒进程的虚拟运行时间比当前进程小,并且差距大于“唤醒粒度× nice 0对应的权重 / 刚唤醒进程的权重”,才可以抢占。这个参数越大,发生唤醒抢占就越困难。唤醒粒度参数的默认值是1毫秒。

    4.参考文档

    (1)内核文档“Documentation/scheduler/sched-design-CFS.txt”

    展开全文
  • 无线传感器网络的比例公平调度
  • Linux 完全公平调度

    2019-09-26 14:37:49
    读书笔记,Linux 系统编程 第六章高级进程管理完全公平调度器Linux调度器为完全公平调度器,简称为CFS。和最近华为鸿蒙提出的确定时延调度相反。完全公平调度器和传统...

    读书笔记,Linux 系统编程 第六章高级进程管理

    完全公平调度器

    Linux调度器为完全公平调度器,简称为CFS。和最近华为鸿蒙提出的确定时延调度相反。

    完全公平调度器和传统的Unix调度器有很大的区别。在大多数Unix系统中,包括引入CFS之前的Linux系统,在进程调度中存在两个基本的基于进程的因素:优先级和时间片。在传统的进程调度器中,会给每个就绪进程分配一个时间片。进程可能会一直运行直到消耗完分配给它的时间片。调度器会给每个进程分配优先级。进程调度器会先调度优先级高的进程,再调度优先级低的进程。这个调度算法非常简单,而且对于早期的基于时间片共享的Unix系统效果良好。但对于交互和公平性的系统而言,如现代计算机的桌面和移动设备,该算法就有些差强人意了。

    完全公平调度器引入了一种不同的算法,成为公平调度,它消除了时间片作为处理器分配单元,而是给每个进程分配了处理器的分配比例。

    算法逻辑如下:CFS最初给N个进程分别分配1/N的处理器时间。然后CFS通过优先级(nice值)权衡每个进程的比例,调整分配。默认的优先级为0,权值是1,则比例不变。优先级的值设置的越小(优先级越高),权值就越高,就增加给该进程的处理器比例值;优先级的值设置的越高(优先级越低),权值越低,就减少分配给该进程的比例值。

    通过这种方式,CFS就基于权值分配给了每个进程处理器比例。要确定每个进程真正的执行时间,CFS需要把比例划分为一个固定周期,这个周期叫目标延迟,表示系统的调度延迟。

    举个例子:假设目标延迟设置为20ms,有两个优先级相同的进程,那每个进程分配到的时间就是10ms。这样CFS就会先执行一个进程运行10ms,再执行另一个进程运行10ms,不断反复。

    如果当前有200个进程怎么办,延迟如果是20ms,那每个进程只能分配到100微秒。这样大部分时间就会浪费在切换进程的上下文,CFS引入最小粒度解决这个问题。

    “最小粒度”是指任意进程运行时间的基准值。所有进程,不管分配到处理器的时间比例是多少,都会至少运行最小粒度的时间。这种机制可以保证不会大部分时间浪费在进程切换上。

    通过给进程分配处理器资源比例,而不是固定的时间片,CFS可以实现公平性。CFS也支持可配置的时间延迟。CFS中,进程按配额运行,时间片是根据系统可运行的进程数动态变化的,解决了传统调度器对于交互进程和IO约束性进程所面临的很多问题。

    展开全文
  • hadoop公平调度算法解析
  • 完全公平调度算法基本原理 完全公平调度的两个时间 完全公平调度的两个对象 完全公平调度算法实现 调度时机 Linux 进程调度算法经历了以下几个版本的发展: 基于时间片轮询调度算法。(2.6之前的版本) ...

    目录

    完全公平调度算法基本原理

    完全公平调度的两个时间

    完全公平调度的两个对象

    完全公平调度算法实现

    调度时机


     

    Linux 进程调度算法经历了以下几个版本的发展:

    • 基于时间片轮询调度算法。(2.6之前的版本)

    • O(1) 调度算法。(2.6.23之前的版本)

    • 完全公平调度算法。(2.6.23以及之后的版本)

    之前我写过一篇分析 O(1)调度算法 的文章:O(1)调度算法,而这篇主要分析 Linux 现在所使用的 完全公平调度算法

    分析 完全公平调度算法 前,我们先了解下 完全公平调度算法 的基本原理。

    完全公平调度算法基本原理

    完全公平调度算法 体现在对待每个进程都是公平的,那么怎么才能做到完全公平呢?有一个比较简单的方法就是:让每个进程都运行一段相同的时间片,这就是 基于时间片轮询调度算法。如下图所示:

    如上图所示,开始时进程1获得 time0 ~ time1 的CPU运行时间。当进程1的时间片使用完后,进程2获得 time1 ~ time2 的CPU运行时间。而当进程2的时间片使用完后,进程3获得 time2 ~ time3 的CPU运行时间。

    如此类推,由于每个时间片都是相等的,所以理论上每个进程都能获得相同的CPU运行时间。这个算法看起来很不错,但存在两个问题:

    • 不能按权重分配不同的运行时间,例如有些进程权重大的应该获得更多的运行时间。

    • 每次调度时都需要遍历运行队列中的所有进程,找到优先级最大的进程运行,时间复杂度为 O(n)。对于一个高性能的操作系统来说,这是不能接受的。

    为了解决上面两个问题,Linux内核的开发者创造了 完全公平调度算法

    完全公平调度的两个时间

    为了实现 完全公平调度算法,为进程定义两个时间:

    1. 实际运行时间:

    实际运行时间 = 调度周期 * 进程权重 / 所有进程权重之和

    • 调度周期:是指所有可进程运行一遍所需要的时间。

    • 进程权重:依据进程的重要性,分配给每个进程不同的权重。

    例如,调度周期为 30ms,进程A的权重为 1,而进程B的权重为 2。那么:

    进程A的实际运行时间为30ms * 1 / (1 + 2) = 10ms

    进程B的实际运行时间为30ms * 2 / (1 + 2) = 20ms

    1. 虚拟运行时间:

    虚拟运行时间 = 实际运行时间 * 1024 / 进程权重 = (调度周期 * 进程权重 / 所有进程权重之和) * 1024 / 进程权重 = 调度周期 * 1024 / 所有进程总权重

    从上面的公式可以看出,在一个调度周期里,所有进程的 虚拟运行时间 是相同的。所以在进程调度时,只需要找到 虚拟运行时间 最小的进程调度运行即可。

    为了能够快速找到 虚拟运行时间 最小的进程,Linux 内核使用 红黑树 来保存可运行的进程,而比较的键值就是进程的 虚拟运行时间

    如果不了解 红黑树 的话,可以把它看成一个自动排序的容器即可。如下图所示:

    如上图所示,红黑树 的左节点比父节点小,而右节点比父节点大。所以查找最小节点时,只需要获取 红黑树 的最左节点即可,时间复杂度为 O(logN)

    完全公平调度的两个对象

    Linux 内核为了实现 完全公平调度算法,定义两个对象:cfs_rq (可运行进程队列) 和 sched_entity (调度实体)

    • cfs_rq (可运行进程队列):使用 红黑树 结构来保存内核中可以运行的进程。

    • sched_entity (调度实体):可被内核调度的实体,如果忽略组调度(本文也不涉及组调度),可以把它当成是进程。

    cfs_rq 对象定义

    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;   // 缓存红黑树最左端节点, 用于快速获取最小节点
        ...
    };

    对于 cfs_rq 对象,现在主要关注的是 tasks_timeline 成员,其保存了可运行进程队列的根节点(红黑树的根节点)。

    sched_entity 对象定义

    struct sched_entity {
      struct load_weight  load;
      struct rb_node    run_node;         // 用于连接到运行队列的红黑树中
      struct list_head  group_node;
      unsigned int    on_rq;              // 是否已经在运行队列中
    
      u64          exec_start;            // 开始统计运行时间的时间点
      u64          sum_exec_runtime;      // 总共运行的实际时间
      u64          vruntime;              // 虚拟运行时间(用于红黑树的键值对比)
      u64          prev_sum_exec_runtime; // 总共运行的虚拟运行时间
    
      u64          last_wakeup;
      u64          avg_overlap;
        ...
    };

    对于 sched_entity 对象,现在主要关注的是 run_node 成员,其主要用于把调度实体连接到可运行进程的队列中。

    另外,进程描述符 task_struct 对象中,定义了一个类型为 sched_entity 的成员变量 se,如下:

    struct task_struct {
        ...
        struct sched_entity se;
        ...
    }

    所以,他们之间的关系如下图:

    从上图可以看出,所有 sched_entity 对象通过其 run_node 成员组成了一颗红黑树,这颗红黑树的根结点保存在 cfs_rq 对象的 task_timeline 成员中。

    另外,进程描述符 task_sturct 定义了一个类型为 sched_entity 的成员变量 se,所以通过进程描述符的 se 字段就可以把进程保存到可运行队列中。

    完全公平调度算法实现

    有了上面的基础,现在可以开始分析 Linux 内核中怎么实现 完全公平调度算法 了。

    我们先来看看怎么更新一个进程的虚拟运行时间。

    1. 更新进程虚拟运行时间

    更新一个进程的虚拟运行时间是通过 __update_curr() 函数完成的,其代码如下:

    /src/kernel/sched_fair.c

    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; // 增加进程总实际运行的时间
        delta_exec_weighted = delta_exec;     // 初始化进程使用的虚拟运行时间
    
        // 根据进程的权重计算其使用的虚拟运行时间
        if (unlikely(curr->load.weight != NICE_0_LOAD)) {
            delta_exec_weighted = calc_delta_fair(delta_exec_weighted, &curr->load);
        }
    
        curr->vruntime += delta_exec_weighted; // 更新进程的虚拟运行时间
    }

    __update_curr() 函数各个参数意义如下:

    • cfs_rq:可运行队列对象。

    • curr:当前进程调度实体。

    • delta_exec:实际运行的时间。

    __update_curr() 函数主要完成以下几个工作:

    1. 更新进程调度实体的总实际运行时间。

    2. 根据进程调度实体的权重值,计算其使用的虚拟运行时间。

    3. 把计算虚拟运行时间的结果添加到进程调度实体的 vruntime 字段。

    我们接着分析怎么把进程添加到运行队列中。

    2. 把进程调度实体添加到运行队列中

    要将进程调度实体添加到运行队列中,可以调用 __enqueue_entity() 函数,其实现如下:

    /src/kernel/sched_fair.c

    static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
    {
        struct rb_node **link = &cfs_rq->tasks_timeline.rb_node; // 红黑树根节点
        struct rb_node *parent = NULL;
        struct sched_entity *entry;
        s64 key = entity_key(cfs_rq, se); // 当前进程调度实体的虚拟运行时间
        int leftmost = 1;
    
        while (*link) { // 把当前调度实体插入到运行队列的红黑树中
            parent = *link;
            entry = rb_entry(parent, struct sched_entity, run_node);
    
            if (key < entity_key(cfs_rq, entry)) { // 比较虚拟运行时间
                link = &parent->rb_left;
            } else {
                link = &parent->rb_right;
                leftmost = 0;
            }
        }
    
        if (leftmost) {
            cfs_rq->rb_leftmost = &se->run_node; // 缓存红黑树最左节点
            cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, se->vruntime);
        }
    
        // 这里是红黑树的平衡过程(参考红黑树数据结构的实现)
        rb_link_node(&se->run_node, parent, link);
        rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
    }

    __enqueue_entity() 函数的主要工作如下:

    1. 获取运行队列红黑树的根节点。

    2. 获取当前进程调度实体的虚拟运行时间。

    3. 把当前进程调度实体添加到红黑树中(可参考红黑树的插入算法)。

    4. 缓存红黑树最左端节点。

    5. 对红黑树进行平衡操作(可参考红黑树的平衡算法)。

    调用 __enqueue_entity() 函数后,就可以把进程调度实体插入到运行队列的红黑树中。同时会把红黑树最左端的节点缓存到运行队列的 rb_leftmost 字段中,用于快速获取下一个可运行的进程。

    3. 从可运行队列中获取下一个可运行的进程

    要获取运行队列中下一个可运行的进程可以通过调用 __pick_next_entity() 函数,其实现如下:

    /src/kernel/sched_fair.c

    static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq)
    {
        // 1. 先调用 first_fair() 获取红黑树最左端节点
        // 2. 调用 rb_entry() 返回节点对应的调度实体
        return rb_entry(first_fair(cfs_rq), struct sched_entity, run_node);
    }
    
    static inline struct rb_node *first_fair(struct cfs_rq *cfs_rq)
    {
        return cfs_rq->rb_leftmost; // 获取红黑树最左端节点
    }

    前面介绍过,红黑树的最左端节点就是虚拟运行时间最少的进程,所以 __pick_next_entity() 函数的流程就非常简单了,如下:

    • 首先调用 first_fair() 获取红黑树最左端节点。

    • 然后再调用 rb_entry() 返回节点对应的调度实体。

    调度时机

    前面介绍了 完全公平调度算法 怎么向可运行队列添加调度的进程和怎么从可运行队列中获取下一个可运行的进程,那么 Linux 内核在什么时候会进行进程调度呢?

    答案是由 Linux 内核的时钟中断触发的。

    时钟中断 是什么?如果没了解过操作系统原理的可能不了解这个东西,但是没关系,不了解可以把他当成是定时器就好了,就是每隔固定一段时间会调用一个回调函数来处理这个事件。

    时钟中断 犹如 Linux 的心脏一样,每隔一定的时间就会触发调用 scheduler_tick() 函数,其实现如下:

    void scheduler_tick(void)
    {
        ...
        curr->sched_class->task_tick(rq, curr, 0); // 这里会调用 task_tick_fair() 函数
        ...
    }

    scheduler_tick() 函数会调用 task_tick_fair() 函数处理调度相关的工作,而 task_tick_fair() 主要通过调用 entity_tick() 来完成调度工作的,调用链如下:

    scheduler_tick() -> task_tick_fair() -> entity_tick()

    entity_tick() 函数实现如下:

    static void
    entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
    {
        // 更新当前进程的虚拟运行时间
        update_curr(cfs_rq);
        ...
        if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
            check_preempt_tick(cfs_rq, curr); // 判断是否需要进行进程调度
    }

    entity_tick() 函数主要完成以下工作:

    • 调用 update_curr() 函数更新进程的虚拟运行时间,这个前面已经介绍过。

    • 调用 check_preempt_tick() 函数判断是否需要进行进程调度。

    我们接着分析 check_preempt_tick() 函数的实现:

    static void
    check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
    {
        unsigned long ideal_runtime, delta_exec;
    
        // 计算当前进程可用的时间片
        ideal_runtime = sched_slice(cfs_rq, curr); 
    
        // 进程运行的实际时间
        delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
    
        // 如果进程运行的实际时间大于其可用时间片, 那么进行调度
        if (delta_exec > ideal_runtime)
            resched_task(rq_of(cfs_rq)->curr);
    }

    check_preempt_tick() 函数主要完成以下工作:

    • 通过调用 sched_slice() 计算当前进程可用的时间片。

    • 获取当前进程在当前调度周期实际已运行的时间。

    • 如果进程实际运行的时间大于其可用时间片, 那么调用 resched_task() 函数进行进程调度。

    可以看出,在 时钟中断 的处理中,有可能会进行进程调度。除了 时钟中断 外,一些主动让出 CPU 的操作也会进行进程调度(如一些 I/O 操作),这里就不详细分析了,有兴趣可以自己研究。

     

    展开全文
  • 公平调度器配置

    2019-07-24 18:21:38
    公平调度器主要为所有运行的应用公平分配资源。 设想一个场景:假设有 A和B两个用户,分别拥有自己的队列。 A启动一个作业,在B没有需求时A会分配到全部可用的资源; 当A的作业仍在运行时B启动了一个作业,一段...

    概念

    公平调度器主要为所有运行的应用公平分配资源。

    设想一个场景:假设有 A和B两个用户,分别拥有自己的队列。

    • A启动一个作业,在B没有需求时A会分配到全部可用的资源;
    • 当A的作业仍在运行时B启动了一个作业,一段时间后,按照我们之前看到的方式,每个作业都用到了一半的集群资源。这时如果B启动第二个作业并且其它作业仍在运行,那么第二个作业将和B的其它作业共享资源,因此B的每个作业将占用四分之一的集群资源,而A仍继续占用一半的集群资源。最终的结果就是资源在用户之间实现了公平共享。

    启用公平调度器

    公平调度器的使用属性为yarn.resourcemanager.scheduler.class。默认是使用容量调度器。但在Hadoop分布式项目中,CDH默认使用的是公平调度器。
    如果要使用公平调度器,需要将yarn-site.xml文件中的yarn.resourcemanager.scheduler.class设置为公平调度器的完全限定名:org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler

    队列配置

    通过一个名为fair-scheduler.xml的分配文件对公平调度器进行配置,该文件位于类路径下。可以通过yarn.scheduler.fair.allocation.file来修改文件名。当没有该分配文件时,公平调度器的工作策略同先前描述的一样,每个应用放置在一个以用户名命名的队列中,队列是在用户提交第一个应用时动态创建的。

    通过分配文件可以为每个队列进行配置,这样可以对容量调度器支持的层次队列进行配置。

    root
     --prod
     --dev
        --eng
        --science

    假设还是这个队列结构,配置文件示例:

    <?xml version="1.0"?>
    <allocations>
        <defaultQueueSchedulingPolicy>fair</defaultQueueSchedulingPolicy>
    
        <queue name="prod">
            <weight>40</weight>
            <schedulingPolicy>fifo</schedulingPolicy>
        </queue>
    
        <queue name="dev">
            <weight>60</weight>
            <queue name="eng"/>
            <queue name="science"/>
        </queue>
    
        <queuePlacementPolicy>
            <rule name="specified" create="false"/>
            <rule name="primaryGroup" create="false"/>
            <rule name="default" queue="dev.eng"/>
        </queuePlacementPolicy>
    </allocations>

    队列的层次使用嵌套queue元素来定义。所有的队列都是root队列的孩子,即使实际上并没有把root queue嵌套进来。这里的dev队列划分成eng和science两个队列。

    队列中有权重元素,用于公平共享计算。在上面的配置文件中,当集群资源按照40:60的比例分配给prod和dev时,集群分配被认为是公平的。eng和science队列没有指定权重,因此它们会被平均分配。权重并不是百分百,例子中时为了简单起见使用了相加之和为100的两个数字。也可以为prod和dev队列分别指定2和3的权重,在效果上是一样的。

    当设置权重时,记住要考虑默认队列和动态创建的队列(例如以用户名命名的队列)。虽然没有在分配文件中为它们指定权重,但它们仍有值为1的权重。

    每个队列可以有不同的调度策略。队列的默认调度策略可以通过顶层元素defaultQueueSchdulingPolicy进行设置,如果省略,表示使用公平调度。尽管名称是“公平”,公平调度器也支持队列级别的FIFO调度,以及DRF调度(点击链接进入详解)。DRF全称为Dominant Resource Fairness

    队列的调度策略可以被该队列schedulingPolicy元素指定的策略覆盖。在上面的配置文件中,由于我们希望每个作业能够顺序进行并在最短可能的时间内完成,所以prod队列使用了FIFO调度策略。值得注意的是,在prod和dev队列之间、eng和science队列之间及内部划分资源仍然使用了公平调度。

    尽管上述的分配文件中没有展示,每个队列仍可配置最大和最小资源数量,及最大可运行的应用的数量。最小资源不是一个硬性的限制,但是调度器常用它对资源分配进行优先排序。如果两个队列的资源都低于它们的公平共享额度,那么远低于最小资源数量的那个队列优先被分配资源。最小资源数量也会用于抢占行为(见下方)。

    队列放置

    公平调度器使用一个基于规则的系统来确定应用应该放到哪个队列。在上述配置文件中,queuePlacementPolicy元素包含了一个规则列表,每条规则会被依次尝试直到匹配成功。第一条规则,specified表示把应用放进所指明的队列中,如果没有指明,或如果所指的队列不存在,则规则不匹配,继续尝试下一条规则。primaryGroup规则会试着把应用放在以用户的主Unix组名命名的队列中,如果没有这样的队列,则继续尝试下一条规则而不是创建队列。Default规则是一条兜底规则,当前规则都不匹配时,将启用该条规则,把应用放进dev.eng队列中。

    当然可以完全省略queuePlacementPolicy元素,此时队列放置默认遵从如下规则:

    <queuePlacementPolicy>
        <rule name="specified" />
        <rule name="user" />
    </queuePlacementPolicy>

    除非明确定义队列,否则必要时会以用户名为队列名创建队列。

    另一个简单的队列放置策略是,将所有的应用放进同一个队列中(default)。这样可以在应用之间公平共享资源,而不是在用户之间共享。策略定义等价于以下规则:

    <queuePlacementPolicy>
        <rule name="default" />
    </queuePlacementPolicy>

    不使用分配文件也可以设置以上策略,通过将属性yarn.scheduler.fair.user-as-default-queue设为false,应用就会被放入default队列,而不是各个用户的队列。另外,将属性yarn.scheduler.fair.allow-undeclared-pools设置为false,用户边不能随意创建队列了。

    抢占

    在一个繁忙的集群中,当作业被提交给一个空队列时,作业不会立即启动,直到集群上已经运行的作业被释放了资源。为了使作业从提交到执行所需的时间可测,公平调度器支持“抢占”功能。

    所谓抢占,就是允许调度器中止那些占用资源超过了其公平共享份额的队列的容器,这些容器资源释放后可以分配给资源数量低于应得份额的队列。注意,抢占会降低整个集群的效率,因为被中止的容器需要重新执行。

    通过将yarn.scheduler.fair.preemption设置为true,可以全面启用抢占功能。有两个相关的抢占超时设置:一个用于最小共享,另一个用于公平共享,两者设定时间均为秒级。默认情况下,两个超时参数均不设置。所以为了允许抢占容器,需要至少设置其中一个参数。

    如果队列在“最小共享”设置指定的时间未获得被承诺的最小共享资源,调度器就会抢占其他容器。可以通过配置文件中的顶层元素defaultMinSharePreemptionTimeout为所有队列设置的默认超时时间,还可以通过设置每个队列的minSharePreemptionTimeout元素来为单个队列指定超时时间。

    类似,如果队列在“公平共享”设置指定的时间内未获得的资源仍然小于其公平共享份额的一半,那么调度器就会抢占其它容器。可以通过配置文件中的顶层元素defaultFairSharePreemptionTimeout为所有队列设置默认的超时时间,还可以通过设置每个队列的fairSharePreemptionTimeout元素来为单个队列指定超时时间。通过设置defaultFairSharePreemptionThreshold和fairShrePreemptionThreshold(针对每个队列)可以修改超时阈值,默认值为0.5

     

    资源扩展,点击下方链接:

    延时调度的配置

    展开全文
  • 公平到设备:用于GC的,可识别GC的SSD公平调度程序
  • Linux的公平调度

    2019-07-11 11:46:29
    Linux的公平调度(CFS)原理 https://www.jianshu.com/p/673c9e4817a8 1、CFS的基本思路 在CFS算法引入之前,Linux使用过几种不同的调度算法,一开始的调度器是复杂度为O(n)的始调度算法(实际上每次会遍历所有...
  • 主资源公平调度策略是目前调度系统中被广泛使用的一种调度策略,已经被证明在提高集群资源利用率和任务完成质量方面有很重要的影响,其是公平调度策略的一种,也是最大最小算法的一个具体体现。Mesos和YARN中都用到...
  • 公平调度是以pool为单位分配任务slots的,容量调度以queue的方式分配tasktracker的,当都只有一个job的时候,两种调度器都可以利用整个集群的资源,在每个pool内部可以是以FIFO方式调度也可以是公平方式调度,但是在...
  • 公平调度是一种将资源分配给应用程序的方法,以便所有应用程序在一段时间内平均获得相等的资源份额。Hadoop NextGen能够调度多种资源类型。在默认情况下,公平调度程序仅基于内存调度公平决策。它可以配置为使用内存...
  • 当前的Linux中有公平调度、实时(rt)调度、deadline调度等等调度算法;其中公平调度是目前最主要的调度算法之一。 公平调度,顾名思义就是要保证公平性,要照顾到所有任务都能够有机会得到CPU调度资源。Linux中有几...
  • Linux内核完全公平调度器改进的研究.pdf
  • 交通信号的实时调度是改善交通拥堵的...最小最大公平调度表现出最好的公平性,但在网络高密度下平均时延表现较差;比例公平调度则在各种交通密度下同时表现出较低的平均时延和较好的公平性。研究结果为实时交通信号的
  • 双极化多波束移动卫星通信的比例公平调度
  • Fair Schedule 先上官网链接:... 本帖基本来自官方文档~细改了翻译,字有点多,多为文字描述= =慢慢看 另一篇:YARN 三大调度器 之 Capacity Schedule 容器调度器 概述 公平调度是一种为...
  • Linux内核完全公平调度器的分析及模拟.pdf
  • Hadoop公平调度器延迟调度算法延迟间隔的选择,张博钰,方维,目前,Hadoop分布式计算框架在各大互联网企业中被广泛的应用。多用户共享集群是Hadoop应用的典型场景,其中如何在保证用户作业服务质
  • 面向云计算多租户的主模块公平调度算法.pdf
  • 比例公平调度matlab源程序

    热门讨论 2011-04-12 19:11:50
    比例公平调度matlab源程序,按照Goldsmith的无线通信上的算法编程。也可以嵌入到OFDMA资源管理的算法中。 该程序有详细注释。程序函数名称为PFS1_08aug18.m
  • 完全公平调度器 Linux调度器为完全公平调度器,简称为CFS。和最近华为鸿蒙提出的确定时延调度相反。 完全公平调度器和传统的Unix调度器有很大的区别。在大多数Unix系统中,包括引入CFS之前的Linux系统,在进程调度...
  • Hadoop框架中基于缺额的公平调度算法以统一的固定配置设置定时计算和更新作业信息,在一定程度上影响了其作业调度的公平性,同时也不能满足作业的资源需求。针对基于缺额的公平调度算法配置方式的不足,提出一种基于...
  • clj-queue-by:按键公平调度的队列
  • Hadoop-0.20.2公平调度器算法解析

    千次阅读 2017-11-01 18:08:07
    1. 目的 本文描述了hadoop中的公平调度的实现算法,公平调度器是由facebook贡献的,适合于多用户共享集群的环境的...本文组织结构如下:1)目的 2)公平调度介绍 3)公平调度算法分析 4)新版hadoop中公平调度
  • Hadoop集群公平调度算法的改进,张晓莉,谷利泽,对于基于特定系统和应用建立的Hadoop集群,任务的作业优先级别有显著差异。此时原有的公平调度算法并不能很好地利用资源并完成相应
  • spark的task调度器(FAIR公平调度算法)

    千次阅读 2016-02-01 16:19:49
    spark源码分析,spark的任务调度之fair公平调度算法代码实现分析.
  • 基于IAFSA-SFLA的新能源电站公平调度方法研究.pdf

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 37,989
精华内容 15,195
关键字:

公平调度