• 进程管理--CFS调度器(1)


    介绍

    CFS(Completely Fair Scheduler,完全公平调度器)用于Linux系统中普通进程的调度。它给cfs_rq(cfs的run queue)中的每一个进程设置一个虚拟时钟,vruntime。如果一个进程得以执行,随着时间的增长(一个个tick的到来),其vruntime将不断增大。没有得到执行的进程vruntime不变。调度器总是选择vruntime跑得最慢的那个进程来执行。这就是所谓的“完全公平”。为了区别不同优先级的进程,优先级高的进程vruntime增长得慢,以至于它可能得到更多的运行机会。

    CFS不区分具体的cpu算力消耗型进程,还是io消耗型进程,统一采用红黑树算法来管理所有的调度实体sched_entity,算法效率为O(log(n))。每个进程都由一个struct task_struct来表示,为什么这里又定义了一个sched_entity?这是由于调度需要一些更详细的信息,比如当前运行时间或上次运行时间,因此就搞出了一个sched_entity的概念,为了存储一些调度相关的信息,给调度器用。CFS跟踪调度实体sched_entity的虚拟运行时间vruntime,平等对待运行队列中的调度实体sched_entity,将执行时间少的调度实体sched_entity排列到红黑树的最左边。调度实体sched_entity通过enqueue_entity()和dequeue_entity()来进行红黑树的出队入队。

    相关结构体

    总览

    • struct sched_entity:调度实体,这个也是CFS调度管理的对象
    • struct cfs_rq:CFS运行队列,该结构中包含了struct rb_root_cached红黑树,用于链接调度实体struct sched_entity。rq运行队列中对应了一个CFS运行队列,此外,在task_group结构中也会为每个CPU再维护一个CFS运行队列。
    • struct task_group:组调度,Linux支持将任务分组来对CPU资源进行分配管理,该结构中为系统中的每个CPU都分配了struct sched_entity调度实体和struct cfs_rq运行队列,其中struct sched_entity用于参与CFS的调度
    1. //红黑树
    2. struct rb_root_cached {
    3. struct rb_root rb_root; //红黑树的根节点
    4. struct rb_node *rb_leftmost; //红黑树中最左边节点
    5. };
    6. //cfs就绪队列
    7. struct cfs_rq {
    8. struct load_weight load;
    9. unsigned int nr_running; //就绪队列上调度实体的个数
    10. u64 min_vruntime; //就绪队列上所有调度实体中的最小虚拟时间
    11. /*用于跟踪调度实体按虚拟时间大小排序的红黑树的信息
    12. (包含红黑树的根以及红黑树中最左边节点)*/
    13. struct rb_root_cached tasks_timeline;
    14. };
    1. struct sched_entity {
    2. struct load_weight load; //权重信息,在计算虚拟时间的时候会用到其中的inv_weight成员
    3. /*CFS调度器的每个就绪队列维护了一颗红黑树,上面挂满了就绪等待执行的task
    4. run_node就是挂载点*/
    5. struct rb_node run_node;
    6. //调度实体se加入就绪队列后,on_rq置1。从就绪队列删除后,on_rq置0
    7. unsigned int on_rq;
    8. //调度实体已经运行实际时间总合
    9. u64 sum_exec_runtime;
    10. //调度实体已经运行的虚拟时间总合
    11. u64 vruntime;
    12. };

    调度类算法

    1. struct sched_class {
    2. const struct sched_class *next;
    3. void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
    4. void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
    5. struct task_struct * (*pick_next_task)(struct rq *rq,
    6. struct task_struct *prev,
    7. struct rq_flags *rf);
    8. void (*yield_task) (struct rq *rq);
    9. bool (*yield_to_task)(struct rq *rq, struct task_struct *p, bool preempt);
    10. void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);

    1. next:next成员指向下一个调度类(比自己低一个优先级)。在Linux中,每一个调度类都是有明确的优先级关系,高优先级调度类管理的进程会优先获得cpu使用权。
    2. enqueue_task:向该调度器管理的runqueue中添加一个进程。我们把这个操作称为入队。
    3. dequeue_task:向该调度器管理的runqueue中删除一个进程。我们把这个操作称为出队。
    4. check_preempt_curr:当一个进程被唤醒或者创建的时候,需要检查当前进程是否可以抢占当前cpu上正在运行的进程,如果可以抢占需要标记TIF_NEED_RESCHED flag。
    5. pick_next_task:通过next指针便利调度类链表,按照优先级顺序选择下一个最适合调度运行的任务,将其从rq移除,。

    现在我们总结一下。Linux中所有的进程使用task_struct描述。task_struct包含很多进程相关的信息(例如,优先级、进程状态以及调度实体等)。但是,每一个调度类并不是直接管理task_struct,而是引入调度实体的概念。CFS调度器使用sched_entity跟踪调度信息。CFS调度器使用cfs_rq跟踪就绪队列信息以及管理就绪态调度实体,并维护一棵按照虚拟时间排序的红黑树。tasks_timeline->rb_root是红黑树的根,tasks_timeline->rb_leftmost指向红黑树中最左边的调度实体,即虚拟时间最小的调度实体(为了更快的选择最适合运行的调度实体,因此rb_leftmost相当于一个缓存)。每个就绪态的调度实体sched_entity包含插入红黑树中使用的节点rb_node,同时vruntime成员记录已经运行的虚拟时间。

    基本概念

    nice值(普通进程的优先级)

    CFS调度器针对优先级提出了nice值的概念,其实和权重是一一对应的关系。nice值就是一个具体的数字,取值范围是[-20, 19]。数值越小代表优先级越大,同时也意味着权重值越大,nice值和权重之间可以互相转换。内核提供了一个表格转换nice值和权重。

    1. const int sched_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. };

    数组的值可以看作是公式:weight = 1024 / 1.25nice计算得到。公式中的1.25取值依据是:进程每降低一个nice值,将多获得10% cpu的时间。公式中以1024权重为基准值计算得来,1024权重对应nice值为0,其权重被称为NICE_0_LOAD。默认情况下,大部分进程的权重基本都是NICE_0_LOAD。

    virtual time(虚拟时间)

    CFS调度器没有时间的概念,而是根据虚拟运行时间(抽象表示任务已运行时间)对任务进行排序,选择虚拟运行时间最小的进程进行调度

    实际运行时间到 vruntime 的计算公式为:

    [ vruntime = 实际运行时间 * 1024 / 进程权重 ]

    vruntime值较小就说明它以前占用cpu的时间较短,受到了“不公平”对待,因此下一个运行进程就是它。这样既能公平选择进程,又能保证高优先级进程获得较多的运行时间,这就是CFS的主要思想。

    执行时间

    挑选的进程进行运行了,它运行多久?

    进程运行的时间是根据进程的权重进行分配。

    [ 分配给进程的运行时间 = 调度周期 *(进程权重 / 所有进程权重之和) ]

  • 相关阅读:
    MySQL入门教程
    技术学习群一周都分享了些啥?
    java锁
    目标检测数据集:摄像头成像吸烟检测数据集(自己标注)
    MYSQL下载及安装完整教程
    《无与伦比》Centos7 安装git
    如何使用宝塔面板部署MySQL数据库,并结合内网穿透实现固定公网地址远程连接
    Qt升级血与泪
    三翼鸟:产品不会变,场景实时变
    创建 Edge 浏览器扩展教程(下)
  • 原文地址:https://blog.csdn.net/qq_52353238/article/details/133302422