Previous slide Next slide Toggle fullscreen Open presenter view
第八讲 多处理器调度
第四节 Linux CFS 调度
完全公平调度(CFS, Completely Fair Scheduler)
向勇 陈渝 李国良 任炬
2024年秋季
课程幻灯片列表
CFS的背景
O(1)和O(n)都将CPU资源划分为时间片
采用固定额度 分配机制,每个调度周期的进程可用时间片是确定的
调度周期结束被重新分配
O(1)调度器本质上是MLFQ (multi-level feedback queue)算法思想
需求
根据进程的运行状况 判断它属于IO密集型还是CPU密集型,再做优先级奖励和惩罚
这种推测本身存在误差,场景越复杂判断难度越大
CFS的背景
匈牙利人Ingo Molnar所提出和实现CFS调度算法
CFS 的思路
摒弃固定时间片分配,采用动态时间片分配
每次调度中进程可占用的时间与进程总数、总CPU时间、进程权重等均有关系,每个调度周期的值都可能会不一样
每个进程都有一个nice值, 表示其静态优先级
CFS 的思路
把 CPU 视为资源 ,并记录下每一个进程对该资源使用的情况
在调度时,调度器总是选择消耗资源最少 的进程来运行(公平分配)
由于一些进程的工作会比其他进程更重要,这种绝对的公平有时也是一种不公平
CFS 调度思想
虚拟运行时间(vruntime):CFS 为每个进程维护一个 vruntime 值,该值表示进程应该获得的 CPU 时间量 。
进程调度:每次调度vruntime最小的进程 ,使得每个进程根据vruntime相互追赶,期望每个进程vruntime接近,保证公平。
vruntime增长速度 :进程的 nice 值(反映进程的相对优先级)会影响其 vruntime 的增长速率。nice值越低,权重越高,其vruntime增加得越慢。
时间片管理:CFS 为每个进程分配一个时间片,当进程用完其时间片时,会被放回就绪队列的末尾。时间片大小 会根据系统的负载 和进程的权重 动态调整。
CFS 的进程运行时间动态分配
根据各个进程的优先级权重分配运行时间
进程权重越大, 分到的运行时间越多
分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重总和
调度周期
将所有处于 TASK_RUNNING 态进程都调度一遍的时间
CFS 的相对公平性
系统中两个进程 A,B,权重分别为 1, 2,调度周期设为 30ms,
A 的 CPU 时间为:30ms * (1/(1+2)) = 10ms
B 的 CPU 时间为:30ms * (2/(1+2)) = 20ms
在这 30ms 中 A 将运行 10ms,B 将运行 20ms
它们的运行时间并不一样。 公平怎么体现呢?
CFS 的虚拟时间vruntime
virtual runtime(vruntime):记录着进程已经运行的时间
vruntime是根据进程的权重将运行时间放大或者缩小一个比例。
vruntime = 实际运行时间 * 1024 / 进程权重
1024是nice为0的进程的权重,代码中是NICE_0_LOAD
所有进程都以nice为0的进程的权重1024作为基准,计算自己的vruntime增加速度
CFS 的虚拟时间vruntime
以上面A和B两个进程为例,B的权重是A的2倍,那么B的vruntime增加速度只有A的一半。
vruntime = (调度周期 * 进程权重 / 所有进程总权重) * 1024 / 进程权重
= 调度周期 * 1024 / 所有进程总权重
虽然进程的权重不同,但它们期望的vruntime应该是一样的 ,与权重无关。
CFS 的虚拟时间计算
所有进程的vruntime增长速度宏观上看应该是同时推进的,就可以用这个vruntime来选择运行的进程。
进程的vruntime值较小说明它以前占用cpu的时间较短,受到了“不公平”对待,因此下一个运行进程就选择它。
这样既能公平 选择进程,又能保证高优先级 进程获得较多的运行时间。
CFS 的虚拟时间计算示例
CFS让每个调度实体(进程或进程组)的vruntime互相追赶,而每个调度实体的vruntime增加速度不同,权重越大的增加的越慢,这样就能获得更多的cpu执行时间。
A每6个时间片执行1个时间片,B每3个时间片执行1个时间片,C每2个时间片执行1个时间片
vruntime:
A: 0 6 6 6 6 6 6 12 12 12 12 12 12
B: 0 0 3 3 3 6 6 6 9 9 9 12 12
C: 0 0 0 2 4 4 6 6 6 8 10 10 12
调度: A B C C B C A B C C B C
红黑树:CFS中进程vruntime数据结构
Linux 采用了红黑树记录下每一个进程的 vruntime
在多核系统中,每个核一棵红黑树
调度时,从红黑树中选取vruntime最小的进程出来运行
参考:红黑树插入/删除操作
CFS 的进程权重
权重由 nice 值确定,权重跟进程 nice 值一一对应
通过全局数组 prio_to_weight 来转换
CFS中新创建进程的 vruntime如何设置?
如果新进程的 vruntime 初值为 0 的话,比老进程的值小很多,那么它在相当长的时间内都会保持抢占 CPU 的优势,老进程就要饿死了,这显然是不公平的。
CFS中新创建进程的 vruntime设置
每个 CPU 的运行队列 cfs_rq 都维护一个min_vruntime 字段
记录该运行队列中所有进程的 vruntime 最小值
新进程的初始vruntime 值设置为它所在运行队列的min_vruntime
CFS中休眠进程的 vruntime 一直保持不变吗?
如果休眠进程的 vruntime 保持不变,而其他运行进程的 vruntime 一直在推进,那么等到休眠进程终于唤醒的时候,它的 vruntime 比别人小很多,会使它获得长时间抢占 CPU 的优势,其他进程就要饿死了。
CFS中休眠进程的vruntime
在休眠进程被唤醒时重新设置 vruntime 值,以 min_vruntime 值为基础,给予一定的补偿 ,但不能补偿太多。
CFS中休眠进程在唤醒时会立刻抢占 CPU 吗?
休眠进程在醒来的时候有能力抢占 CPU 是大概率事件,这也是 CFS 调度算法的本意,即保证交互式进程的响应速度,交互式进程 等待用户输入会频繁休眠。
CFS中休眠进程在唤醒时会立刻抢占 CPU 吗?
主动休眠的进程 同样也会在唤醒时获得补偿,这类进程往往并不要求快速响应,它们同样也会在每次唤醒并抢占,这有可能会导致其它更重要的应用进程被抢占,有损整体性能。
sched_features 的 WAKEUP_PREEMPT 位表示禁用唤醒抢占特性,刚唤醒的进程不立即抢占 运行中的进程,而是要等到运行进程用完时间片之后
CFS中的进程在 CPU 间迁移时 vruntime 会不会变?
在多 CPU 的系统上,不同的 CPU 的负载不一样,有的 CPU 更忙一些,而每个 CPU 都有自己的运行队列,每个队列中的进程的vruntime 也走得有快有慢 ,每个CPU运行队列的 min_vruntime 值,都会有不同
CFS中的进程迁移
当进程从一个 CPU 的运行队列中出来时 ,它的 vruntime 要减去 队列的 min_vruntime 值;
当进程加入 另一个 CPU 的运行队列时,它的vruntime 要加上 该队列的 min_vruntime 值。
CFS的vruntime 溢出问题
vruntime 的类型 usigned long
进程的虚拟时间是一个递增的正值,因此它不会是负数,但是它有它的上限,就是unsigned long 所能表示的最大值
如果溢出了,那么它就会从 0 开始回滚,如果这样的话,结果会怎样?
CFS 的vruntime 溢出示例
unsigned char a = 251 ;
unsigned char b = 254 ;
b += 5 ;
unsigned char a = 251 ;
unsigned char b = 254 ;
b += 5 ;
signed char c = a - 250 ,
signed char d = b - 250 ;
参考:无符号数的有符号比较
万字长文,锤它!揭秘Linux进程调度器 https://www.eet-china.com/mp/a111242.html
- CFS 不计算优先级,而是通过计算进程消耗的 CPU 时间(标准化以后的虚拟 CPU 时间)来确定谁来调度。从而到达所谓的公平性。
![bg right:40% 100%](figs/prio-to-weight.png)
CFS(Completely Fair Scheduler) https://www.jianshu.com/p/1da5cfd5cee4
![bg right 100%](figs/rbtree.png)
O(n)、O(1)和CFS调度器 http://www.wowotech.net/process_management/scheduler-history.html
Virtual runtime = (physical runtime) X (nice value 0的权重)/进程的权重
通过上面的公式,我们构造了一个虚拟的世界。二维的(load weight,physical runtime)物理世界变成了一维的virtual runtime的虚拟世界。在这个虚拟的世界中,各个进程的vruntime可以比较大小,以便确定其在红黑树中的位置,而CFS调度器的公平也就是维护虚拟世界vruntime的公平,即各个进程的vruntime是相等的。
![bg right 100%](figs/rbtree.png)