CFS

136 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情

本节主要讲述普通进程的调度,包括交互进程和批处理进程等。在完全公平调度器(Completely Fair Scheduler,CFS)出现之前,早期Linux内核中曾经出现过两个调度器,分别是O(n)和O(1)调度器。O(n)调度器发布于1992年,该调度器算法比较简洁,从就绪队列中比较所有进程的优先级,然后选择一个优先级最高的进程作为下一个调度进程。每个进程有一个固定时间片,当进程时间片使用完之后,调度器会选择下一个调度进程,当所有进程都运行一遍后再重新分配时间片。该调度器选择下一个调度进程前需要遍历整个就绪队列,花费O(n)时间。 在Linux 2.6.23内核发布之前有一款名为O(1)的调度器,优化了选择下一个进程的时间。它为每个CPU维护一组进程优先级队列,每个优先级一个队列,这样在选择下一个进程时,只需要查询优先级队列相应的位图即可知道哪个队列中有就绪进程,所以查询时间为常数O(1)。O(1)调度器在处理某些交互式进程时依然存在问题,特别是在有一些测试场景下导致交互式进程反应缓慢。另外,它对NUMA的支持也不完善,因此大量难以维护和阅读的代码被加入该调度器代码实现中。Linux内核社区的一位传奇人物Con Kolivas[1]提出了楼梯调度算法来实现公平性,在社区的一番争论之后,Red Hat公司的Ingo Molnar借鉴楼梯调度算法的思想提出了CFS算法。

vruntime的计算

内核使用0~139的数值表示进程的优先级,数值越低,优先级越高。优先级0~99给实时进程使用,100~139给普通进程使用。另外,在用户空间中有一个传统的变量nice,它映射到普通进程的优先级,即100~139。 内核使用load_weight数据结构来记录调度实体的权重(weight)信息。


<include/linux/sched.h>

struct load_weight {
     unsigned long weight;
     u32 inv_weight;
}

其中,weight是调度实体的权重,inv_weight指inverse weight,它是权重的一个中间计算结果,稍后会介绍如何使用。调度实体的数据结构中已经内嵌了load_weight数据结构,它用于描述调度实体的权重。


<include/linux/sched.h>

struct sched_entity {
     struct load_weight load;
...
}

代码中经常通过p->se.load来获取进程p的权重信息。nice值的范围是−20~19,进程默认的nice值为0。这些值类似于级别,可以理解成有40个等级,nice值越高,优先级越低,反之亦然。如果一个CPU密集型的应用程序的nice值从0增加到1,那么它相对于其他nice值为0的应用程序将减少10%的CPU时间。因此,进程每降低一个nice级别,优先级则提高一个级别,相应的进程多获得10%的CPU时间;进程每提升一个nice级别,优先级则降低一个级别,相应的进程少获得10%的CPU时间。为了计算方便,内核约定nice值为0的权重值为1024,其他nice值对应的权重值可以通过查表的方式[2]来获取,内核预先计算好了一个表sched_prio_to_weight [40],表的下标对应nice值−20~19。