《Operating System:Three Easy Pieces》阅读笔记<五>——MLFQ策略、Proper-Share策略

561 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

image.png

MLFQ策略

我们已经学习了在假设状态下的最基础调度策略,它们都是为了优化既定指标而粗略设计的调度策略。在实际的操作系统中,调度是一项十分复杂的工作,要同时面对数百个状态不一的进程,还要保证较高的处理速率和较快的响应时间。基于同时优化两个指标(turnaround time和response time) 的目的,科学家设计了一种策略,叫做Multi-level Feedback Queue(MLFQ)策略。它的核心思想是:关注进程在一段时间内行为,并依此进行处理。

基础的设计是使用多个优先队列,让所有进程都在队列中,并遵循5个最基本的规则:

  • 如果进程A所处队列优先级比进程B高,则运行A
  • 如果进程A和进程B在同一个队列,则它们之间遵循RR策略,交替运行
  • 当有进程产生时,放在最高级的队列中
  • 当一个进程用完了在当前队列中的时间份额,则切换到下一级队列中
  • 在一段时间 S 后,所有进程都重置到最高级队列中(Priority Boost)

我们来解释一下它的设计思想,你也许注意到了,MLFQ是真正可以适用于现实系统的调度策略,因为上一节最关键的假设:每个作业的运行时是已知的,总是不成立。因此操作系统面对多个进程的时候,无法一下子得知哪个进程应当被安排到最优先进行,哪个应该是最后进行。解决这一问题的关键是构建一种基于过程的启发式方法,也就是在进程运行的过程中通过它的行为判断应该分配多少CPU资源,上面的规则就是基于这个前提构建出来的。

实际上这些规则的构建过程十分严谨而且比较复杂,有兴趣的可以看原书第7节

启发式方法指人在解决问题时所采取的一种根据经验规则进行发现的方法。其特点是在解决问题时,利用过去的经验,选择已经行之有效的方法,而不是系统地、以确定的步骤去寻求答案。

MLFQ策略成功的实现了turnaround time和response time的双重优化,对于需要长时间运行的进程,能够在S时间后得到一段运行的时间,而交互性强的进程,则总是在较上级的队列中,能够较频繁的访问CPU,但不至于霸占CPU不放。第四条规则让所有进程不管在什么状态下都不能长时间占用CPU,保证了所有进程之间的公平性(fairness)。

在MLFQ策略下的workload如下图所示

由于它优良的性能,许多系统包括UNIX衍生,Solaris、Windows系统都采用不同形式的MLFQ来作为进程的基本调度器。在实际使用时,每个系统都对它做了非常多的配置和改进,在Linux中我们可以在系统文件中配置调度器的参数,例如队列级数、不同队列的RR切片粒度、优先级提升的时间间隔S等等

Proper-Share策略

在上一节我们介绍了基于turnaround time和response time两个指标的MLFQ策略,现在我们引入一种新的体系,基于fair-share理念,也就是CPU时间分块分配的调度策略。这一类策略会给每个进程一个确定百分比的CPU时间,不已优化具体指标为目的,而是直接实现按需分配。我们将讨论三种基于这一理念的调度策略,来学习这种策略的基本思想和构建方法。

这里提了一种fairness的量化方法,大致就是执行时间相同的进程之间结束的时间越接近,则越fair

lottery scheduling

第一种是lottery scheduling,这一策略首先定义了票(tickets)作为分配CPU时间的标准。即给定每个进程一定份额的票,进程所持有的票数占全部票的比例就是该进程在这次时间片切换中获得运行时间的概率。workload如下图所示

简单解释一下上图,我们设定进程A的票数是75,进程B的票数是25,那么每次时间片切换中随机数为0-75则运行A,75-100则运行B

在lottery scheduling中,进程运行时间越长,获得响应比例的时间的准确性越强,且基于随机性的分配有三大好处

  • 可靠(长时间下,如下图,时间越长公平性越高)

  • 轻量(无需存储中间状态)

  • 快速(就算一下概率)

lottery scheduling的基础理念比较简单,也有一些底层机制(ticket mechanisms)是一类的方法收到的基础,性能不错,但是最大的问题是:如何将票分给进程,因为和上面的MLFQ一样,系统没办法提前知道进程应当占用多少CPU资源。

stride scheduling

第二种是stride scheduling,这一策略首先定义每一个进程有一个stride,以此计算出pass,在一个时间片我们在当前运行的进程的counter上累加它的pass,每一次进程切换都选择counter最小的进程开始。workload如下图

基于stride scheduling,CPU时间被精准的分配到每一个进程上。看上去很好,而且它比lottery scheduling更加稳定,不需要长时间的运行也能保持公平性,但是它最大的问题是没有全局状态,stride是怎么初始化的?当有新的进程加入时,counter怎么算?这些问题都关系到进程产生后能否分配到和其它进程一样的CPU资源。

Completely Fair Scheduler

基于stride scheduling,科学家提出了The Linux Completely Fair Scheduler (CFS)。它的基础理念是为每一个进程分配各自不同的时间片,有些像加权版的RR策略,但是要复杂的多,具体的实现规则为:

定义sched latency,代表所有进程的时间片时间总和,再定义min granularity,代表进程的最小时间片,以确保不会在上下文切换(context switch) 上开销太大。定义每个进程的weight,也叫nice level,用固定的表组成,有-20到19共40个权值,对应stride scheduling中的stride,这个值为正数表示优先级较低,为负数表示优先级较高。

还记得吗,进程间切换需要将多个寄存器的值保存再替换,这样的一套操作就叫上下文切换

通过进程的weight,可以计算出本次sched latency中不同进程能够分配到的CPU时间

stride scheduling中的counter在CFS中称为vruntime,在计算出每个进程的时间片后,选vruntime最小的进程开始,vruntime的大小变化和所占的CPU时间呈正相关,因此不会出现进程占用CPU过久的情况,这就是CFS的基本规则。

此时进程表中每个进程都有对应的weight,为了帮助搜索众多进程中weight最小的进程,CFS采用红黑树存储进程信息,将O(n)的搜索时间复杂度降为O(logn)。

当处理那些需要休眠的进程,也就是长时间IO的进程,CFS会将它们恢复时的vruntime设置为树中进程的最小值,这样能够解决休眠进程占用的问题,但是对于频繁休眠的进程,可能会造成其无法获得CPU时间。

通过巧妙的设计和高级数据结构的合理使用,CFS做到了较少的调度开销,提高了效率。此外CFS还有许多高级特性如提高缓存性能的启发式方法、处理多CPU和跨进程组调度等。

小结

通过前面一段的学习,我们初步了解了CPU的虚拟化的原理。首先,了解一系列重要的机制:trap和trap处理程序、计时器中断,以及操作系统和硬件在进程之间切换时如何保存和恢复状态。我们还了解到操作系统总是在确保一直负责机器的运行,通过运行受限保证恶意进程一直是收到限制的。我们还了解了调度器的概念,构建高性能的调度器是提升操作系统性能的关键,目前也存在许许多多的调度器,例如在Linux系统中CFS,BFS,O(1)等调度器一直都占有一定的部分。