转自:https://blog.csdn.net/liglei/article/details/82896765
- 进程类型
交互是进程:人机交互进程,如鼠标键盘,触摸屏,系统响应越快越好
批处理进程:占用较多系统资源,如编译代码
实时进程:对延时有严格要求
- 调度策略与调度器
用户进程的调度策略 调度器
SCHED_NORMAL cfs
SCHED_BATCH cfs
SCHED_FIFO realtime
SCHED_RR realtime
SCHED_IDLE idle
SCHED_DEADLINE deadline
- 内核中进程的权重
struct load_weight {
unsigned long weight; //prio_to_weight[40]
u32 inv_weight; //prio_to_wmult[40]
};
- 用户空间nice值、内核进程优先级和权重关系
prio 动态优先级 0~139 调度器最终使用的优先级值
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,
};
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,
};
prio_to_wmult[i]=2^32/prio_to_weight[i]
nice_0_weight =1024, 每降低一个nice级别,优先级提高一个级别,相应进程多获得10% cpu时间;每升高一个nice级别,优先级降低一个级别,相应进程少获得10% cpu时间。如A和B进程的nice值为0,权重都是1024,各获得50%的cpu时间。如果A进程增加一个nice值,权重变为820,B进程nice不变,权重不变,则A进程获得cpu时间:820/(1024+820)≈44.5%,B进程获得cpu时间:1024/(1024+820)≈55.5%。cpu时间差约为10%
- vruntime
在2.6.23版本引入cfs调度算法,引入虚拟时间:
vruntime = delta_exec * nice_0_weight / weight
OR
vruntime = (delta_exec * (nice_0_weight * inv_weight)) >> 32
nice_0_weight = 1024
weight = prio_to_weight[i]
inv_weight = prio_to_wmult[i]
inv_weight = 2^32/weight
进程优先级越大,进程权重weight值越大,对比经过实际运行时间来说,虚拟时间增长越慢(越小);cfs中的就绪队列以vruntime为键值的红黑树,按虚拟时间从小到大排序,虚拟时间最小的进程最先被调度,相互追赶,以达到虚拟运行时间相同的目的。因而优先级越大的进程,scale到真实时钟的时间更长,也可以理解为按weight比重给进程分配cpu时间。
- 为什么引入PELT算法?
调度算法需要尽量准确的推测每个进程对系统的需求,才能更好的完成调度任务。因此,只能通过进程过去的调度信息-即主要是进程的历史负载,作为该进程未来对CPU需求的依据。
3.8版本之前的内核CFS调度器在计算CPU load的时候采用的是跟踪每个运行队列上的负载(per-rq load tracking)需要注意的是:CFS中的“运行队列”实际上是有多个,至少每个CPU就有一个runqueue。而且,当使用“按组调度”( group scheduling)功能时,每个控制组(control group)都有自己的per-CPU运行队列。对于per-rq的负载跟踪方法,调度器可以了解到每个运行队列对整个系统负载的贡献。这样的统计信息足以帮助组调度器(group scheduler)在控制组之间分配CPU时间,但从整个系统的角度看,我们并不知道当前负载来自何处。除此之外,per-rq的负载跟踪方法还有另外一个问题,即使在工作负载相对稳定的情况下,跟踪到的运行队列的负载值也会变化很大。
per-entity load tracking有什么好处?
有了Per-entity负载跟踪机制,在没有增加调度器开销的情况下,调度器现在对每个进程和“调度进程组”对系统负载的贡献有了更清晰的认识。有了更精细的统计数据(指per entity负载值)通常是好的,但人们可能会怀疑这些信息是否真的对调度器有用。
我们可以通过跟踪的per entity负载值做一些有用的事情。最明显的使用场景可能是用于负载均衡:即把runnable进程平均分配到系统的CPU上,使每个CPU承载大致相同的负载。如果内核知道每个进程对系统负载有多大贡献,它可以很容易地计算迁移到另一个CPU的效果。这样进程迁移的结果应该更准确,从而使得负载平衡不易出错。目前已经有一些补丁利用per entity负载跟踪来改进调度器的负载均衡,相信这些补丁会在不久的将来进入到内核主线。
small-task packing patch的目标是将“小”进程收集到系统中的部分CPU上,从而允许系统中的其他处理器进入低功耗模式。在这种情况下,显然我们需要一种方法来计算得出哪些进程是“小”的进程。利用per-entity load tracking,内核可以轻松的进行识别。
内核中的其他子系统也可以使用per entity负载值做一些“文章”。CPU频率调节器(CPU frequency governor)和功率调节器(CPU power governor)可以利用per entity负载值来猜测在不久的将来,系统需要提供多少的CPU计算能力。既然有了per-entity load tracking这样的基础设施,我们期待看到开发人员可以使用per-entity负载信息来优化系统的行为。虽然per-entity load tracking仍然不是一个能够预测未来的水晶球,但至少我们对当前的系统中的进程对CPU资源的需求有了更好的理解。
参考文章:http://www.wowotech.net/process_management/PELT.html
- task负载描述符-struct sched_avg{}
- Linux kernel v3.8
sched_avg->runnable_avg_sum:调度实体在就绪队列里可运行状态下总的衰减累加时间
sched_avg->runnable_avg_period:调度实体在系统中总的衰减累加时间
sched_avg->load_avg_contrib:进程平均负载的贡献度
cfs_rq->runnable_load_avg:用于累加在该就绪队列上所有调度实体的load_avg_contrib总和,在smp负载均衡调度器中用于衡量CPU是否繁忙
task负载计算:load_avg_contrib=runnable_avg_sum*weight/runnable_avg_period
- Linux kernel v4.18
struct sched_avg {
u64 last_update_time
u64 load_sum
u64 runnable_load_sum
u32 util_sum
u32 period_contrib
unsigned long load_avg
unsigned long runnable_load_avg
unsigned long util_avg
struct util_est util_est
} ___cacheline_aligned
load_sum: 调度实体衰减累加负载
load_avg: 调度实体的平均负载
runnable_load_sum:调度实体在runnable时的衰减累加负载
runnable_load_avg: 调度实体在runnable时的平均负载
PELT代码在v4.18.rc5之后单独迁移到kernel/sched/pelt.c文件中
- 调度实体负载总和
进程运行的时间横轴,以1ms为一个period周期,Li是一个调度实体在i个时间周期内的负载,虽然进程已经又运行了i个周期,它对当前系统负载仍有贡献 Li*y^i,y是衰减系数, y^32=0.5,距离当前时间点越远,衰减系数越大,对调度实体总的负载影响越小。
统计多个PI周期内调度实体对系统负载的贡献 L=L0+L1*y+L2*y^2+L3*y^3+......L32*y^32+...... (y^32=0.5
调度实体某一个历史period周期的负载,每经过32ms,都会衰减一半,后边也使用此方法计算经过32ms整数倍的负载衰减。
- 衰减因子
[kernel/sched/sched-pelt.h]
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,
};
runnable_avg_yN_inv[i]=(2^32-1) * y^i
y^32=1/2
i=0~31
y~=0.9785
Static const u32 runnable_avg_yN_org[] = {
0.999, 0.978, 0.957, 0.937, 0.917, 0.897,
……
0.522, 0.510
}
runnable_avg_yN_org[i]=runnable_avg_yN_inv[i]>>32
- 计算第n个周期的衰减值
static u64 decay_load(u64 val, u64 n)
val表示n个周期前的负载值,n表示第n个周期
t0时刻负载为val,t1时刻,红色历史时间区域衰减负载值是:val*y^n=val* runnable_avg_yN_inv[n]>>32
- 查询连续n个周期的累加衰减负载(1<=n<=32)[v4.12中删除 (05296e7535)]
static const u32 runnable_avg_yN_sum[] = {
0, 1002, 1982, 2941, 3880, 4798, 5697, 6576, 7437, 8279, 9103, 9909,10698,11470,12226,12966,13690,14398,15091,15769,16433,17082, 17718,18340,18949,19545,20128,20698,21256,21802,22336,22859,23371,
};
runnable_avg_yN_sum[n]=1024*(y^1+y^2+y^3+……+y^n)
1024:1ms
period=1024us
y~=0.9785; y^32=1/2
- task负载计算:
* d1 d2 d3
* ^ ^ ^
* | | |
* |<->|<----------------->|<--->|
* ... |---x---|------| ... |------|-----x (now)
*
* p-1
* u' = (u + d1) y^p + 1024 Sum y^n + d3 y^0
* n=1
* = u y^p + (Step 1)
* p-1
* d1 y^p + 1024 Sum y^n + d3 y^0 (Step 2)
* n=1
* p-1
* c2 = 1024 Sum y^n
* n=1
*
* inf inf
* = 1024 ( Sum y^n - Sum y^n - y^0 )
* n=0 n=p
u´= (u+delta1)*y^p + 1024* (y^1+y^2+……+y^(p-1)) + delta3*y^0
= u*y^p + delta1*y^p + 1024* (y^1+y^2+……+y^(p-1)) + delta3*y^0
= decay_load(u, p)+ decay_load(delta1, p)+ LOAD_AVG_MAX - decay_load(LOAD_AVG_MAX, p)-1024 + delta3
由此可见,进程负载=历史负载衰减+本次更新的负载累加衰减=udecay+contrib
LOAD_AVG_MAX=47742= 1024+1024*y+1024*y^2+……+y^n+…+y^inf (n>345)
LOAD_AVG_MAX=1024+1024* (y^1+y^2+…+y^(p-1)) + 1024* (y^p+…+y^inf)
1024* (y^p+…+y^inf) =1024*y^p(1+y+y^2+…+y^inf)=y^p*LOAD_AVG_MAX=decay_load(LOAD_AVG_MAX, p)
load_avg = load.weight * load_sum/(LOAD_AVG_MAX-1024+period_contrib)
runnable_load_avg = runnable_weight * runnable_load_sum/(LOAD_AVG_MAX-1024+period_contrib)
util_avg=util_sum/(LOAD_AVG_MAX-1024+period_contrib)
- task负载计算实例
divider = LOAD_AVG_MAX - 1024 + sa->period_contrib
t1时刻平均负载的计算:
load_avg={decay(t0) + contrib(t1-t0)*weight}/divider
runnable_load_avg={decay(t0) + contrib(t1-t0)*weight}/divide
util_avg={decay(t0) + contrib(t1-t0)*weight}/divider
t2时刻平均负载的计算:
load_avg={decay(t1) + contrib(t2-t1)*weight}/divider
runnable_load_avg={decay(t1) + contrib(t2-t1)*weight}/divider
util_avg=decay(t0) /divider (contrib=0)
t4时刻平均负载的计算:
load_avg=decay(t3)/divider
runnable_load_avg=decay(t3)/divider
util_avg=decay(t3) /divider
- task与cfs-rq平均负载的计算
- Question:
V4.15-rc1 Commit: 1ea6c46a23 对task平均负载的计算发生了变化:
se->avg=