文章目录

  • ​​1.CFS​​
  • ​​2.实时调度​​
  • ​​3.CFS性能​​
  • ​​4.CFS与红黑树​​

1.CFS

在内核 V2.6.23 的发布中,完全公平调度程序(CFS)成为默认的 Linux 调度算法。

  • Linux 系统的调度基于调度类
  • 每个类都有一个特定优先级。
  • 内核针对不同的调度类,采用不同的调度算法,以便满足系统与进程的需要。
  • 例如,用于 Linux 服务器的调度准则,也许不同于移动设备的。为了确定应运行哪个进程,调度程序从最高优先级调度类中选择具有最高优先级的任务。

Linux 标准内核实现两个调度类:采用 CFS 调度算法的默认调度类和实时调度类。这里分别讨论这些。当然,新调度类也可添加。

  • CFS 调度程序并不采用严格规则来为一个优先级分配某个长度的时间片,而是为每个任务分配一定比例的 CPU 处理时间。
  • 每个任务分配的具体比例是根据友好值(友好一词源自如下想法:当一个任务增加了它的友好值,如从 0 至 +10,该任务通过降低优先级,进而对其他任务更加友好。)来计算的。
  • 友好值的范围从 -20 到 +19,数值较低的友好值表示较高的相对优先级。具有较低友好值的任务,与具有较高友好值的任务相比,会得到更高比例的处理器处理时间。
  • 默认友好值为 0。

CFS 没有使用离散的时间片,而是采用目标延迟,这是每个可运行任务应当运行一次的时间间隔

  • 根据目标延迟,按比例分配 CPU 时间。除了默认值和最小值外,随着系统内的活动任务数量超过了一定阈值,目标延迟可以增加。

CFS 调度程序没有直接分配优先级。相反,它通过每个任务的变量 vruntime 以便维护虚拟运行时间,进而记录每个任务运行多久。

  • 虚拟运行时间与基于任务优先级的衰减因子有关,更低优先级的任务比更高优先级的任务具有更高衰减速率。
  • 对于正常优先级的任务(友好值为 0),虚拟运行时间与实际物理运行时间是相同的。
  • 因此,如果一个默认优先级的任务运行 200ms,则它的虚拟运行时间也为 200ms。然而,如果一个较低优先级的任务运行 200ms,则它的虚拟运行时间将大于 200ms。同样,如果一个更高优先级的任务运行 200ms,则它的虚拟运行时间将小于 200ms。当决定下步运行哪个任务时,调度程序只需选择具有最小虚拟运行时间的任务。此外,一个更高优先级的任务如成为可运行,就会抢占低优先级任务。

下面分析一下 CFS 调度程序是如何工作的。

  • 假设有两个任务,它们具有相同的友好值。一个任务是 I/O 密集型而另一个为 CPU 密集型。
  • 通常,I/O 密集型任务在运行很短时间后就会阻塞以便等待更多的 I/O;
  • 而 CPU 密集型任务只要有在处理器上运行的机会,就会用完它的时间片。

因此,I/O 密集型任务的虚拟运行时间最终将会小于 CPU 密集型任务的,从而使得 I/O 密集型任务具有更高的优先级。

这时,如果 CPU 密集型任务在运行,而 I/O 密集型任务变得有资格可以运行(如该任务所等待的 I/O 已成为可用),那么 I/O 密集型任务就会抢占 CPU 密集型任务。

2.实时调度

Linux 也实现了实时调度。采用 SCHED_FIFO 或 SCHED_RR 实时策略来调度的任何任务,与普通(非实时的)任务相比,具有更高的优先级。

Linux 采用两个单独的优先级范围,一个用于实时任务,另一个用于正常任务。

  • 实时任务分配的静态优先级为 0〜99,而正常任务分配的优先级为 100〜139。
  • Linux系统的调度优先级
  • Linux进程调度策略_运维

  • 这两个值域合并成为一个全局的优先级方案,其中较低数值表明较高的优先级。
  • 正常任务,根据它们的友好值,分配一个优先级;这里 -20 的友好值映射到优先级 100,而 +19 的友好值映射到 139。

3.CFS性能

