进程调度算法详解

🏞️1. 调度指标

指标是我们用来衡量某些东西的东西,在进程调度中,有一些不同的指标是有意义的,现在,让我们来定义一个指标:周转时间. 任务的周转时间定义为任务的完成时间减去任务到达系统的时间,更正式的周转时间定义T周转时间是:T周转时间 = T完成时间 - T到达时间.

我们假设所有任务在同一时间到达,那么T到达时间 = 0,因此T周转时间 = T完成时间,随着放宽假设,这个情况将改变.

🌁2. 先进先出(FIFO)

我们可以实现的最基本的算法,被称为先进先出调度,也称为先到先服务.FCFS

FIFO有一些积极的特性:它很简单,而且易于实现.

我们来看一个简单的例子:假设有三个工作A,B,C在大致相同的时间到达系统,因为FIFO必须将某个工作放在前面,所以我们假设当它们都同时到达时,AB早一点点,然后BC早一点点,假设每个工作运行10s,这些工作的平均周转时间是多少?

image-20221115204445419

A在10s时完成,B在20s时完成,C在30s时完成,因此这三个任务的平均周转时间是(10 + 20 + 30)/ 3 = 20.

现在放宽假设,不再认为每个任务的运行时间相同,FIFO表现如何?

依然举一个例子:A运行100s,B和C运行10s.

image-20221115204836354

系统的平均周转时间是比较高的:(100 + 110 + 120) / 3 = 110.

这个问题通常被称为护航效应,一些耗时较少的潜在资源消费者被排在重量级的资源消费者之后.

总结:非抢占式的调度算法,按照请求的顺序进行调度,有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长.

🌠3. 最短作业优先(SJF)

这个名称很容易记住,因为它完全描述了这个策略:先运行最短的任务,然后是次短的任务,如此下去.

我们用上面的例子,但以SJF作为调度策略,下图展示了A、B和C的结果,它清楚的说明了为什么在考虑平均周转时间的情况下,SJF调度策略更好,仅通过在A之前运行BCSJF将平均周转时间从110s降到了50s.

image-20221115205924668

事实上,考虑所有工作同时到达的假设,我们可以证明SJF确实是一个最优调度算法.

抢占式调度程序:在过去的批处理计算中,开发了一些非抢占式的调度程序,这样的系统会将每项工作做完,再考虑是否运行新工作,几乎所有现代化的调度程序都是抢占式的,非常愿意停止一个进程以运行另一个进程.

因此,现在我们假设工作可以随时到达,而不是同时到达,这导致了什么问题?

在这里我们可以再次用一个例子来说明问题,现在,假设A在t = 0时到达,且需要运行100s,而BCt = 10s时到达,且各需要运行10s,用纯SJF,得到如下图所示的调度:

image-20221115210655918

从图中可以看出,即使B和C在A不久后到达,它们仍然被迫等待A完成,从而遭遇同样的护航问题,这三项工作的平均周转时间103.33s,即(100 + (110 - 10) + (120 - 10)) / 3.

总结:非抢占式的调度算法,按估计运行时间最短的顺序进行调度.

长作业有可能会饿死,处于一直等待短作业执行完毕的状态,因为如果一直有短作业到来,那么长作业永远得不到调度.

🌌4. 最短剩余时间优先(STCF)

有一个调度程序是这样做的:向SJF添加抢占,称为最短剩余时间优先(STCF),每当新工作进入系统时,它就会确定剩余工作和新工作中,谁的剩余时间最少,然后调度该工作,因此,在我们的例子中,STCF将抢占A并运行BC,以完成,只有在它们完成后,才能调度A的剩余时间.

image-20221115211750822

🌿5. 新度量指标:响应时间

因此,如果我们知道任务长度,而且任务只使用CPU,而我们唯一的衡量是周转时间,STCF将是一个很好的策略. 事实上,对于许多早期批处理系统,这些类型的调度算法有一定的意义,然而,引入分时系统改变了这一切,现在,用户将会坐在终端前面,同时也要求系统的交互性好,因此,一个新的度量标准诞生了响应时间

响应时间定义为从任务到达系统到首次运行的时间,更正式的定义是:

T响应时间 = T首次运行 - T到达时间

