【原创】(二)Linux进程调度器-CPU负载
背景
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基
说明:
- Kernel版本:4.14
- ARM64处理器,Contex-A53,双核
- 使用工具:Source Insight 3.5, Visio
1. 概述
CPU负载(cpu load
)指的是某个时间点进程对系统产生的压力。
来张图来类比下(参考Understanding Linux CPU Load)
- CPU的运行能力,就如大桥的通行能力,分别有满负荷,非满负荷,超负荷等状态,这几种状态对应不同的cpu load值;
- 单CPU满负荷运行时cpu_load为1,当多个CPU或多核时,相当于大桥有多个车道,满负荷运行时cpu_load值为CPU数或多核数;
- CPU负载的计算(以单CPU为例),假设一分钟内执行10个任务代表满负荷,当一分钟给出30个任务时,CPU只能处理10个,剩余20个不能处理,cpu_load=3;
在实际系统中查看:
cat /proc/cpuinfo
:查看CPU信息;cat /proc/loadavg
:查看cpu最近1/5/15分钟的平均负载:
计算CPU负载,可以让调度器更好的进行负载均衡处理,以便提高系统的运行效率。此外,内核中的其他子系统也可以参考这些CPU负载值来进行相应的调整,比如DVFS
等。
目前内核中,有以下几种方式来跟踪CPU负载:
- 全局CPU平均负载;
- 运行队列CPU负载;
PELT(per entity load tracking)
;
这也是本文需要探讨的内容,开始吧。
2. 全局CPU平均负载
2.1 基础概念
先来明确两个与CPU负载计算相关的概念:
active task(活动任务)
:只有知道活动任务数量,才能计算CPU负载,而活动任务包括了TASK_RUNNING
和TASK_UNINTERRUPTIBLE
两类任务。包含TASK_UNINTERRUPTIBLE
任务的原因是,这类任务经常是在等待I/O请求,将其包含在内也合理;NO_HZ
:我们都知道Linux内核每隔固定时间发出timer interrupt
,而HZ
是用来定义1秒中的timer interrupts
次数,HZ
的倒数是tick
,是系统的节拍器,每个tick
会处理包括调度器、时间管理、定时器等事务。周期性的时钟中断带来的问题是,不管CPU空闲或繁忙都会触发,会带来额外的系统损耗,因此引入了NO_HZ
模式,可以在CPU空闲时将周期性时钟关掉。在NO_HZ
期间,活动任务数量的改变也需要考虑,而它的计算不如周期性时钟模式下直观。
2.2 流程
Linux内核中定义了三个全局变量值avenrun[3]
,用于存放最近1/5/15分钟的平均CPU负载。
看一下计算流程:
- 计算活动任务数,这个包括两部分:1)周期性调度中新增加的活动任务;2)在
NO_HZ
期间增加的活动任务数; - 根据活动任务数值,再结合全局变量值
avenrun[]
中的old value
,来计算新的CPU负载值,并最终替换掉avenrun[]
中的值; - 系统默认每隔5秒钟会计算一次负载,如果由于
NO_HZ
空闲而错过了下一个CPU负载的计算周期,则需要再次进行更新。比如NO_HZ
空闲20秒而无法更新CPU负载,前5秒负载已经更新,需要计算剩余的3个计算周期的负载来继续更新;
2.3 计算方法
Linux内核中,采用11位精度的定点化计算,CPU负载1.0由整数2048表示,宏定义如下:
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */
#define LOAD_FREQ (5*HZ+1) /* 5 sec intervals */
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
计算公式如下:
load
值为旧的CPU负载值avenrun[]
,整个计算完成后得到新的负载值,再更新avenrun[]
;EXP_1/EXP_5/EXP_15
,分别代表最近1/5/15分钟的定点化值的指数因子;active
值,根据读取calc_load_tasks
的值来判断,大于0则乘以FIXED_1(2048)
传入;- 根据
active
和load
值的大小关系来决定是否需要加1,类似于四舍五入的机制;
关键代码如下:
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
avenrun[0] = calc_load(avenrun[0], EXP_1, active);
avenrun[1] = calc_load(avenrun[1], EXP_5, active);
avenrun[2] = calc_load(avenrun[2], EXP_15, active);
NO_HZ
模式下活动任务数量更改的计算
由于NO_HZ
空闲效应而更改的CPU活动任务数量,存放在全局变量calc_load_nohz[2]
中,并且每5秒计算周期交替更换一次存储位置(calc_load_read_idx/calc_load_write_idx
),其他程序可以去读取最近5秒内的活动任务变化的增量值。
计算示例
假设在某个CPU上,开始计算时load=0.5
,根据calc_load_tasks
值获取不同的active
,中间进入NO_HZ
模式空闲了20秒,整个计算的值如下图:
3. 运行队列CPU负载
- Linux系统会计算每个tick的平均CPU负载,并将其存储在运行队列中
rq->cpu_load[5]
,用于负载均衡;
下图显示了计算运行队列的CPU负载的处理流程:
最终通过cpu_load_update
来计算,逻辑如下:
- 其中传入的
this_load
值,为运行队列现有的平均负载值。
上图中的衰减因子,是在NO_HZ
模式下去进行计算的。在没有使用tick时,从预先计算的表中计算负载值。Linux内核中定义了两个全局变量:
#define DEGRADE_SHIFT 7
static const u8 degrade_zero_ticks[CPU_LOAD_IDX_MAX] = {0, 8, 32, 64, 128};
static const u8 degrade_factor[CPU_LOAD_IDX_MAX][DEGRADE_SHIFT + 1] = {
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 64, 32, 8, 0, 0, 0, 0, 0 },
{ 96, 72, 40, 12, 1, 0, 0, 0 },
{ 112, 98, 75, 43, 15, 1, 0, 0 },
{ 120, 112, 98, 76, 45, 16, 2, 0 }
};
衰减因子的计算主要是在delay_load_missed()
函数中完成,该函数会返回load * 衰减因子
的值,作为上图中的old_load
。
计算方式如下:
4. PELT
PELT, Per-entity load tracking
。在Linux引入PELT
之前,CFS调度器
在计算CPU负载时,通过跟踪每个运行队列上的负载来计算;在引入PELT
之后,通过跟踪每个调度实体的负载贡献来计算。(其中,调度实体:指task或task_group
)
4.1 PELT计算方法
总体的计算思路:
将调度实体的可运行状态时间(正在运行+等待CPU调度运行),按1024us
划分成不同的周期,计算每个周期内该调度实体对系统负载的贡献,最后完成累加。其中,每个计算周期,随着时间的推移,需要乘以衰减因子y进行一次衰减操作。
先来看一下每个调度实体的负载贡献计算公式:
- 当前时间点的负载贡献 = 当前时间点负载 + 上个周期负载贡献 * 衰减因子;
- 假设一个调度实体被调度运行,运行时间段可以分成三个段
d1/d2/d3
,这三个段是被1024us
的计算周期分割而成,period_contrib
是调度实体last_update_time
时在计算周期间的贡献值,; - 总体的贡献值,也是根据
d1/d2/d3
来分段计算,最终相加即可; y
为衰减因子,每隔1024us
就乘以y来衰减一次;
计算的调用流程如下图:
- 函数主要是计算时间差,再分成d1/d2/d3来分段计算处理,最终更新相应的字段;
decay_load
函数要计算val * y^n
,内核提供了一张表来避免浮点运算,值存储在runnable_avg_yN_inv
数组中;
static const u32 runnable_avg_yN_inv[] = {
0xffffffff, 0xfa83b2da, 0xf5257d14, 0xefe4b99a, 0xeac0c6e6, 0xe5b906e6,
0xe0ccdeeb, 0xdbfbb796, 0xd744fcc9, 0xd2a81d91, 0xce248c14, 0xc9b9bd85,
0xc5672a10, 0xc12c4cc9, 0xbd08a39e, 0xb8fbaf46, 0xb504f333, 0xb123f581,
0xad583ee9, 0xa9a15ab4, 0xa5fed6a9, 0xa2704302, 0x9ef5325f, 0x9b8d39b9,
0x9837f050, 0x94f4efa8, 0x91c3d373, 0x8ea4398a, 0x8b95c1e3, 0x88980e80,
0x85aac367, 0x82cd8698,
};
Linux中使用struct sched_avg
来记录调度实体和CFS运行队列的负载信息,因此struct sched_entity
和struct cfs_rq
结构体中,都包含了struct sched_avg
,字段介绍如下:
struct sched_avg {
u64 last_update_time; //上一次负载更新的时间,主要用于计算时间差;
u64 load_sum; //可运行时间带来的负载贡献总和,包括等待调度时间和正在运行时间;
u32 util_sum; //正在运行时间带来的负载贡献总和;
u32 period_contrib; //上一次负载更新时,对1024求余的值;
unsigned long load_avg; //可运行时间的平均负载贡献;
unsigned long util_avg; //正在运行时间的平均负载贡献;
};
4.2 PELT计算调用
PELT
计算的发生时机如下图所示:
- 调度实体的相关操作,包括入列出列操作,都会进行负载贡献的计算;
PELT
的算法还在持续的改进中,各个内核版本也存在差异,大体的思路已经在上文中介绍到了,细节就不再深入分析了。
【原创】(二)Linux进程调度器-CPU负载的更多相关文章
- 【原创】(一)Linux进程调度器-基础
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Linux进程调度器的设计--Linux进程的管理与调度(十七)
1 前景回顾 1.1 进程调度 内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来. 调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为 ...
- Linux进程调度器概述--Linux进程的管理与调度(十五)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 1 背景知识 1.1 什么是调度器 ...
- linux里的CPU负载
昨天查看Nagios警报信息,发现其中一台服务器CPU负载过重,机器为CentOS系统.信息如下: 2011-2-15 (星期二) 17:50 WARNING - load average: 9.73 ...
- Linux系统排查——CPU负载篇
本随笔介绍CPU负载的排查手段. 查看系统负载的工具:uptime,w,都能查看系统负载,系统平均负载是处于运行或不可打扰状态的进程的平均数, 可运行:运行态,占用CPU,或就绪态,等待CPU调度. ...
- 【原创】(三)Linux进程调度器-进程切换
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Linux如何查看CPU负载
负载(load)是Linux机器的一个重要指标,直观了反应了机器当前的状态.如果机器负载过高,那么对机器的操作将难以进行. linux的负载高,主要是由于CPU使用.内存使用.IO消耗三部分构成.任意 ...
- Nginx学习系列二Linux下Nginx实现负载均衡
关于在本地虚拟机(VMware 14)下安装Linux同时安装Nginx,请参考Nginx学习系列之搭建环境 1.启动Nginx 在Nginx安装成功的前提下,启动Nginx 已root模式登陆(权限 ...
- 【原创】(五)Linux进程调度-CFS调度器
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
随机推荐
- 交换机广播风暴,STP生成树协议,端口聚合
交换机(工作在数据链路层)具有学习功能: 一台刚重启的交换机上的mac地址表为空,根据数据包的来源,目的地来学习MAC地址与端口的映射关系映射关系,对于MAC地址表之中已有的就不管了,对未知端 ...
- react与redux的一点心得(理解能力有限,蜗牛进度)
Redux是一款状态管理库,并且提供了react-redux库来与React亲密配合, 但是总是傻傻分不清楚这2者提供的API和相应的关系.这篇文章就来理一理. 如果要用一句话来概括Redux,那么可 ...
- 关于redis分布式锁
Lock 分布式锁 1.安全属性:互斥,不管任何时候,只有一个客户端能持有同一个锁. 2.效率属性A:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区. 3.效率属性B:容错,只 ...
- Spring注解:InitBinder
注解 InitBinder 是用来初始化绑定器Binder的,而Binder是用来绑定数据的,换句话说就是将请求参数转成数据对象. @InitBinder用于在@Controller中标注于方法,表示 ...
- canal 基于Mysql数据库增量日志解析
canal 基于Mysql数据库增量日志解析 1.前言 最近太多事情 工作的事情,以及终身大事等等 耽误更新,由于最近做项目需要同步监听 未来电视 mysql的变更了解到公司会用canal做增量监 ...
- cogs 397. [USACO Oct09] 热浪 Dijkstra
397. [USACO Oct09] 热浪 ★☆ 输入文件:heatwvx.in 输出文件:heatwvx.out 简单对比时间限制:1 s 内存限制:128 MB 德克薩斯純樸的民眾 ...
- window 10 安装Oracle odac 64位
下载地址:https://www.oracle.com/cn/database/technologies/windows/downloads.html 可以下载XCopy版,也可以继续往下看下载安装文 ...
- SliverAppBar 介绍及使用
SliverAppBar控件可以实现页面头部区域展开.折叠的效果,类似于Android中的CollapsingToolbarLayout.先看下SliverAppBar实现的效果,效果图如下: Sli ...
- HTML中使用Vue+Dhtmlxgantt制作任务进度图
HTML中使用Vue+Dhtmlxgantt制作任务进度图 Dhtmlxgantt官网: https://dhtmlx.com/docs/products/dhtmlxGantt/ 参考文章 甘特图配 ...
- jqGrid以setGridParam方式postData,包含历史数据的问题
系统在使用jqGrid时,如果某些页面的查询项是复选框,后台是数组接收的,就会出现传值不正确问题. 1.项目中某查询页面存在的复选框:待处理S1,正在处理S2,已处理S3: 使用jqGrid提交查询数 ...