Linux CFS 调度程序釆用高效算法,以便选择运行下个任务。

  • 每个可运行的任务放置在红黑树上(这是一种平衡的、二分搜索树,它的键是基于虚拟运行时间的)。
  • 这种树如下图所示:
  • Linux进程调度策略_linux_02

  • 当一个任务变成可运行时,它被添加到树上。当一个任务变成不可运行时(例如,当阻塞等待 I/O 时),它从树上被删除。
  • 一般来说,得到较少处理时间的任务(虚拟运行时间较小)会偏向树的左侧;得到较多处理时间的任务会偏向树的右侧。
  • 根据二分搜索树的性质,最左侧的结点有最小的键值;从 CFS 调度程序角度而言,这也是具有最高优先级的任务。
  • 由于红黑树是平衡的,找到最左侧结点会需要 O(lgN) 操作(这里 N 为树内结点总数)。不过,为高效起见,Linux 调度程序将这个值缓存在变量 rb_leftmost 中,从而确定哪个任务运行只需检索缓存的值。

4.CFS与红黑树

CFS能在真实硬件上模拟出一种“公平的、精确的任务多处理CPU”。

  • 公平,即对于n个正在运行的任务,当这些任务同时不断地运行时,CPU会尽可能分配给他们1/n的处理时间。CFS是一种基于加权公平排队思想的调度算法。
  • 精确,指的是它采用红黑树作为调度的任务队列的数据结构。

红黑树特点

  • 红黑树是一种特殊的二叉搜索树,也就是左边节点都小于根节点都小于右边节点,递归整个树都满足这一点。
  • 也就是说最左边的叶子节点是最小的,最右边的叶子节点是最大的。
  • 红黑树相比二叉搜索树多了红色黑色两个颜色的宏定义,红黑树有以下5个性质:
    (1)每个结点要么是红的要么是黑的。
    (2)根结点是黑的。
    (3)每个叶结点都是黑的。
    (4)如果一个结点是红的,那么它的两个儿子都是黑的。
    (5)对于任意结点而言,其到叶结点的每条路径都包含相同数目的黑结点。
  • 红黑树通过给每个节点引入颜色属性,使得红黑树的查找、插入、删除操作的时间复杂度都是O(log n)

CFS特点

  • CFS使用红黑树结构,来存储要调度的任务队列。
  • 每个节点代表了一个要调度的任务,节点的key即为虚拟时间(vruntime),虚拟时间由这个人物的运行时间计算而来。
  • key越小,也就是vruntime越小的话,红黑树对应的节点就越靠左。
  • CFS scheduler每次都挑选最左边的节点作为下一个要运行的任务,这个节点是“缓存的”——由一个特殊的指针指向;不需要进行O(logn)遍历来查找。也因此,CFS搜索的时间是O(1)。

CFS能很快对交互式进程做出反应的原因如下

  • 对于新任务来说,vruntime = 0
  • vruntime并不是无限小的,有一个最小值来限定。
    假如新进程的vruntime初值为0的话,比老进程的值小很多,那么它在相当长的时间内都会保持抢占CPU的优势,老进程就要饿死了,这显然是不公平的。
  • CFS是这样做的:每个CPU的运行队列 cfs_rq都维护一个min_vruntime字段,记录该运行队列中所有进程的vruntime最小值,新进程的初始vruntime值就以它所在运行队列的min_vruntime为基础来设置,与老进程保持在合理的差距范围内。
  • CFS的唤醒抢占特性:
    (1)休眠进程在唤醒时会获得vruntime的补偿,它在醒来的时候有能力抢占CPU是大概率事件,这也是CFS调度算法的本意,即保证交互式进程的响应速度,因为交互式进程等待用户输入会频繁休眠。
    (2)想象一下当你执行每一个敲击键盘、移动鼠标等交互操作的时候,对于系统来说,这就是来了个新任务->运行时间为0->vruntime为0->被放到调度任务队列红黑树的最左节点->最左节点通过一个特殊的指针指向,且该指针已被缓存

CFS原则:

  • 随着任务的执行,它的运行时间增加,因此vruntime也会变大,它会在红黑树中向右移动(想象一下这个画面);
  • 计算密集型作业将运行很长时间,因此它将移到最右侧;
  • /O密集型作业会运行很短的时间,因此它只会稍微向右移动;
  • 对于更重要的任务,也就是nice值较小的(一般是小于0),他们的移动速度相对慢很多。(相对于nice = 0的任务,nice每小一级,CPU usage就会多10%,“10% effect”)虚拟时钟的滴答声更慢。

CFS的缺点: