本文是自己在深入阅读Operating Systems Three Easy Pieces关于Proportional Share(比例份额调度)的知识点总结,后面会不断更新连续每一节,欢迎大家批评指正。
也是面试经典问题总结
0. overview
💡 本节介绍一种不同的调度器→proportional-share(fair-share)比例份额调度器或者称为公平份额的调度器。其想法很简单:不是关注周期时间和响应时间,该调度器而是保证每个进程作业获得**特定比例份额的CPU时间。**
经典的调度有lottery scheduling→彩票调度。其想法很简单:获得彩票的进程应该得到运行,经常运行的进程应该有更多机会去获得彩票。
1. basic idea : random tickets(lottery scheduling)
彩票调度是一个非常基本的概念:彩票ticket,用于表示进程(或用户或其他)应接收的资源份额。
持有彩票很简单:调度程序必须知道总共有多少张彩票(在我们的示例中,有 100 张)。
调度器会随机选一个获奖的彩票(这里假设有100张,则选的号码为0-99),持有获奖彩票的进程能够得到运行。
如A有彩票0到74,B有彩票75-99。则按道理A会运行百分之75的时间。B会运行百分之25
若调度器选获奖的彩票为85,则B开始运行。
这两个工作的时间越长,它们就越有可能达到预期的百分比。
💡 优点:该方法实现简单,不需要记录全局的状态,只要记录总共有多少个ticket和每个进程的ticket数量就好了。 **其不足之处**:调度器随机选一个获奖的彩票号运行,可能不能够满足预期的程序运行的百分比。
1.1 ticket mechanisms
1.2 Implementation
其实现很简单:所需要的只是一个好的随机数生成器来选择获胜的彩票ticket,一个系统进程的数据结构(例如,列表)以及彩票总数。
如此时有三个进程,A,B,C分别有100,50,250的ticket数量,总共有400个ticket。假设调度器随机生成获胜的ticket为300。
其简单的遍历该列表,通过累加每个节点的tix数,直到累加的数大于300,则该节点就是即将运行的进程。
为了能够是程序更加高效,我们可以将其列表排序。
1.3 精确份额的ticket→stride scheduling
原来随机的scheduling的其不足之处:调度器随机选一个获奖的彩票号运行,可能不能够满足预期(不精确)的程序运行的百分比。
这里提出精确的调度方法→stride scheduling步调调度
其思想:系统中的每个作业都有一个步幅stride,与它拥有的票数成反比。初始每个作业有个pass value,初始为0。每次运行时,选择pass value最小的值的作业运行,运行完后将其作业的pass value叠加其stride值。
计算stride步幅:用一个较大数,除以每个作业拥有的ticket数量。
如:A,B,C有100,50,250个ticket,则其stride分别可以计算为用10000除以其每个ticket。→100,200,40。
如上图,初始每个作业的pass value均为0,运行时选择最小的作业进行运行,假设选取A,则运行完后A的pass value叠加成100,此时从pass value在选最小的,假设选B。则B的pass value叠加成200,最后。。。。
最后运行完,发现A,B,C运行的比例和原始的ticket的比例份额事准确一样的。
💡 为什么不用该stride方法呢?而用random彩票调度的方法? **random彩票调度具有stride步幅调度没有的一个很好的属性:没有保存全局状态。** 该步幅调度,需要保存每个作业全局状态信息,如pass value和stride值。**假设此时新来一个进程作业D**,则其初始pass value设置为0,则后面D就会垄断CPU的执行了。
所以对比之下,彩票调度可以更轻松地以合理的方式整合新的作业。
2. The Linux Completely Fair Scheduler (CFS)
当前linux实现了完全公平的调度,提高了效率和可伸缩性。CFS的目标是能够花尽量少的时间做出调度决策。研究表明,调度会使用CPU的5%的时间。
因此,尽可能地减少这种开销是现代调度器架构的一个关键目标。
2.1 CFS basic operation
不像大多数调度器一样,将时间片化成固定的。CFS会将时间片进行变化:公平地将CPU的运行时间均匀划分给每个竞争的进程。当每个进程运行时,会累积其vruntime(虚拟运行时间),随着程序的运行,其vruntime会增加。当调度发生时CFS会选择最低的vruntime进程进行运行。
问题1: CFS什么时候进行调度切换呢?
例如,假设sched-latency = 48 ms,如果有 n = 4 个进程正在运行,CFS 会将sched-latency值除以 n,得出每个进程 12 毫秒的时间片。CFS 然后调度第一个作业并运行它,直到它使用了 12 毫秒的(虚拟)运行时,然后检查是否有一个具有较低 vruntime 的作业来代替运行。在这种情况下,有,CFS 将切换到其他三个工作之一,依此类推。图 9.4 显示了一个示例,其中四个作业(A、B、C、D)分别以这种方式运行两个时间段。然后完成其中两个(C,D),只剩下两个,然后每个以Round-robin方式运行24毫秒。
问题2: 若有太多的进程在运行,那么时间片会不会很短,从而经常发生调度切换,影响其性能?
2.2 CFS控制程序的优先级→nice table?
CFS 还支持对进程优先级的控制,使用户能够或管理员为某些进程提供更高的 CPU 运行份额。它不是通过ticket来做到这一点,而是通过一种经典的UNIX机制来实现的,这种机制被称为进程的nice level。对于进程,nice 参数可以设置为 -20 到 +19 之间的任意位置,默认值为 0。正值意味着优先级较低,负值表示较高优先级;
CFS 将每个过程的nice值映射到一个权重
所以其时间片段计算改为:
假设有两个进程作业A,B,我们更喜欢A,则给A分配低的nice值-5,B分配为0。
则A的权重为3121,B为1024。通过计算,分给A的时间片段是B的三倍。从而改变其份额优先级分配。
除了改变其时间片段,其虚拟时间vruntime的叠加也发生改变:
A的vruntime时间的叠加比B慢三分之一,所以A运行的更久
构建上述权重表的一个聪明方面是,当 nice 值的差异是恒定的时,该表保留了 CPU 比例比率。例如,如果进程 A 的 nice 值是 5(不是 -5),而进程 B 的 nice 值是 10(不是 0),CFS 会以与之前完全相同的方式调度它们。
2.3 CFS using Red-Black tree
如上所述,CFS 的一个主要关注点是效率。对于调度器来说,效率有很多方面,但其中之一就是这么简单:当调度器必须找到下一个要运行的作业时,它应该尽快完成。像列表这样的简单数据结构无法扩展:现代系统有时由数千个进程组成,因此每隔几毫秒就搜索一个长列表是一种浪费。
所以CFS使用的是红黑树。CFS并不把所有进程都保留在这个结构中;相反,只有正在运行的(或可运行的)进程被保留在其中。如果进程进入睡眠状态(例如,等待 I/O 完成,或等待网络数据包到达),则会将其从树中删除并跟踪其他位置。
如:假设有 10 个作业,并且它们具有以下 vruntime 值:1、5、9、10、14、18、17、21、22 和 24。如果我们将这些作业保存在有序列表中,则找到下一个要运行的作业将很简单:只需删除第一个元素即可。但是,将该作业放回列表中时(按顺序),则需要找到合适的插入位置进行插入,需要消耗O(n)的时间。但是使用红黑树只需要O(logn)的时间。提高其效率;
2.4 CFS处理I/O等长时间睡眠的进程
选择最低 vruntime 来运行会导致一些问题,如会出现在长时间休眠的进程作业中。想象两个进程,A 和 B,其中一个 (A) 连续运行,另一个 (B) 进入休眠,睡眠很长一段时间(比如 10 秒)。当 B 醒来时,它的 vruntime 将比 A 晚 10 秒,因此B 现在将在接下来的 10 秒内独占 CPU,同时赶上A,实际上使 A 处于饥饿状态。
解决办法:
当进程唤醒时,CFS 通过更改作业唤醒时的 vruntime来处理这种情况。将其vruntime设置为红黑树中最小的vruntime值。这样避免了其他进程的挨饿现象,但睡眠时间短的作业经常无法获得 CPU 的公平份额。
3. summary
公平共享的调度程序有其公平的问题。一个问题是,这种方法与I / O不是特别吻合;如上所述,偶尔执行 I/O 的作业可能无法获得其公平的 CPU 份额。另一个问题是,他们留下了ticket或优先级分配的难题,即,应该分配多少ticket,或者设置nice值哪个好值?