🍁6. 轮转(RR)

轮转的基本思想很简单:RR在一个时间片(有时候称为调度量子)内运行一个工作,然后切换到运行队列的下一个任务,而不是运行一个任务直到结束. 它反复执行,直到所有任务完成,因此,RR有时被称为时间切片.

为了更详细的介绍RR,我们来看一个例子,假设三个任务A、B、C在系统中同时到达,并且它们都希望运行5s,SJF调度算法必须运行完当前任务才可以运行下一个任务,相比之下,1s时间片的RR可以快速的循环工作.

SJF(响应时间不好)

image-20221115213316301

轮转:

image-20221115213415605

RR的平均响应时间为(0 + 1 + 2)/ 3 = 1;SJF算法平均响应时间是(0 + 5 + 10)/ 3 = 5;

时间片长度对于RR是至关重要的,越短,RR在响应时间上表现越好,然而,时间片太短是有问题的:突然上下文切换的成本将影响整体性能,因此,系统设计者需要权衡时间片的长度,使其足够长,以便摊销上下文切换成本,而又不会使系统不及时响应.

总结:将所有就绪进程按FCFS的原则排成一个队列,每次调度时,把CPU时间分配给队首进程,该进程可以执行一个时间片,当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送到就绪队列的末尾,同时继续把CPU时间分配给队首的进程.

🍃7. 优先级调度

为每个进程分配一个优先级,按优先级进行调度,为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级.

⛺8. 多级反馈队列(MLFQ)

📖8.1 基本规则

1962年,Corbato首次提出多级反馈队列,应用于兼容时分共享系统(CTSS).

多级反馈队列需要解决两方面的问题:
首先,它要优化周转时间,这通过先执行短工作来完成,然而,操作系统通过不知道工作要运行多久,而这又是SJF(或STCF)等算法所必需的.

其次,MLFQ希望给交互用户很好的交互体验,因此,需要降低响应时间,然而,像轮转这样的调度算法虽然降低了响应时间,周转时间却很差.

MLFQ基本规则:

MLFQ中有许多独立的队列,每个队列有不同的优先级. 任何时刻,一个工作只能存在于一个队列中,MLFQ总是优先执行较高优先级的工作(即在较高级队列中的工作).

当然,每个队列中可能有多个工作,因此具有同样的优先级,在这种情况下,我们就对这些工作采用轮转调度.

因此,MLFQ调度策略的关键在于如何设置优先级,MLFQ没有为每个工作指定不变的优先级,而是根据观察到的行为调整它的优先级.

例如,如果一个工作不断放弃CPU去等待键盘输入,这是交互型进程的可能行为,MLFQ因此会让它保持高优先级,相反,如果一个工作长时间的占用CPU,MLFQ会降低其优先级,通过这种方式,MLFQ在进程运行过程中学习其行为,从而利用工作的历史来预测它未来的行为.

所以,我们得到了两条基本规则

  • 规则1:如果A的优先级 > B的优先级,运行A
  • 规则2:如果A的优先级 = B的优先级, 轮转运行A和B.

image-20221115223222727

但是存在这种情况:由于A和B具有最高优先级,调度程序将交替的调度它们,可怜的C和D永远都没有机会运行.

📖8.2 改变优先级

工作负载:既有运行时间很短,频繁放弃CPU的交互型工作,也有需要很多CPU时间、响应时间却不重要的长时间计算密集型工作.

所以,我们尝试优先级调整算法:

  • 规则3:工作进入系统时,放在最高优先级
  • 规则4a:工作用完整个时间片后,降低其优先级(移入下一个队列)
  • 规则4b:如果工作在其时间片以内主动放弃CPU,则优先级不变.

实例1:单个长工作

image-20221115224047257

该工作首先进入最高优先级,执行一个10ms的时间片后,调度程序将工作的优先级减一,因此进入Q1,在Q1执行一个时间片后,最终降低优先级进入系统的最低优先级,一直留在那里.

实例2:来了一个短工作

image-20221115224319892

A是一个长时间运行的CPU密集型工作,B是一个运行时间很短的交互型工作,假设A执行一段时间后B到达,会发生什么?

