操作系统导论-进程调度

最近读经典书籍少了,总是爱看一些水文博客,不用费脑子想,还总有一种学到了的感觉,其实回味思考下,发现毫无营养。所以开一系列读书笔记,强迫自己读书,从《操作系统导论》开始。坚持每周一部分内容,年前读完。

前面一些知识储备内容,不写了。

运行任务的底层机制是上下文切换,那上层还需要操作系统的调度。为了理解操作系统调度策略,先把问题理想化,把操作系统中的运行的任务作出如下假设:

  1. 每一个任务运行时间相同
  2. 所有任务同时到达
  3. 任务禁止抢占
  4. 所有任务只使用CPU,即无I/O操作
  5. 每个任务时间是已知的

为了比较不同的调度策略,先给出一个衡量指标,对衡量指标也先做简单化处理,即只有一个指标:周转时间(turnaround time)。任务的周转时间定义为任务完成时间减去任务到达系统时间。

我们可以想到并实现的最基本的算法,就是先进先出调度(FIFO)。

假设有三个任务A、B、C,完全满足上述理想化假设条件,都在T0时刻到达系统。因为先进先出必须要将某个任务放在前面,我们假设A>B>C。再假设每个任务执行10s。平均周转时间就是(10+20+30) / 3 = 20。

我们先放开第一个假设条件,即每个任务运行时间不同。我们想一下会有在什么场景发生什么问题。

假设依旧是A、B、C三个任务,同时到达,但是A执行时间100s,B、C执行时间10s。平均周转时间就变成了(100+110+120) / 3 = 110。

发生的这种现象被称为护航效应,一些耗时较少的潜在资源消费者被排在了重量级的资源消费者之后。

我们就需要一个更好的算法,叫做最短任务优先(SJF)。还是上述的场景,在最短任务优先策略下,平均周转时间等于(10+20+120) / 3 = 50。可以看到,在只放开第一个假设时,SFJ确实比FIFO策略在周转时间指标上表现更好。

此时,我们再放开一个假设,任务任务不是同时到达的,又会导致什么问题。

假设依旧A、B、C三个任务,A在t=0时到达,并且需要运行100s。B、C在t=10时到达,并且仅需要运行10s。用SJF策略时,平均周转时间变为(100+(110-10)+(120-10)) / 3 = 103.33。

产生这个问题的一个主要原因在于我们的假设3上,任务不可抢占。我们向SJF策略中增加抢占,就变为了最短完成时间优先(STCF)或者抢占式最短作业优先(PSJF)。每当有新的任务进入时,他都会重新确认剩余任务和新的任务中,谁的剩余时间最少,然后调度该任务。再看在此时,上述场景中平均周转时间等于(120+10+20)/3=50。

但是,我们有很多的任务并不是只关注周转时间,例如交互任务,我们坐在电脑前,敲击一下键盘,需要得到快速的响应。因此,我们就有了一个新的度量指标:响应时间,即任务从到达系统到首次运行的时间。

对于响应时间指标来说,PSJF等上述策略表现都很不好。

为了解决这个问题,又引入了新的调度算法,轮转(RR)。基本思想就是:RR在一个时间片(time slice)内运行一个工作,然后切换到运行队列中的下一个任务,反复执行,直到所有任务结束。

时间片的长度对于RR策略极为重要,越短,RR在响应时间上表现越好,然后时间片太短,上下文切换的成本会影响系统整体性能。上下文切换不仅仅是保存和灰度少量寄存器的内容,还包括CPU高速缓存、TLB、分支预测和其他片上硬件建立的大量状态被刷新。

所以,我们需要权衡时间片的长度,使其足够长以便摊销上下文切换成本,而又不会是系统不及时响应。

当系统操作有固定成本时,通常都会用摊销技术。通过减少成本的频度,系统总得成本会降低。

对于RR策略来说,响应时间指标表现很好,但是周转时间指标表现很差,这是因为RR的目的就是延伸每一个任务,只运行一个任务很短时间,就轮转到下一个任务。而周转时间只关心任务完成时间,所以表现很差是正常。

当我们把所有假设都放开时,即任务有I/O操作,任务时间是未知的,上述所有的调度策略都不再适用。

那我们就需要一种新的调度策略,叫多级反馈队列(MLFQ)。多级反馈队列目的是为了解决两个问题,首先优化周转时间,但是我们又无法得知任务要运行多久。其次给用户交互很好地体验,既缩短响应时间。

