Linux内核实现完全公平调度算法

news2024/9/28 23:28:50

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

  • 基于时间片轮询调度算法。(2.6之前的版本)
  • O(1) 调度算法。(2.6.23之前的版本)
  • 完全公平调度算法。(2.6.23以及之后的版本)

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

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

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

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

  • 不能按权重分配不同的运行时间,例如有些进程权重大的应该获得更多的运行时间。
  • 每次调度时都需要遍历运行队列中的所有进程,找到优先级最大的进程运行,时间复杂度为 O(n)。对于一个高性能的操作系统来说,这是不能接受的。

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

完全公平调度的两个对象

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 成员,其主要用于把调度实体连接到可运行进程的队列中。

学习直通车:

Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂​ke.qq.com/course/4032547?flowToken=1040236​ke.qq.com/course/4032547?flowToken=1040236​ke.qq.com/course/4032547?flowToken=1040236

内核资料直通车:

Linux内核源码技术学习路线+视频教程内核源码​docs.qq.com/doc/DTXFKZlZ4YmFYQWZS正在上传…重新上传取消

另外,进程描述符 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 操作),这里就不详细分析了,有兴趣可以自己研究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/353836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT AI 人工智能 开发路径

ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI研发的聊天机器人程序&#xff0c;于2022年11月30日发布。 推荐以下几个AI 开发学习资源 一、GPTZero AI: GPTZero GPTZero 是普林斯顿大学学生 Edward Tian …

Java爬虫—WebMagic

一&#xff0c;WebMagic介绍WebMagic企业开发&#xff0c;比HttpClient和JSoup更方便一&#xff09;&#xff0c;WebMagic架构介绍WebMagic有DownLoad&#xff0c;PageProcessor&#xff0c;Schedule&#xff0c;Pipeline四大组件&#xff0c;并有Spider将他们组织起来&#xf…

MySQL中JSON数据类型详解

目录 概要及优点 JSON定义 JSON字段的增删改查操作 插入操作 查询操作 修改操作 删除操作 如何对JSON字段创建索引&#xff1f; 加索引查询结果分析&#xff1a; 不加索引查询结果分析&#xff1a; 使用JSON时的注意事项 概要及优点 JSON数据类型是MySQL5.7.8开始支持的…

FlowChartX/Diagramming for ActiveX 4.9.8 Crack

构建完美的图表 如果您的应用程序以 ActiveX 平台为目标&#xff0c;并且您需要实现图表功能&#xff0c;那么您所需要的只是 FlowChartX。它提供了创建、自定义和呈现流程图的所有功能。 ActiveX 图表库&#xff1a;分类图表 图 Diagramming for ActiveX该组件为您提供了一组…

浅谈C++函数重载

