Linux进程的调度

news2024/9/19 20:37:17

目录

调度策略与调度类

实时调度策略

普通调度策略

调度类

sched_class有几种实现:

完全公平调度算法

调度队列与调度实体

调度类是如何工作的?


调度策略与调度类

在Linux里面,进程大概可以分成两种。 一种称为实时进程,也就是需要尽快执行返回结果的那种。另一种是普通进程,大部分的进程其实都是这种。

在task_struct数据结论里面有一个成员变量,叫做调度策略:

unsigned int policy;

他有以下的几个定义:

#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6

配合调度策略的,还有优先级,也在task_struct中:

int prio, static_prio, normal_prio;
unsigned int rt_priority;

优先级其实就是一个数值,对于实时进程,优先级的范围是0~99;对于普通进程,优先级的范围是100~139。数值越小,优先级越高

实时调度策略

对于调度策略,其中SCHED_FIFO、SCHED_RR、SCHED_DEADLINE是实时进程的调度策略:

SCHED_FIFO是先来先服务,可以给进程分配更高的优先级,也就是说,高优先级的进程可以抢占低优先级的进程,而相同优先级的进程,我们遵循先来先得。

SCHED_RR轮流调度算法,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,而高优先级的任务也是可以抢占低优先级的任务。

SCHED_DEADLINE,是按照任务的deadline进行调度的。当产生一个调度点的时候,DL调度器总是选择其deadline距离当前时间点最近的那个任务,并调度它执行。

普通调度策略

对于普通进程的调度策略有,SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE。

SCHED_NORMAL是普通的进程。

SCHED_BATCH是后台进程,几乎不需要和前端进行交互。这类项目可以默默执行,不要影响需要交互的进程,可以降低他的优先级。

SCHED_IDLE是特别空闲的时候才跑的进程。

调度类

上面提到的无论是policy还是priority,都设置了一个变量,变量仅仅表示了应该这样这样干,但事情总要有人去干,谁呢?在task_struct里面,还有这样的成员变量:

const struct sched_class *sched_class;

调度策略的执行逻辑,就封装在这里面,它是真正干活的那个。

sched_class有几种实现:

stop_sched_class优先级最高的任务会使用这种策略,会中断所有其他线程,且不会被其他任务打断;

dl_sched_class就对应上面的deadline调度策略;

rt_sched_class就对应RR算法或者FIFO算法的调度策略,具体调度策略由进程的task_struct- >policy指定;

fair_sched_class就是普通进程的调度策略

idle_sched_class就是空闲进程的调度策略。

完全公平调度算法

在Linux里面,实现了一个基于CFS的调度算法。CFS全称Completely Fair Scheduling,叫完全公平调度,具体的运行思想如下:

需要记录下进程的运行时间。CPU会提供一个时钟,过一段时间就触发一个时钟中断。就像咱们的表滴答一下,这个我们叫Tick。CFS会为每一个进程安排一个虚拟运行时间vruntime。如果一个进程在运行,随着时间的增长,也就是一个个tick的到来,进程的vruntime将不断增大没有得到执行的进程vruntime不变。那些vruntime少的,原来受到了不公平的对待,需要给它补上所以会优先运行这样的进程。

在更新进程运行的统计量的时候,我们运行的如下的逻辑:

/*
 * Update the current task's runtime statistics.
 */
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->exec_start = now;
......
curr->sum_exec_runtime += delta_exec;
......
curr->vruntime += calc_delta_fair(delta_exec, curr);
update_min_vruntime(cfs_rq);
......
}
/*
 * delta /= w
 */
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
if (unlikely(se->load.weight != NICE_0_LOAD))
 /* delta_exec * weight / lw.weight */
delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
return delta;
}

在这里得到当前的时间,以及这次的时间片开始的时间,两者相减就是这次运行的时间delta_exec ,但是得到的这个时间其实是实际运行的时间,需要做一定的转化才作为虚拟运行时间vruntime。转化方法如下:

虚拟运行时间vruntime += 实际运行时间delta_exec * NICE_0_LOAD/权重

调度队列与调度实体

根据上面对CFS的分析,可以看出对CFS是需要一个数据结构来对vruntime进行排序,找出最小的那个。这个能够排序的数据结构不但需要查询的时候,能够快速找到最小的,更新的时候也需要能够快速的调整排序。能够平衡查询和更新速度的是树,在这里使用的是红黑树