A(黑色)在最低优先级队列执行(长时间运行的CPU密集型工作),B(灰色)在时间T = 100时到达,并被加入最高优先级队列,由于它的运行时间很短,经过两个时间片,在被移入最低优先级队列之前,B执行完毕,然后A继续运行.

通过这个例子,大概体会到:如果不知道工作是短工作还是长工作,那么就在开始的时候假设其是短工作,并赋予最高优先级,如果确实是短工作,则很快会执行完毕,否则将被慢慢移入低优先级队列,而这时该工作也被认为是长工作了,通过这种方式:MLFQ近似于SJF.

实例3:如果有I/O呢?

image-20221115225253235

上图展示了这个运行过程,交互型工作 B(用灰色表示)每执行 1ms 便需要进行 I/O操作,它与长时间运行的工作 A(用黑色表示)竞争 CPU. MLFQ 算法保持 B 在最高优先级,因为 B 总是让出 CPU。如果 B 是交互型工作,MLFQ 就进一步实现了它的目标,让交互型工作快速运行.

📖8.3 当前MLFQ的一些问题

首先,会有饥饿(starvation)问题。如果系统有“太多”交互型工作,就会不断占用CPU,导致长工作永远无法得到 CPU(它们饿死了)。即使在这种情况下,我们希望这些长工作也能有所进展

其次,聪明的用户会重写程序,愚弄调度程序(game the scheduler)。愚弄调度程序指的是用一些卑鄙的手段欺骗调度程序,让它给你远超公平的资源。上述算法对如下的攻击束手无策:进程在时间片用完之前,调用一个 I/O 操作(比如访问一个无关的文件),从而
主动释放 CPU。如此便可以保持在高优先级,占用更多的 CPU 时间。做得好时(比如,每运行 99%的时间片时间就主动放弃一次 CPU),工作可以几乎独占 CPU.

📖8.4 提升优先级

让我们试着改变之前的规则,看能否避免饥饿问题。要让 CPU 密集型工作也能取得一些进展(即使不多),我们能做些什么?

一个简单的思路是周期性地提升(boost)所有工作的优先级:

  • 规则5:经过一段时间S,就将系统中所有工作重新加入最高优先级队列.

新规则一下解决了两个问题:

首先,进程不会饿死——在最高优先级队列中,它会以轮转的方式,与其他高优先级工作分享 CPU,从而最终获得执行。

其次,如果一个 CPU 密集型工作变成了交互型,当它优先级提升时,调度程序会正确对待它

📖8.5 更好的计时方式

现在还有一个问题要解决:如何阻止调度程序被愚弄?可以看出,这里的元凶是规则4a4b,导致工作在时间片以内释放 CPU,就保留它的优先级。那么应该怎么做?

  • 规则4:一旦工作用完了其在某一层中的时间配额(无论中间主动放弃多少次CPU),就降低其优先级.

image-20221115230400029

上图对比了在规则 4a、4b 的策略下(左图),以及在新的规则 4(右图)的策略下,同样试图愚弄调度程序的进程的表现。没有规则 4 的保护时,进程可以在每个时间片结束前发起一次 I/O 操作,从而垄断 CPU 时间。有了这样的保护后,不论进程的 I/O 行为如何,都会慢慢地降低优先级,因而无法获得超过公平的 CPU 时间比例

本章包含了一组优化的MLFQ规则:

  • 规则1:如果 A 的优先级 > B 的优先级,运行 A(不运行 B)
  • 规则2:如果 A 的优先级 = B 的优先级,轮转运行 A 和 B
  • 规则3:工作进入系统时,放在最高优先级(最上层队列)
  • 规则4:一旦工作用完了其在某一层中的时间配额(无论中间主动放弃了多少次CPU),就降低其优先级(移入低一级队列)
  • 规则5:过一段时间 S,就将系统中所有工作重新加入最高优先级队列

总结:一个进程需要执行100个时间片,如果采用时间片轮转调度算法,那么需要交换100
多级反馈队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,进程在第一个队列没执行完,就会被移入到下一个队列,这种方式下,之前的进程只需要交换7
每个队列的优先级也不同,最上面的优先级最高,因此只有上一个队列没有进程在排队,才能调度当前队列上的进程.

可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合.

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉默.@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值