C相较于C语言来说,重载是一重大特性,让我们一起简单的回顾一下重载那些事 传送门函数重载是什么为什么有函数重载函数重载是如何实现的总结函数重载是什么 函数重载:是函数的一种特殊情况,C允许在同一作用域中声明几个功能相似的同名函数 这些同名函数的形参列表(参数个数or类…

day19_抽象类丶接口

由来 当我们声明一个几何图形类&#xff1a;圆、矩形、三角形类等&#xff0c;发现这些类都有共同特征&#xff1a;求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又无法给出具体的实现&#xff0c;而是应该交给子类各自…

当遇到国外客户的问题,你解决不了的时候怎么办

对我来说&#xff0c;今年的这个春节假期有点长&#xff0c;差不多休了一个月。复工之后&#xff0c;截止目前做到了60万RMB的业绩&#xff0c;但是相较于往年&#xff0c;整体状态还是差了些。往年的春节&#xff0c;我都是随时待命的状态&#xff0c;整个春节天天坐于电脑前&…

JSP 和 JSTL

文章目录&#x1f353;摘要&#x1f353;一、JSP&#x1f349;1.1 JSP的基础语法&#x1f36b;1.1.1 简介&#x1f36b;1.1.2 依赖&#x1f36b;1.1.3 注释&#x1f36b;1.1.4 Scriptlet 脚本&#x1f349;1.2 JSP的指令标签&#x1f36b;1.2.1 include 静态包含&#x1f36b;1…

2023年数学建模美赛A题(A drought stricken plant communities)分析与编程

2023年数学建模美赛A题&#xff08;A drought stricken plant communities&#xff09;分析与编程 2023年数学建模美赛D题&#xff08;Prioritizing the UN Sustainability Goals&#xff09;分析与编程 特别提示&#xff1a; 1 本文介绍2023年美赛题目&#xff0c;进行深入分析…

台式计算机加固态硬盘,台式机添加固态硬盘教程_台式主机固态硬盘怎么安装-win7之家...

固态硬盘是用固态电子存储芯片阵列制成的硬盘&#xff0c;也是电脑中比较常见的内存硬件&#xff0c;有些用户在使用电脑时候&#xff0c;由于内存不足导致系统运行较卡的情况&#xff0c;往往会选择添加固态硬盘来解决&#xff0c;那么台式主机固态硬盘怎么安装呢&#xff1f;…

Ansible中的角色使用(ansible roles)

文章目录一、ansible 角色简介二、roles目录结构三、role存放的路径&#xff1a;配置文件ansible.cfg中定义四、创建目录结构五、playbook中使用rolesplaybook变量会覆盖roles中的定义变量六、控制任务执行顺序七、ansible—galaxy命令工具八、安装选择的角色1.从网上下载&…

2023.2.17-博客记录

1.斐波那契数列的复杂度 参考于&#xff1a;斐波那契数列时间复杂度 运用到了递归思想 那么&#xff0c;推导&#xff1a;时间复杂度 f(n) f(n-1) f(n-2) 每一层都包含一个加法操作 例如n 8时&#xff0c;T(n) 2^0 2^1 2^2 2^3 2^4 2^5 2^6 2^7-1 O(n) 2^7-1 2…

四六级真题长难句分析与应用

一、基本结构的长难句 基本结构的长难句主要考点&#xff1a;断开和简化 什么是长难句&#xff1f; 其实就是多件事连在了一块&#xff0c;这时候句子就变长、变难了 分析步骤&#xff1a; 第一件事就是要把长难句给断开&#xff0c;把多件事断开成一件一件的事情&#xff0…

ElementUI`resetFields()`方法避坑

使用ElementUI中的resetFields()方法有哪些注意点 场景一 场景一&#xff1a;当编辑弹出框和新增弹出框共用时&#xff0c;编辑数据后关闭编辑弹出框时调用this.$refs.form.resetFields()无法清空弹出框 问题代码&#xff1a; // 点击新增按钮handleAdd() {this.dialogVi…

《Qt6开发及实例》6-3 双缓冲机制

目录 一、原理与设计 1.1 原理 1.2 设计 二、绘图区的实现 2.1 鼠标移动事件 2.2 重绘函数&调整大小函数&清除屏幕 三、主窗口的实现 3.1 代码 一、原理与设计 1.1 原理 双缓冲就是在绘制控件时&#xff0c;将内容绘制在一个图片中&#xff0c;再将图片一次性…

全局状态管理插件 Vuex 介绍及使用

文章目录Vuex 是什么简介Vuex 如何存储数据Vuex 核心概念单向数据流StateGetterMutationActionModuleVuex 使用实例总结Vuex 是什么 简介 官方解释&#xff1a;Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以…

Linux环境下(CentOS 7)安装Java(JDK8)

Linux环境下(CentOS 7)安装Java(JDK8) 一、安装教程 1.1 首先&#xff0c;进入oracle官网下载jdk8的安装包&#xff0c;下载地址如下&#xff0c;这里以 jdk-8u121-linux-x64.tar.gz安装包为例。 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-21…

2023美赛(MCM/ICM)数据汇总

2023美赛已经开始了十二个小时了&#xff0c;对于本次比赛&#xff0c;赛题 难度都不大。主要就是收集数据的问题 &#xff0c;为了更好地帮助大家选题&#xff0c;我们将 我们今天已经收集到的数据进行汇总&#xff0c;分享。其中&#xff0c;首先是A、E题目都提及的天气数据&…

C语言学习_DAY_4_判断语句if_else和分支语句switch_case【C语言学习笔记】

高质量博主&#xff0c;点个关注不迷路&#x1f338;&#x1f338;&#x1f338;&#xff01; 目录 1.案例引入 2.if判断语句的语法与注意事项 3.switch多分支语句的语法与注意事项 前言: 书接上回&#xff0c;我们已经学习了所有的数据类型、运算符&#xff0c;并且可以书写…

基于matlab/simulink的风光柴储微电网仿真建模

模型是基于之前的风光储系统上增加一部分柴油发电机系统&#xff0c;后面文章我会单独介绍柴油机这一部分&#xff0c;主要应用在船舶电力系统&#xff0c;一般小型电网黑启动也会用到。 风光柴储微电网发电系统是一种小型发电系统&#xff0c;同时具备并网运行和孤岛运行的功能…