Linux内核实现了基于虚拟时钟的完全公平调度算法。
每个调度实体都有一个sched_entity
与之关联的结构,其快照看起来像
结构 sched_entity {
...
u64 执行开始;
u64 sum_exec_runtime;
u64 虚拟运行时;
u64 prev_sum_exec_runtime;
...
}
上述四个属性用于跟踪进程的运行时间,并使用这些属性以及其他一些方法(update_curr()
更新这些方法)来实现虚拟时钟。当一个进程分配给 CPU 时,exec_start
会更新到当前时间,消耗的 CPU 时间记录在sum_exec_runtime
. 当进程从 CPU 中取出时,sum_exec_runtime
值保留在prev_sum_exec_runtime
. sum_exec_runtime
是累计计算的。(意味着它单调增长)。
vruntime
存储进程执行期间在虚拟时钟上经过的时间量。
是如何vruntime
计算的?
忽略所有复杂的计算,如何计算的核心概念是:-
vruntime += delta_exec_weighted;
delta_exec_weighted = delta_exec * (NICE_0_LOAD/load.weight);
这delta_exec
是分配给 CPU 和从 CPU 中取出的进程之间的时间差,而load.weight
进程的权重取决于优先级(Nice Value)。通常,进程的 nice 值增加 1 意味着它会减少 10% 的 CPU 时间,从而减少重量。NICE 值为 0 的过程,权重 = 1024 重新NICE 值为 1 的过程,权重 = 1024/1.25 = 820(大约)
从上面绘制的点
- 所以
vruntime
当一个进程获得 CPU 时增加
- 与
vruntime
较低优先级的进程相比,较高优先级的进程缓慢增加。
运行队列维护在红黑树中,每个运行队列都有一个min_vruntime
与之关联的变量,该变量保存vruntime
运行队列中所有进程中最小的一个。(min_vruntime
只能增加,不能减少,因为进程将被安排)。
红黑树中节点的键是process->vruntime - min_vruntime
调用调度程序时,内核基本上会选择具有最小键(最左侧节点)的任务并为其分配 CPU。
具有较小键的元素将被放置在更左侧,因此被安排得更快。
- 当一个进程在运行时,它
vruntime
会稳步增加,所以它最终会在红黑树中向右移动。因为vruntime
对于更重要的进程来说增加得更慢,所以它们也会更慢地向右移动,所以它们被调度的机会对于不太重要的进程来说更大 - 就像需要的那样。
- 如果一个进程休眠,
vruntime
它将保持不变。因为同时每个队列min_vruntime
会增加,所以睡眠进程在唤醒后会更靠左,因为键(上面提到的)变小了。
因此,如果 CPU 被剥夺,作为较低优先级的进程就没有饥饿的机会,它将拥有其vruntime
最小的,因此密钥将是最小的,因此它会快速移动到树的左侧并因此被调度。