const int sched_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值为0的权重NICE_0_LOAD=1024,nice值每差1,权重大约差1.25倍。这里的1.25计算依据来源于nice值差1,运行时间相差10%这样的设计。
虚拟运行时间
解决完每次分配多少时间的问题后,还有一个问题需要解决,就是下一个运行的进程是谁?CFS实现的原则是“完全的公平”,那么高优先级的进程如何保证多一点的运行时间?这就要引入一个概念,叫“虚拟运行时间”了。假设我们希望有如下的情况出现:
高优先级进程运行15ms=低优先级运行5ms
那么我们就将这个相等的量设置为一个“虚拟运行时间”,这样就可以保证高优先级进程运行的时间几乎总是低优先级的3倍。在Linux中,这个虚拟运行时间与实际运行时间的关系就是刚刚提到的权重:
虚拟运行时间=真实运行时间 * NICE_0_LOAD / 进程的权重
Linux在调度元素中定义了vruntime变量(include/linux/sched.h#L453),用于记录进程的累计虚拟运行时间,代码如下:
struct sched_entity {
/* For load-balancing: */
struct load_weight load;
struct rb_node run_node;
struct list_head group_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
u64 nr_migrations;
struct sched_statistics statistics;
进程每次运行完毕后就会更新vruntime变量,至于如何挑选出vruntime最少的进程,这将由红黑树完成。
红黑树是一种自平衡的二叉树,最左的叶子节点永远是key最小的节点。红黑树通过插入(更新)和删除时的操作保证这些原子操作之间红黑树永远是平衡的。
具体的操作参见https://www.jianshu.com/p/e136ec79235c
内核将调度队列上的进程按照vruntime排列成红黑树,这样选取最小vruntime的进程就变为了简单地选出最左边的叶子节点了。
最小vruntime
内核除了红黑树以外还维护一个min_vruntime变量,以记录此时最小的虚拟运行时间。
为什么需要这个min_vruntime?我们设想以下几种状况:
- 新的进程加入了红黑树,那么它的vruntime应当是多少?
- 休眠了10万年的进程等待到了它要求的事件,现在被唤醒了,它还保持原有的vruntime吗?
- 进程被负载均衡迁移到另一个CPU上(另一个调度队列),那么它的vruntime如何改变?
由此可以看出,min_vruntime的作用就是帮助Linux内核解决这些情况。