周期性调度器由scheduler_tick()函数实现,在每个时钟中断中都会调用该函数来更新一些统计量,并且会激活当前进程所属调度类的周期性处理接口,代码流程如下所示:

具体来说,scheduler_tick()做了以下工作:

1)更新就绪队列的实际时钟时间,不是虚拟时钟时间。

2)更新就绪队列权重数组cpu_load中的权重值

3)调用当前CPU上正在执行的进程所属调度类的task_tick接口,更新调度类相关的统计信息,并检查是否需要重新调度

4)如果是多处理器系统,检查当前CPU是否处于IDLE状态,并调用trigger_load_balance()来检查是否需要对CPU之间的负载进行均衡,如果需要,则触发SCHEDULE_SOFTIRQ软中断来迁移进程。

下面来看一看内核中是如何来完成这些操作的。

1.就绪队列时钟的更新

就绪队列的时钟由update_rq_clock()函数来更新,如果在编译内核的时候启用了CONFIG_HAVE_UNSTABLE_SCHED_CLOCK选项(CentOS的内核是默认开启的),sched_clock_stable变量的值为1,这种情况下会调用sched_clock()函数来获取当前的CPU时间。sched_clock()函数中会调用rdstc指令来读取CPU的周期数,然后调用__cycles_2_ns()将周期数转换为纳秒。rdstc指令在多处理器下会有问题,关于这个问题的描述和解决办法,参见 《多核时代不宜再用 x86 的 RDTSC 指令测试指令周期和时间》 ,较新的内核中已使用同步算法来fix这个问题。

2.权重数组的更新

就绪队列中的cpu_load数组用来跟踪此前的CPU负荷状态,在sched_init()中初始化为0,在CPU间迁移进程时会用到这个数组中记录的权重。cpu_load数组由update_cpu_load()函数更新,在每个tick周期里都会调用该函数,代码如下所示:

static void update_cpu_load( struct rq * this_rq) 

    unsigned long this_load = this_rq - > load.weight; 
    int i, scale; 
    this_rq - > nr_load_updates ++ ; 
    /* Update our load: */ 
    for (i = 0 , scale = 1 ; i < CPU_LOAD_IDX_MAX; i ++ , scale += scale) { 
        unsigned long old_load, new_load; 
        /* scale is effectively 1 << i now, and >> i divides by scale */ 
        old_load = this_rq - > cpu_load[i]; 
        new_load = this_load; 
        /* 
         * Round up the averaging division if load is increasing. This 
         * prevents us from getting stuck on 9 if the load is 10, for 
         * example. 
         */ 
        if (new_load > old_load) 
            new_load += scale - 1 ; 
        this_rq - > cpu_load[i] = (old_load * (scale - 1 ) + new_load) >> i; 
    } 
    if (time_after_eq(jiffies, this_rq - > calc_load_update)) { 
        this_rq - > calc_load_update += LOAD_FREQ; 
        calc_load_account_active(this_rq); 
    } 
}

在向就绪队列添加调度实体时,都会将调度实体的权重值添加到就绪队列的当前负荷的统计成员load中。在这里,this_load保存了当前就绪队列中的权重负荷。在更新cpu_load数组前要累加nr_load_updates成员,该成员记录了更新cpu_load数组的次数,只在输出/proc/sched_debug文件时使用,用于调试调度系统。

在for循环中,根据老的权重值和当前的权重值来进行更新。为了便于理解,我们可以进行下面的转换:

this_rq - > cpu_load[i] = (old_load * (scale - 1 ) + new_load) >> i 
                          = (old_load * ( 2 ^ i - 1 ) + new_load) / ( 2 ^ i) 
                          = old_load * ( 1 - 1 / ( 2 ^ i)) + new_load / ( 2 ^ i) 
                          = old_load + (new_load - old_load) / ( 2 ^ i)

通过转换,我们可以清晰地看到cpu_load数组中的元素是如何更新的。如果i等于0,则2^i的值为1,所以this_rq->cpu_load[0]保存的就是更新时当前就绪队列的权重值。如果当前队列的权重值是增加的,会将new_load(保存的就是当前的权重值)加上(scale-1),向上取整。

update_cpu_load()中除了更新cpu_load数组的内容后,还会检查是否要更新系统平均负载的统计信息,这些信息每隔5秒钟才更新一次,主要是统计在系统中处于active状态的进程的个数,包括进程状态是TASK_RUNNING和TASK_UNINTERRUPTIBLE的进程。系统的平均负载可以通过top或w命令查看。

3.CFS的周期性处理

CFS调度系统的调度类实例由全局变量fair_sched_class表示,设置的周期性处理接口是task_tick_fair()。在2.6.24中引入了组调度的概念,所以在task_tick_fair()中通过for_each_sched_entity宏来遍历处理当前进程所在的调度层级。这里为了简化,我们假设当前进程的parent为NULL,即当前进程处在就绪队列中的红黑树中。对当前进程的周期性处理主要由entity_tick()函数来完成,主要代码流程如下所示:

