公平类的调度算法强调的是一个公平,比如有5个任务,则每个人分享20%的CPU。这是一种人人平等的理想情况,实际情况下,nice值会决定任务的权重,nice值位于[-20, 19]之间,nice越低,权重越高,应该获得更多的CPU。nice意味着与人为善,比如坐地铁看到老人或者儿童就让座,这样就是nice,显然nice值越高,自己能坐到座位的机会越低(优先级越低);nice值越低,优先级越高。
在内核的Documentation/scheduler/sched-nice-design.rst中实际画了这样一幅图,在Linux内核早期的O(1)算法里,nice值实际改变了任务能获取的时间片的大小:
现在Linux的CFS调度类采用的是权重影响任务运行的vruntime虚拟运行时间的概率。
负载权重基准,就是nice=0的任务对应的权重 1024,任务权重的计算公式为:
从而得到如下权重表(位于kernel/sched/core.c):
CFS采用红黑树(RBTREE)组织调度实体,追求vruntime的公平,每次都跑vruntime最小的调度实体。
下面一个程序创建2个线程,包括主线程在内,3个线程都跑while(1)。
为了屏蔽负载均衡的影响,我们用taskset把它绑定在CPU0上面跑。
用top -H命令看他们的CPU利用率分布,大概是均等的33%:
默认情况下,nice都是0。下面我们把4606 线程renice为-1, 把4608线程 renice为1。
特别留意其中renice -1时候的sudo和renice 1的时候没有sudo,这说明想提升优先级(更多索取)是要权限的,想降低优先级(更多自我奉献)是比较随意的。
重新看CPU利用率的情况,基本上4607的CPU利用率没有变,4606由于提升了权重,CPU利用率变成约为4607的1.25倍;而4608则变地更少,4607约是4608的1.25倍。
CFS的实现依赖于红黑树,试想如果我们用链表而不是红黑树来实现vruntime的排序,插入的开销可能就是O(N)而不是对数复杂度O(log n)。
CFS内部有3种调度策略:SCHED_NORMAL、SCHED_BATCH和SCHED_IDLE
SCHED_NORMAL:常规任务;
SCHED_BATCH:非交互的批处理任务;
SCHED_IDLE:低优先级任务
那么它们是需要3个分开的红黑树,还是1个红黑树上面放入所有的3种策略的任务呢?答案实际是后者。
为了实现SCHED_IDLE的所谓“低优先级”,实际上它只要一个很低的权重就可以了,比如:WEIGHT_IDLEPRIO = 3。
在fair这个sched_class类的wakeup_preempt() callback中,SCHED_IDLE的任务可以被非idle的抢占:
另外,check_preempt_wakeup_fair()也进一步阻止了batch和idle任务去抢占normal任务(也并阻止了batch内部多个任务、idle内部多个任务的互相抢占):
下面的代码,main里面创建2个线程,加上主线程在内,3个线程都while(1)执行死循环。
三个线程的调度策略和nice值如下:
nice值19的权重等于15,是来自于前面的sched_prio_to_weight表。
编译并执行(绑定在一个CPU0上面跑):
top -H看看3个线程的CPU利用率:
这里明显可以看出,SCHED_NORMAL并不会阻止SCHED_BATCH和SCHED_IDLE的运行,而且由于11697和11698都是权重15,它们的CPU利用率相同(尽管他们的调度策略分别是SCHED_NORMAL和SCHED_BATCH),它们的CPU利用率都是权重为3的SCHED_IDLE策略的11699的5倍。它们这3者主要的区别,主要还是任务醒来时候谁可以抢占谁的问题上(本例中3个线程都是无睡眠的);其他的,还是按照CFS的权重来跑的。我们也可以用chrt来验证一下3个线程的调度策略: