CFS调度器(一)

2023-10-07 07:58
文章标签 调度 cfs

本文主要是介绍CFS调度器(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关于调度类和优先级的概念,前面的文章《调度器概述》中已经做了介绍了,本文不在重述。本文主要关注的就是CFS调度器,或者叫做fair_sched_class调度类。这种调度器是被SCHED_NORMAL、SCHED_BATCH这两种调度策略使用的。(本文基于Linux4.0)

提到调度器涉及到两个问题:

1.怎么根据参量快速选出下一个要调度执行的进程?
2.怎么定义和更新不同进程的相关参量?

带着这两个问题,我们慢慢去讲解CFS调度器。

权重

权重代表着进程对于CPU使用时间的占有率,所以它的大小跟进程优先级相关,优先级越高,抢占的CPU时间越多,因此它的权重就越大。它的关系可以用下面的公式来表示:

进程运行时间 = 进程权重 * CPU总时间 / 所有进程总权重

内核中每个调度实体都用一个结构体成员来表示权重:

struct sched_entity {struct load_weight load;...
}

表示权重的结构体为:

struct load_weight {unsigned long weight;u32 inv_weight;
}

其中weight代表的是权重,inv_weight是为了方便计算出来的一个中间值。内核根据优先级nice来定义权重,约定nice值为0的进程权重值为1024,其他nice值的进程权重可以根据查表获取:

/** Nice levels are multiplicative, with a gentle 10% change for every* nice level changed. I.e. when a CPU-bound task goes from nice 0 to* nice 1, it will get ~10% less CPU time than another CPU-bound task* that remained on nice 0.** The "10% effect" is relative and cumulative: from _any_ nice level,* if you go up 1 level, it's -10% CPU usage, if you go down 1 level* it's +10% CPU usage. (to achieve that we use a multiplier of 1.25.* If a task goes up by ~10% and another task goes down by ~10% then* the relative distance between them is ~25%.)*/
static const int 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值的权重值都是基于1024计算出来的,高一优先级的进程权重总是低一优先级进程的1.25倍。除了这个表,内核为了计算方便还定义了另外一个表:

/** Inverse (2^32/x) values of the prio_to_weight[] array, precalculated.** In cases where the weight does not change often, we can use the* precalculated inverse to speed up arithmetics by turning divisions* into multiplications:*/
static const u32 prio_to_wmult[40] = {/* -20 */     48388,     59856,     76040,     92818,    118348,/* -15 */    147320,    184698,    229616,    287308,    360437,/* -10 */    449829,    563644,    704093,    875809,   1099582,/*  -5 */   1376151,   1717300,   2157191,   2708050,   3363326,/*   0 */   4194304,   5237765,   6557202,   8165337,  10153587,/*   5 */  12820798,  15790321,  19976592,  24970740,  31350126,/*  10 */  39045157,  49367440,  61356676,  76695844,  95443717,/*  15 */ 119304647, 148102320, 186737708, 238609294, 286331153,
};

内核设置进程权重的函数:

static void set_load_weight(struct task_struct *p)
{int prio = p->static_prio - MAX_RT_PRIO;struct load_weight *load = &p->se.load;/** SCHED_IDLE tasks get minimal weight:*/if (p->policy == SCHED_IDLE) {load->weight = scale_load(WEIGHT_IDLEPRIO);load->inv_weight = WMULT_IDLEPRIO;return;}load->weight = scale_load(prio_to_weight[prio]);load->inv_weight = prio_to_wmult[prio];
}

虚拟时间

CFS调度器的目标是保证每个进程的完全公平调度,尝试对进程一视同仁,把CPU时间平均分配给每个进程去运行,进程运行的同时会记录每个进程的运行时间,按照这种公平算法,CPU每次调度都会优先选择运行时间最少的那个进程,以保证进程的公平。
假如按照上述思想去设计调度器,那么会有一个问题,所有进程都是完全公平,那么如何体现优先级的不同呢?实际上内核并不是使用实际运行时间作为pick_next进程的参考量,而采用的是一个虚拟时间,这个虚拟时间加入了不同优先级权重因素的。虚拟时间的计算方法如下:

vruntime = delta_exec * NICE0_WEIGHT / weight;

vruntime是虚拟运行时间,delta_exec是实际运行时间,NICE0_WEIGHT是nice值为0的权重,也就是1024,weight进程的权重。这样由上面的表可知,优先级越高的进程算出来的vruntime越小,从而被CFS调度器选出来的概率就越大。runqueue中的所有进程都要维护一个vruntime变量,并且把这些进程按照红黑树来管理,vrunquque总是处于红黑树的最左侧,这样在算法选择时具有更高的效率。

回到前面的问题,CFS调度器就是通过vruntime参量来决定当前要选择运行的进程,为了选择算法的高效和快速,采用红黑树来管理这个参量,这就是CFS调度器的核心思想了。
下面是内核中的代码定义:

struct sched_entity {u64         exec_start;u64         sum_exec_runtime;u64         vruntime;u64         prev_sum_exec_runtime;......
};

对于实际运行时间转换为虚拟时间,通过如下函数来实现calc_delta_fair:

static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{if (unlikely(se->load.weight != NICE_0_LOAD))delta = __calc_delta(delta, NICE_0_LOAD, &se->load);return delta;
}

有了这个虚拟时间,我们可以用来作为选择pick next进程的参考量,那么此参考量何时需要更新呢?


1.进程创建,加入运行队列时要更新2.周期调度时要更新

调度延迟

调度延迟表示是保证系统中的每个进程都能执行一遍所需要的时间。它不是一个固定值,计算方式如下:


unsigned int sysctl_sched_latency = 6000000ULL;         //6ms
static unsigned int sched_nr_latency = 8;
unsigned int sysctl_sched_min_granularity = 750000ULL;   //0.75msstatic u64 __sched_period(unsigned long nr_running)
{u64 period = sysctl_sched_latency;unsigned long nr_latency = sched_nr_latency;if (unlikely(nr_running > nr_latency)) {period = sysctl_sched_min_granularity;period *= nr_running;}return period;
}

当系统处于就runnable的进程少于一个定值(默认值8)的时候,调度延迟也是固定一个值不变(默认值6ms)。当系统就绪态进程个数超过这个值时,我们保证每个进程至少运行一定的时间,那么调度延迟总时间就是nr_running * sysctl_sched_min_granularity。
当获取到一个调度延迟后,还需要计算一个进程分配的时间片,CFS调度器和以往的O(1)调度器不同,不在有固定的时间片,而是通过权重计算出的时间片。按照前面权重的介绍,通过

时间片=调度延迟总时间*进程权重/总权重

来计算一个进程的时间片。

static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);for_each_sched_entity(se) {struct load_weight *load;struct load_weight lw;cfs_rq = cfs_rq_of(se);load = &cfs_rq->load;if (unlikely(!se->on_rq)) {lw = cfs_rq->load;update_load_add(&lw, se->load.weight);load = &lw;}slice = __calc_delta(slice, se->load.weight, load);}return slice;
}