static void 
entity_tick( struct cfs_rq * cfs_rq, struct sched_entity * curr, int queued) 

    /* 
     * Update run-time statistics of the 'current'. 
     */ 
    update_curr(cfs_rq); 
...... 
    if (cfs_rq - > nr_running > 1 || ! sched_feat(WAKEUP_PREEMPT)) 
        check_preempt_tick(cfs_rq, curr); 
}

update_curr()根据现在的实际时钟时间和进程的权重计算本次运行的虚拟时钟时间,并更新进程和CFS就绪队列相关的统计信息。update_curr()使用CPU就绪队列的实际时钟时间减去当前进程的开始运行时间(由sched_entity结构的exec_start成员描述),得到当前进程实际的运行时间,然后调用__update_curr()来将实际的运行时间转换为虚拟时钟时间,并且加到当前进程总的运行的虚拟时钟时间(由sched_entity的sum_exec_runtime成员描述)。实际时钟时间和虚拟时钟时间的转换公式为:

在update_curr()中更新当前进程的虚拟运行时间后,需要重新计算CFS就绪队列中最小的虚拟运行时间。假设cfs_rq(结构类型为cfs_rq)为当前CPU就绪队列中的CFS就绪队列,最小的虚拟运行时间在CFS就绪队列当前的最小运行时间(即cfs_rq->min_vruntime)、正在执行的进程的虚拟运行时间(即cfs_rq->curr->vruntime,更新后的值)和CFS就绪队列中最左边节点(管理调度实体的红黑树中最左边的节点)的虚拟运行时间(cfs_rq->leftmost->vruntime)这三个值之间选择一个最小值。如果选出来的值大于当前CFS就绪队列的最小虚拟运行时间,则使用选出来的值来作为新的最小虚拟运行时间,并设置到cfs_rq->  min_vruntime上,否则维持原来的值不变。通过这样的策略,可以保证CFS就绪队列中的最小虚拟运行时间总是单调递增的,防止时钟倒流。由于最小虚拟运行时间总是单调递增的,所以就绪队列中最左边节点的运行时间有可能小于cfs_rq->min_vruntime。

完成更新操作后,检查CFS就绪队列中可运行进程的数目是否大于1,如果大于1,则调用check_preempt_tick()检查是否要重新调度正在执行的进程,检查主要分以下几个步骤:

1)调用sched_slice()计算当前进程在调度延迟内期望的运行时间。如果系统中可运行进程的数量小于sched_nr_latency(其值为 sysctl_sched_latency/ sysctl_sched_min_granularity ),调度延迟由系统参数sysctl_sched_latency的值确定;如果可运行进程的数量大于sched_nr_latency,调度延迟的值为( sysctl_sched_latency * (nr_running / sched_nr_latency)),其中nr_running为CFS就绪队列中可运行进程的数量。而当前进程在调度延迟中分得的时间(实际时钟时间)根据下面的公式来计算(period为调度延迟,weight为当前进程的权重,cfs_rq->load.weight为CFS就绪队列的权重):

2)如果当前进程本次已经执行的时间(实际时钟时间)超过期望的运行时间,说明当前进程运行的时间已经足够了,这种情况下要重新调度当前进程,并调用clear_buddies()确保当前进程不在CFS就绪队列中的next或last中,避免当前进程在下次选择执行进程时又被优先调度到。

3)如果当前进程本次已经执行的时间小于进程的最小运行时间(保存在sysctl_sched_min_granularity中),也不能重新调度,避免进程切换的太过频繁。

4)如果就绪队列中可运行进程的数量超过1,比较当前进程和就绪队列中最左边进程的运行时间来确定是否要重新调度。如果当前进程的虚拟运行时间减去就绪队列中最左边进程的虚拟运行时间的差值大于当前进程的期望运行时间,则重新调度当前进程。

4.负载均衡

多处理器系统中,内核必须要保证不同CPU间的负载要均衡,避免某些CPU的负载已经很高了,而某些CPU却很空闲,充分利用CPU资源。但是,迁移进程会导致CPU高速缓存失效,严重影响性能,所以在创建进程时,内核已经开始在CPU间负载均衡。

在SMP系统上,周期性调度器函数scheduler_tick()完成前面的任务后,会调用 trigger_load_balance()来检查是否要发起CPU间的负载均衡。这里我们不考虑启用动态时钟(即设置了CONFIG_NO_HZ选项)下的处理。如果当前的时间已经超过就绪队列中保存的下次均衡的时间,并且当前CPU在某个调度域内,则触发SCHED_SOFTIRQ软中断,适当的时候内核会调用run_rebalance_domains()函数在CPU间进行负载均衡。

调度域是一个CPU的集合,是负载均衡的单位,进程的迁移是在调度域内各CPU间进行,所以只有在当前CPU属于某个调度域的时候才发起进程迁移。通过调度域可以将邻近或共享高速缓存的CPU群集起来,优先选择在这些CPU之间迁移进程,这样可以降低迁移进程导致的性能损失。在普通的SMP系统上,所有的处理器都包含在一个调度域中。