红黑树的的节点是应该包括vruntime的,称为调度实体(根据调度实体进行排序)

在task_struct中有这样的成员变量:

struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;

这里有实时调度实体sched_rt_entity,Deadline调度实体sched_dl_entity,以及完全公平算法调度实体sched_entity。这说明不仅CFS需要这样一个数据结构进行排序,其他的调度策略也同样有自己的数据结构进行排序。

而进程根据自己是实时的,还是普通的类型,通过这个成员变量,将自己挂在某一个数据结构里面,和其他的进程排序,等待被调度。比如,如果这个进程是个普通进程,则通过sched_entity,将自己挂在这棵红黑树上。

对于普通进程的调度实体定义如下,这里面包含了vruntime和权重load_weight,以及对于运行时间的统计:

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 nr_migrations;
    struct sched_statistics statistics;
......
};

下图是一个红黑树的例子:

所有可运行的进程通过不断地插入操作最终都存储在以时间为顺序的红黑树中,vruntime最小的在树的左侧,vruntime最多的在树的右侧。 CFS调度策略会选择红黑树最左边的叶子节点作为下一个将获得cpu的任务

每个CPU都有自己的 struct rq 结构,其用于描述在此CPU上所运行的所有进程,其包括一个实时进程队列rt_rq和一个CFS运行队列cfs_rq,在调度时,调度器首先会先去实时进程队列找是否有实时进程需要运行,如果没有才会去CFS运行队列找是否有进行需要运行:

struct rq {
    /* runqueue lock: */
    raw_spinlock_t lock;
    unsigned int nr_running;
    unsigned long cpu_load[CPU_LOAD_IDX_MAX];
......
    struct load_weight load;
    unsigned long nr_load_updates;
    u64 nr_switches;
    struct cfs_rq cfs;
    struct rt_rq rt;
    struct dl_rq dl;
......
    struct task_struct *curr, *idle, *stop;
......
};

对于普通进程公平队列cfs_rq,定义如下:

/* CFS-related fields in a runqueue */
struct cfs_rq {
    struct load_weight load;
    unsigned int nr_running, h_nr_running;
    u64 exec_clock;
    u64 min_vruntime;
#ifndef CONFIG_64BIT
    u64 min_vruntime_copy;
#endif
    struct rb_root tasks_timeline;
    struct rb_node *rb_leftmost;
    struct sched_entity *curr, *next, *last, *skip;
......
};

这里面rb_root指向的就是红黑树的根节点,这个红黑树在CPU看起来就是一个队列,不断的取下一个应该运行的进程。rb_leftmost指向的是最左面的节点。

对于上面提及的数据结构关系结构如下:

调度类是如何工作的?

调度类的定义如下:

struct sched_class {
    const struct sched_class *next;
    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,
    struct task_struct *prev,
    struct rq_flags *rf);
    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 (*task_dead) (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 (*update_curr) (struct rq *rq)

 这个结构定义了很多种方法,用于在队列上操作任务。第一个成员变量是一个指针,指向下一个调度类,上面有提到过,调度类分为以下几种:

extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;

 这些调度类是放在一个链表上的。以调度中取下一个任务为例子:

/*
 * Pick up the highest-prio task:
 */
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
    const struct sched_class *class;
    struct task_struct *p;
......
    for_each_class(class) {
        p = class->pick_next_task(rq, prev, rf);
        if (p) {
            if (unlikely(p == RETRY_TASK))
                goto again;
            return p;
        }
    }
}

可以看到,这里面有一个for_each_class循环,沿着上面的顺序,依次调用每个调度类的方法。这就说明,调度的时候是从优先级最高的调度类到优先级低的调度类,依次执行。而对于每种调度类,有自己的实现,例如,CFS就有fair_sched_class:

const struct sched_class fair_sched_class = {
    .next = &idle_sched_class,
    .enqueue_task = enqueue_task_fair,
    .dequeue_task = dequeue_task_fair,
    .yield_task = yield_task_fair,
    .yield_to_task = yield_to_task_fair,
    .check_preempt_curr = check_preempt_wakeup,
    .pick_next_task = pick_next_task_fair,
    .put_prev_task = put_prev_task_fair,
    .set_curr_task = set_curr_task_fair,
    .task_tick = task_tick_fair,
    .task_fork = task_fork_fair,
    .prio_changed = prio_changed_fair,
    .switched_from = switched_from_fair,
    .switched_to = switched_to_fair,
    .get_rr_interval = get_rr_interval_fair,
    .update_curr = update_curr_fair,
};

对于同样的pick_next_task选取下一个要运行的任务这个动作,不同的调度类有自己的实现。 fair_sched_class的实现是pick_next_task_fair,rt_sched_class的实现是pick_next_task_rt。我们会发现这两个函数是操作不同的队列,pick_next_task_rt操作的是rt_rq,pick_next_task_fair操作的是cfs_rq

整体串起来就是:

在每个CPU上都有一个队列rq,这个队列里面包含多个子队列,例如rt_rq和cfs_rq,不同的队列有不同的实现方式,cfs_rq就是用红黑树实现的。当有一天,某个CPU需要找下一个任务执行的时候,会按照优先级依次调用调度类,不同的调度类操作不同的队列。当然rt_sched_class先被调用,它会在rt_rq上找下一个任务,只有找不到的时候,才轮到fair_sched_class被调用,它会在cfs_rq上找下一个任务。这样保证了实时任务的优先级永远大于普通任务。

总结:

一个CPU上有一个队列,CFS的队列是一棵红黑树,树的每一个节点都是一个sched_entity,每个sched_entity都属于一个 task_struct,task_struct里面有指针指向这个进程属于哪个调度类。在调度的时候,依次调用调度类的函数,从CPU的队列中取出下一个进程。 

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

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

相关文章

Redis主从复制+哨兵模式

必读 redis的主从复制是单向的,只能有主节点到从节点,主节点以写为主从节点以读为主不能写入数据!因为系统的80%的需求都是读的需求。 redis服务默认自己是主节点,一个主节点由一个或多个从节点,一个从节点只有一个主…

图信号处理论文

图信号处理并且非图神经网络的论文: Donget.al“GraphSignal Processingfor MachineLearning A Review and New Perspectives," ICASSP Tutorial, June 2021. Lorenzoet.al“Adaptiveleast mean squaresestimation ofgraph signals"IEEE Trans. Signal I…

Vue3 学习笔记 —— 破坏式更新、自定义指令 directive

目录 1. 什么叫破坏式更新? 2. Vue3 中的自定义指令 2.1 自定义指令的生命周期 2.1.1 Vue2 Vs Vue3 的自定义指令生命周期 2.1.2 自定义指令的生命周期中,接收的参数 2.2 定义一个自定义指令 2.2.1 在 setup 中定义自定义指令(此处为 …

数据结构 树练习题

目录 判断 选择 判断 1.一棵有124个结点的完全二叉树,其 叶结点个数是确定的。 【答案】正确 【解析】完全二叉树 若设二叉树的深度为h 除第 h 层外 其它各层 1~(h-1) 的结点数都达到最大个数(即1~(h-1)层为一个满二叉树) 第 h 层所有的结点都连续集…

【C++】STL

文章目录回调函数:一、STL的诞生二、STL基本概念三、STL六大组件四、STL中容器,算法,迭代器回调函数: 函数被作为参数传递到另一个函数(主要函数)的那个函数就叫做 回调函数 一、STL的诞生 C的面向对象和…

DHTMLX Diagram JavaScript/HTML5 Pro Library:5.0

Diagram — JavaScript/HTML5 Diagram Library Ω578867473 破解版DHTMLX Diagram comprises a set of interactive HTML5 UI components such as organization charts, flowcharts, decision trees, block diagrams, mind maps, etc. Consisting of nodes and connectors, di…

i.MX 6ULL 驱动开发 二十八:网络设备

一、网络设备的系统框图 MAC:工作在网络模型的数据链路层,通过 RGMII 或 RMII 接口连接 PHY,MAC 控制器中的 MDIO 控制器提供 MDIO 接口,用于访问 PHY 寄存器。 PHY:工作在网络模型的物理层,是 IEEE802.3 …

SVN项目,提交Git保留之前提交记录

📃目录跳转简介:1.创建文件2.命令下载:3.上传到远程git3.1 创建远程git工程3.2 添加一个新的远程 Git 仓库3.3 拉取远程master的数据合并3.3 推送远程git分支简介: 由于之前直接搞的项目是部署在自己搭建的SVN服务器上,平时创建的…

5. JVM调优工具详解及调优实战(这里有我的实战案例预制构件生产管理平台)

1. Jmap,Jstack,Jinfo命令详解 1.1 Jmap 此命令可以用来查看内存信息,实例个数以及占用内存大小 jps 先查看有哪些java程序 jmap -histo 16492 > ./log.txt jmap -heap 16492 查看堆的信息 查看堆年轻代老年代的使用情况 堆内存dum…

Matlab:tftb-0.2时频工具箱安装小记

Matlab:tftb-0.2时频工具箱安装小记一、安装过程记录1、解压缩:2、将解压缩后的文件夹复制到自己的Matlab安装目录工具箱下;3、打开Matlab设置路径:设置路径4、测试是否安装成功:5、小试牛刀叮嘟!这里是小啊…

【ASE+python学习】-批量识别石墨烯团簇结构中的吡啶氮,并删除与其相连的氢

批量识别石墨烯团簇结构中的吡啶氮,并删除与其相连的氢文章背景任务内容程序实现思路实现代码建立标准结构中边缘碳与氢的位置差值标准数据集读入待修改结构,识别氮与氢位置差值是否存在标准数据集代码细节剖析文章背景 在科研工作中,我的工…

STM32系列(HAL库)——串口IAP

前言 IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产 品中的固件程序进行更新升级。 设备具备IAP功能…

javaScript学习———变量概述 变量的使用 变量语法扩展 变量命名规范交换 变量案例

博主每篇博文的浪漫主义: 【东京girl秀场上那些甜度爆表的女孩子们。💖】 https://www.bilibili.com/video/BV1pG411F7KT/?share_sourcecopy_web&vd_source385ba0043075be7c24c4aeb4aaa73352 东京girl秀场上那些甜度爆表的女孩子们。💖…

计算机组成原理--------12.4---------开始

计算机硬件的基本组成 冯诺依曼计算机的特点 冯诺依曼首次提出“存储程序”概念 计算机由五大部件组成:I/O设备(输入输出),存储器(存放数据和程序),运算器(算术运算、逻辑运算&…

[附源码]JAVA毕业设计科研项目审批管理系统(系统+LW)

[附源码]JAVA毕业设计科研项目审批管理系统(系统LW) 目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目…

关于解释型语言和编译性语言的区别

关于博主每篇博文的浪漫主义 【初恋是整遍《手写的从前》】 https://www.bilibili.com/video/BV1JP411g7qF/?share_sourcecopy_web&vd_source385ba0043075be7c24c4aeb4aaa73352 初恋是整遍《手写的从前》能够知道解释性语言和编译型语言的特点能够知道标识符不能是关键字…

docker_重装mysql

1.docker ps 查看docker正在运行的容器 2.docker stop xxx 停止正在运行的mysql 3.docker pull mysql:5.7 docker拉取mysql指定版本的镜像 docker pull mysql docker拉取最新版本的镜像 4.docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql:…

PHP代码审计系列(一)

PHP代码审计系列&#xff08;一&#xff09; 本系列将收集多个PHP代码安全审计项目从易到难&#xff0c;并加入个人详细的源码解读。此系列将进行持续更新。 extract变量覆盖 源码如下 <?php$flagextractFlag.txt; extract($_GET);if(isset($shiyan)){ $contenttrim(f…

YOLO算法创新改进系列(项目汇总)

&#x1f680;&#x1f680;&#x1f680;——YOLO算法创新改进系列项目汇总——&#x1f384;&#x1f384;&#x1f384; &#x1f680; YOLO算法创新改进系列 &#xff08;项目汇总&#xff09;&#x1f384;&#x1f388; &#x1f340; 改进YOLOv5/YOLOv7——魔改YOLOv5/Y…

文件操作及IO

目录 一、文件的分类 二、文件路径 三、File 常见方法 1、get 相关方法使用 2、文件的创建和删除 3、遍历目录下所有文件 四、文件读写 一、文件的分类 站在程序员的角度&#xff0c;文件通常可以分为两类&#xff1a; 文本文件&#xff1a;以字符形式存储二进制文件&…