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系统的调度优先级 -
这两个值域合并成为一个全局的优先级方案,其中较低数值表明较高的优先级。 -
正常任务,根据它们的友好值,分配一个优先级;这里 -20 的友好值映射到优先级 100,而 +19 的友好值映射到 139。
3.CFS性能
Linux CFS 调度程序釆用高效算法,以便选择运行下个任务。
-
每个可运行的任务放置在红黑树上(这是一种平衡的、二分搜索树,它的键是基于虚拟运行时间的)。 -
这种树如下图所示: -
当一个任务变成可运行时,它被添加到树上。当一个任务变成不可运行时(例如,当阻塞等待 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的缺点:
|