Linux CFS调度系统----周期性调度器相关推荐

  1. AGV调度、AGV调度系统、AGV小车调度、AGV小车调度系统、搬运小车调度、搬运小车调度系统、slam车调度、slam调度系统、调度、调度系统

    AGV调度.AGV调度系统.AGV小车调度.AGV小车调度系统.搬运小车调度.搬运小车调度系统.slam车调度.slam调度系统.调度.调度系统. 1.产品介绍:一套标准的.开源的AGV调度系 统,适 ...

  2. Linux桌面GUI系统的调度器应该怎么做才不卡顿呢?

    承接前面两篇文章,继续这个话题: 为什么Linux CFS调度器没有带来惊艳的碾压效果 https://blog.csdn.net/dog250/article/details/95729830 为什 ...

  3. Linux进程调度器概述--Linux进程的管理与调度(十五)

    日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度 ...

  4. 工作流调度系统介绍,常见工作流调度系统对比,azkaban与Oozie对比,Azkaban介绍与特性(来自学习笔记)

    1. 工作流调度器azkaban 1.1 概述 1.1.1为什么需要工作流调度系统 一个完整的数据分析系统通常都是由大量任务单元组成:shell脚本程序,java程序,mapreduce程序.hive ...

  5. 美团点评容器平台HULK的调度系统

    本文是美团点评基础架构系列文章之一.这个系列将全面介绍支撑数亿用户.超千万日订单的美团点评平台诸多业务的公共基础架构相关技术.系列已经发布的文章包括: - <分布式会话跟踪系统架构设计与实践&g ...

  6. 为什么需要工作流调度系统?

    写在前面 本文隶属于专栏<100个问题搞定大数据理论体系>,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定大数据理 ...

  7. Kubernetes — 调度系统

    目录 文章目录 目录 调度系统 Kubernetes 调度器的设计 Kubernetes 调度器的工作流 Kubernetes 调度系统的未来 Scheduler Extender(调度器扩展) Mu ...

  8. 面向大数据处理应用的广域存算协同调度系统

    点击上方蓝字关注我们 面向大数据处理应用的广域存算协同调度系统 张晨浩1,2, 肖利民1,2, 秦广军3, 宋尧1,2, 蒋世轩1,2, 王继业4 1 软件开发环境国家重点实验室,北京 100191 ...

  9. 进程调度程序模拟_通用设计:指挥调度系统——复杂行动的大脑与神经

    指挥与调度系统能大幅提高行动的整体性,协调性,并能根据提前推演出的预案解决实际行动时决策困难的问题.本篇文章分享指挥调度系统的设计方案与模型处理步骤,带领我们更具体地了解指挥调度系统的组成设计. 背景 ...

  10. 华脉智联可视化指挥调度系统

    华脉智联可视化指挥调度系统,多端一体化综合智能指挥调度平台,可以实现对各级人员.设备.系统统一指挥,应急调度,为行业应用提供可视化智能指挥调度系统解决方案. 1.可视化指挥调度系统介绍 可视化指挥调度 ...

最新文章

  1. JupyterLab Server 搭建与使用笔记
  2. 《信息与电脑》流通业在SaaS模式下的在线应用
  3. anaconda降级python失败_如何降级Python版本安装spyder?
  4. Verilog中Dump函数及用法
  5. 混合模式单点登录的实现
  6. 计算科学如何帮助中国制造业成为全球领导者?李开复余承东胡郁等14位产学研大佬这样说|2021 CCF CTO高峰论坛...
  7. 开发日记-20190705 关键词 读书笔记 《Perl语言入门》Day 2
  8. 金城教授:基于手绘草图的图像检索技术研究
  9. Linux vim 快捷键
  10. 智能运维监管系统终端_什么系统能实现机房智能运维?
  11. 硬件基础知识---(5)电阻的用法
  12. [JAVA基础]你知道Java的四种引用类型吗
  13. 【codevs1034】家园——网络流
  14. Mipmap与可绘制文件夹[重复]
  15. 使用 ExMerge.exe 工具从邮箱中删除感染病毒的邮件
  16. 乐视入股酷派,手机圈全面战争已开打
  17. Kail linux中无法定位软件包
  18. android照片同步到另一部手机,怎样可以把以前手机里的照片导入另一个手机?...
  19. 小米6不显示与电脑连接到服务器,小米6怎么连接不上电脑没反应怎么办
  20. 移动支付的方式有哪些拾方易告诉你

热门文章

  1. Android Hessian 通信
  2. 并联机构工作空间求解_断路器机构弹簧的设计
  3. 自定义RPC通讯框架,实现dubbo远程RPC服务治理功能
  4. HBase 写优化之 BulkLoad 实现数据快速入库
  5. 获得PMP认证的过程及心路历程
  6. MATLAB plot画线的颜色设定
  7. Java实时读取日志文件
  8. springMVC常见问题
  9. python BeautifulSoup对应关系
  10. ES6 关于Set对象