那么没有任务长度的先验知识,如何设计呢?多级反馈队列是采用了历史经验预测未来的方式,在操作系统中有很多的地方用到了这种技术,例如分支预测、缓存算法等。如果工作有很明显的阶段性行为,可以采用预测,这种技术很有效。当然也要小心使用,因为预测可能出错,会导致系统更高的代价来恢复。

顾名思义,MLFQ中有多个队列,每个队列有不同的优先级。任何时刻,一个任务只能在一个队列中。MLFQ总是会优先执行较高优先级队列中的任务。

每个优先级的队列内的所有任务采用FIFO的轮转调度。

在MLFQ中,会根据每个任务的行为不停的动态调整每个任务的优先级,例如:如果一个任务不停的放弃CPU去等待键盘输入,这个任务可能是交互型,需要较快的响应时间,MLFQ会使其保持较高的优先级。如果一个任务长时间的占用CPU,则可能是CPU密集型任务,MLFQ会降低其优先级。

那么我们得到MLFQ的两条基本规则:

  • 规则1:如果任务A优先级 > 任务B优先级,调度A
  • 规则2:如果任务A优先级 = 任务B优先级,轮转任务A、B

再看MLFQ是如何改变任务优先级的,如果不对任务优先级改变,低优先级队列中的任务,可能永远不被执行到,导致饿死。

改变优先级有以下三个规则:

  • 规则3:任务进入系统是,放在最高优先级队列
  • 规则4a:任务用完整个时间片后,降低其优先级
  • 规则4b:任务在时间片内主动释放CPU,则不改变其优先级

我们再看下是否可以满足了所有场景。

场景1:单个长时间运行任务

任务刚进入系统时,在最高优先级队列。运行一个时间片后,优先级减一,进入第一级的任务队列......重复一直到任务降低到最底层的队列中,一直留在那里直到完成。

场景2:来了一个短任务

再有长短任务的复杂场景,MLFQ是否可以近似SJF。在场景1中,假设任务A已经到了最低优先级的队列中。此时来了一个任务B,并且加到了最高优先级。此时MLFQ会优先调度任务B,直到任务B执行完,或者也到了最低优先级队列时,才会和任务A轮转调度。

场景3:如果任务有I/O

如果任务在时间片内主动放弃CPU,则任务不降级。假设有任务C,每次都在时间片用完前,释放CPU,则任务C一直在最高优先级队列中,则当任务C从I/O恢复后,每次系统调度都会优先调度任务C。

我们看一下每个场景中,MLFQ表现似乎都很好。但是,我们想一下场景3,如果有大量的类似任务C的任务,则他们会一直在最高优先级中,不停地抢占CPU,导致低级队列中的任务都被饿死。

并且,如果程序员玩阴招,每次在时间片用完前,执行一次I/O调度,比如访问下无关的文件,会导致他的任务永远被优先调度。

那么MLFQ需要另外一个规则,即提升优先度

  • 规则5:经过一段时间S后,把所有任务都提升到最高优先级队列中

规则5,首先解决了任务不会被饿死的问题。并且,当一个CPU密集型任务转变为交互型时,有机会让调度策略重新正确的对待他。

但是时间S的设置又是个问题,S设置得太高,长工作会饥饿;S设置的太低,交互性工作又不会得到合适的CPU时间比例。

这种类似S的值,叫做巫毒常量。

并且需要更改规则4,修正任务的计时方式,即每次重新调度后,不再重新计时,而是累加任务在该队列中运行的总时长。只要是运行总时长超过了自己的配额,即时间片,则降级到下一个队列中。无论是一次用完,还是很多次。

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

新的规则4,保证了更完善的CPU计时方式,可以保证,无论进程I/O行为如何,都可以正确的被降级。

并且,MLFQ还有可调优,比如高优先级的队列,经常是响应型任务,所以需要更短的时间片。低优先级队列,更多是CPU密集型任务,所以需要更长的时间片。

所以MLFQ中,每个优先级队列的时间片长度不是相同的,更高优先级的队列时间片更短,更低优先级的队列时间片更长。

 

以上,循序渐进了解了操作系统的进程调度策略。

像护航效应,在实际工作中经常出现,例如线程池运行任务,有的任务执行时间长,导致任务队列堆积。那么就有了工作窃取思想。

像摊销技术,在性能优化中经常用,比如常见的pipline、合并处理等减少网络I/O。

预测技术更为常见了,缓存、预加载、文件的块读写,JIT的分支预测等等常见的技术,基本都是预测思想。

还有巫毒常量,更是极为常见,比如脏页多久、多大比例刷新?设置大了,怕有数据丢失;设置小了,怕影响性能。所以很多时候,都会从另外一些角度来避免巫毒常量。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值