__calc_delta传入的参数分别是总时间,当前调度实体的权重,整个runqueue的总权重。因此每个进程的时间片是不固定的,而是根据权重和系统中进程的数量发生着变化。

与调度相关的场景

1.进程创建过程
2.周期调度
3.直接调度
后续文章将针对每个场景介绍调度相关的内容。

这篇关于CFS调度器(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/157068

相关文章

搭建Kafka+zookeeper集群调度

前言 硬件环境 172.18.0.5        kafkazk1        Kafka+zookeeper                Kafka Broker集群 172.18.0.6        kafkazk2        Kafka+zookeeper                Kafka Broker集群 172.18.0.7        kafkazk3

一种改进的red5集群方案的应用、基于Red5服务器集群负载均衡调度算法研究

转自: 一种改进的red5集群方案的应用: http://wenku.baidu.com/link?url=jYQ1wNwHVBqJ-5XCYq0PRligp6Y5q6BYXyISUsF56My8DP8dc9CZ4pZvpPz1abxJn8fojMrL0IyfmMHStpvkotqC1RWlRMGnzVL1X4IPOa_  基于Red5服务器集群负载均衡调度算法研究 http://ww

Golang进程权限调度包runtime

关于 runtime 包几个方法: Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行GOMAXPROCS:设置最大的可同时使用的 CPU 核数Goexit:退出当前 goroutine(但是defer语句会照常执行)NumGoroutine:返回正在执行和排队的任务总数GOOS:目标操作系统NumCPU:返回当前系统的 CPU 核数量 p

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑燃料电池和电解槽虚拟惯量支撑的电力系统优化调度方法》

本专栏栏目提供文章与程序复现思路,具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源程序擅长文章解读,论文与完整源程序,等方面的知识,电网论文源程序关注python

【Linux】探索进程优先级的奥秘,解锁进程的调度与切换

目录 进程优先级: 是什么? 为什么存在进程优先级的概念呢? Linux为什么调整优先级是要受限制的? PRI vs NICE Linux的调度与切换 概念准备: 那我们到底怎样完成进程的调度和切换呢? 区分:寄存器VS寄存器的内容 Linux实现进程调度的算法,需要考虑优先级,考虑进程饥饿问题,考虑效率问题。 解决优先级问题: 解决进程饥饿问题: 解决效率的问题:

k8s调度(pod亲和、反亲和、污点、容忍度)

pod亲和性 针对对象为Pod,目的是实现,新建Pod和目标Pod调度到一起,在同一个Node上。 示例: apiVersion: v1kind: Podmetadata:name: testpod01labels:app: myapp01env: test1spec:containers:- name: testpod01image: nginx:1.23.2---apiVersio

黑马程序员---银行业务调度系统

模拟实现银行业务调度系统逻辑 需求分析: 银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。 有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。 异步随机生成各种类型的客户,生成各类型用户的概率比例为:         VIP客户 :普通客户 :快速客户 =  1:6:3。 客户办理业务所

RMS调度详解

1.RMS调度简介 任务按单调速率优先级分配(RMPA)的调度算法,称为单调速率调度(RMS)。RMPA是指任务的优先级按任务周期T来分配。它根据任务的执行周期的长短来决定调度优先级,那些具有小的执行周期的任务具有较高的优先级,周期长的任务优先级低。 2.RMS调度实现介绍 SylixOS目前关于RMS调度分为创建、删除、调度三个部分组成。创建和删除就不予介绍。重点关注下调度算法的实现。调度

Kubernetes Scheduler:Pod调度的双步骤—预选(Predicates)和优选(Priorities)

Kubernetes Scheduler:Pod调度的双步骤—预选(Predicates)和优选(Priorities) 1、预选(Predicates)2、优选(Priorities) 💖The Begin💖点点关注,收藏不迷路💖 在Kubernetes中,Pod的调度是由Scheduler负责的。Scheduler通过两个关键步骤——预选(Predicat

【蓝桥杯嵌入式(一)程序框架和调度器】

蓝桥杯嵌入式(一)程序框架和调度器 序、代码命名规则零、STM32和8051⼀、软件及环境安装⼆、⼯程框架搭建1.时钟配置2、SYS配置3、⼯程配置4、NVIC配置5.、Keil配置 三、系统初始化四、任务调度器 链接: 视频出处 序、代码命名规则 以下是一些常见的举例 零、STM32和8051 链接: 8位和32位单片机最本质区别 ⼀、软件及环境安装