当进入中断处理程序后(比如完成I/O操作),内核就要决定切换到哪一个进程继续运行。然而怎样决定运行哪一个进程就需要调度器使用调度算法来决定了。调度算法并不是产生于计算机科学中,而是产生于操作管理中并借鉴应用到计算机中。

不同的调度算法有不同的度量方法。这里首先通过周转时间(turnaround time)和响应时间(response time)来考虑算法。在这里我们首先对任务(就是进程,通常称为job)进行如下假设,并不断逐条打破假设来思考更深层次算法。

        1、假设任务的运行时间是一样的。

        2、假设任务是同时到达的。

        3、假设任务总会执行完毕。

        4、每个任务只使用CPU(比如没有I/O请求)。               

        5、假设任务的执行时间是已知的。

 

一、周转时间(turnaround time)衡量的算法

周转时间是一个性能指标,另一个我们感兴趣的指标为公平性。通常这两个指标很难同时达到,比如牺牲公平性换来性能。周转时间的公式定义如下:

周转时间 = 任务完成时刻 - 任务到达时刻

由于我们假定任务是同时到达的,所以在这里:

周转时间 = 任务完成时刻。

(1)FIFO(First In First Out)先到先处理算法

与数据结构中的FIFO的概念一样,假设这时A、B、C三个任务同时到达(B在A到达后在及其短的时间后到达,C同理,此时间忽略不计),此时B就要等待A执行完才可以执行,C要等待A、B执行完才可以执行。现在我们是假设每个任务的运行时间是一样的,所以我们可以得到下图:

                                   

ospf cos值40 怎么算出来的_ospf cos值40 怎么算出来的

根据此图和公式我们不难算出每个任务的平均周转时间为(10+20+30)/3=20。但是现在是在每个任务的运行时间都相同的假设下。当我们打破这个规则,会发生什么呢(比如同样的到达顺序但是A的执行时间比B、C两个任务加起来都长)?

                                      

ospf cos值40 怎么算出来的_ospf cos值40 怎么算出来的_02

这确实及其糟糕,因为B、C处理时间要短的多但是却还要等待如此之长的A任务结束才可以执行。这就好比去市场买早饭但是你突然有急事要办要赶紧卖完去处理事情但是还必须排队一要着急。可算出任务平均周转时间为(100+110+120)/3=110。那么有什么好的办法能继续减少平均周转时间呢?

(2)短任务优先SJT(Shortest Job First)算法

                                       

ospf cos值40 怎么算出来的_优先级_03

很简单,只需要将运行时间短的任务放在运行时间长的任务前先执行即可。但是这种方法也是乐观假设的,因为他满足第二条规则:所有任务同时到达。如果我们打破这个规则,任务会随机到达会怎么样呢?

                                       

ospf cos值40 怎么算出来的_ospf cos值40 怎么算出来的_04

(3)抢占式调度STCF(Shortest Time-to-Completion First)

其实很容易想到,以早饭这个例子来说,你跟老板和前面的顾客说一下差个队就可以了。同样,在执行A时如果B、C到达(时间轴为10的时候),只要暂停A的执行先将较短的B、C执行就可以了。

                                     

ospf cos值40 怎么算出来的_时间片_05

        抢占式调度器会判断新到来的任务和当前任务执行时间的长短并选择执行运行时间短的那一个。可以计算出该算法程序的平均周转时间为(120+(20-10)+(30-10))/3=50。事实上,抢占式调度被现代操作系统广泛使用,非抢占式调度更多的在之前的批处理机器中使用。

二、响应时间(response time)衡量的算法

现在的电脑已经不是批处理形式的了,通常OS会提供一个与用户交互的程序(比如shell),为了良好的使用体验,用户提供一组输入后应尽快提供输出,因此响应时间变的尤其重要。定义响应时间的公式如下:

响应时间 = 任务被调度时刻 - 任务到达时刻

(1)轮转RR(Round Robin)算法

与之前的算法不同,RR算法并不要求任务必须执行完毕才可以切换。相反他把时间切片并为每一个任务分配时间片(time slice),让每个任务只运行一段时间,然后切换至任务队列的下一个任务(A、B、C同时到达):

                                      

ospf cos值40 怎么算出来的_时间片_06

根据上图不难计算出每个任务的平均响应时间为(0+1+2)/3 = 1,这相比于SJF的(0+5+10)/3 = 5来说小了很多。由此可见,时间片的长短对于响应时间来说是一个关键因素!在一定程度上时间片越短取得的响应时间越好。但是时间片一定越短越好吗?当然不是。如果时间片很短的话,势必会带来频繁的上下文切换。短时间大量的上下文切换会极大影响系统性能。请注意,上下文切换的成本不仅仅来自保存和恢复一些寄存器的操作系统操作。 在程序运行时,它们还会在CPU缓存,TLB,分支预测器和其他硬件上构建大量的状态,这可能导致在上下文切换时产生可见的性能降低。所以设计时间片时既要足够短使得响应速度快,也要足够长使得能够均摊上下文切换带来的时间开销。

但是RR算法真正的解决了问题么?通过A、B、C三个进程的每个任务时间来看,尽管RR算法提高了进程的响应速率,但是每个进程从开始到结束的时间大大加长。这意味着每个进程的周转时间(turnaround time,进程从提交到结束的时间)变长。所以如果算法要以周转时间为衡量标准的话,RR算法可以说是最糟糕的算法之一,甚至在某些情况比FIFO队列还糟糕。

现在我们将假设4取消,使得进程可以有I/O行为。CPU不仅要在发起I/O时将发起进程阻塞然后切换至别的进程运行,还要在I/O结束时(I/O完成时发起中断,进入内核的中断处理程序将该进程由阻塞(blocked)改为就绪(ready))决定运行哪个进程。

我们来看一个例子:假设现在有两个进程A、B。A每隔10毫秒就会发起一次I/O请求,而B只是运行50毫秒无I/O请求。这样A任务就会被分成每个长度为10毫秒的子任务。

                                               

ospf cos值40 怎么算出来的_优先级_07

 从上图可以看出不对阻塞时间进行利用的话会导致效率低下。所以我们根据STCF算法(之所以能选择STCF的原因也是因为条件5:假设知到每个进程的运行时间。试想一下如果不提前知到任务的运行时间还能根据A任务运行时间的特点来选择STCF算法吗?肯定不行,因为并不知道A是否有I/O操作)来构建调度器。这里我们要把每个子任务看做为1个长度为10毫秒的任务(他们实际上还是一个任务)。根据STCF会让运行时间最短的任务先运行的特点,第一次一会选择A任务,因为子任务为10毫秒比B小。在第一个子任务被阻塞后,由于只剩下了B任务,所以在A阻塞期间调度器就可以切换至B任务运行。注意不要犯糊涂可以切换到另一个A的子任务,这些子任务是一个整体!所以当那些交互式任务发起I/O被阻塞时,CPU可以让其他资源敏感的任务来占用CPU运行,进而提高CPU的利用率:

 

                                              

ospf cos值40 怎么算出来的_优先级_08

 

三、多级反馈队列(Multi-Level Feedback Queue)算法

多级反馈队列主要为了解决一下两个问题:

(1)优化周转时间。周转时间是SJF等算法的基础,但是操作系统通常不知道一个任务要运行多长时间。

(2)如何让操作系统在预先知道任务长度的情况下对交互用户有灵敏的响应,减少响应时间。

一、基本规则

MLFQ有很多不同的队列,每个队列有不同的优先级。在任何时候,准备运行的任务在其中一个队列中。MLFQ使用优先级决定在某一时间哪个任务先运行:优先级越高的任务先运行。当然可能在某一个队列中有多个任务,这些任务的优先级是相同的。所以在这种情况就可以对该队列使用RR算法。所以我们得到以下两个MLFQ的规则:

      • 规则 1: 如果Priority(A) > Priority(B),那么A先运行,B不运行. 

      • 规则 2: 如果Priority(A) = Priority(B),那么A和B使用RR算法轮流运行

       所以MLFQ的关键是如何设置优先级。MLFQ并不是给每个任务一个固定的优先级,相反MLFQ会根据每个任务的观察到的行为(observed behavior)来调整优先级。

例如,一个任务如果经常的放弃CPU执行权来等待用户输入,那么这说明这个任务为响应交互式任务,这时候响应性就变得至关重要。所以,MLFQ会一直保持这个任务的搞优先级。相反,如果一个任务长时间集中占用CPU,MLFQ会降低他的优先级。通过这种方式,MLFQ将尝试在流程运行时了解流程,从而使用工作历史来预测其未来行为。

                                                         

ospf cos值40 怎么算出来的_运行时间_09

上图即为MLFQ的示意图。在上面的图中,由于A、B两个任务在最高级别,所以CPU只会在A、B两者中切换运行,而C、D却得不到运行。因此我们需要来调整任务的优先级来保证每个任务都能被运行。

尝试一:如何更改优先级?

我们首先要记住系统中的任务是交互式任务(短时间运行,可能频繁放弃CPU使用权等待用户输入)和需要长时间占用CPU进行运算的计算密集型(CPU-bound)任务(响应时间不是很重要)。对于尝试一,我们制定如下规则

      • 规则 3: 当一个任务进入系统时,他会被排在优先级最高的队列中(最顶上的那个队列)

      • 规则 4a: 如果一个任务用完了他的时间片,那么他的优先级会被减少(往下面的队列移动)。

      • 规则 4b: 如果一个任务在他的时间片用尽之前放弃了CPU使用权,那么他的优先级不变。

例1:来看这样一个CPU密集型任务在MLFQ中的变化:

                                                            

ospf cos值40 怎么算出来的_时间片_10

该任务刚来时进入Q2队列。当Q2中的时间片用完时降到Q1队列。然而在Q1队列中的时间片也用完了,那么该任务优先级继续减少,进而到达Q3队列。

例2:在这个例子中我们让算法尽量像SJF算法一样运行。我们把上面例1中的计算密集型任务设为A,在该例子中在引入一个交互式任务B。那么图像是什么样呢?

                                                            

ospf cos值40 怎么算出来的_时间片_11

在100ms的时候,B任务到来,放在Q2中。在B用完了两次时间片即将放入优先级最低的队列时完成了任务,A继续运行。从上图中我们可以看出这种算法的主要目标之一:由于不知道一个新来的任务是计算密集型的还是交互式的,所以直接假设任何一个任务最开始都是交互式的,放在优先级最高的队列。这样的好处就是如果他真的是交互式任务那么该任务可以立即运行保证用户体验,计算密集型的任务慢慢降低优先级即可。

例3:我们现在来看一看加入I/O后的情况。A为计算密集型任务(黑色),被放在了优先级最低的队列;B仍然为交互式任务(灰色),他只占用CPU1ms的时间便放弃CPU执行权发起I/O。根据我们上面的规则4b,B总是在时间片没有用完的时候就发起I/O,因此优先级不变。这样MLFQ进一步实现了快速运行交互式任务的目标。

                                                              

ospf cos值40 怎么算出来的_时间片_12

当前MLFQ算法存在的问题:

(1)饥饿问题:如果系统中存在太多交互式作业,它们将共同消耗所有CPU时间,因此计算密集型的作业将永远不会被运行(它们会饿死)。

(2)安全问题:由于在时间片结束前结束就可以保证优先级不变,那么某个任务只要在当前时间片马上结束前立马发起I/O放弃CPU使用权再重新运行即可几乎独占CPU资源。

(3)算法无法根据任务的变化来调整:如果一个计算密集型任务在运行一段时间后变为了交互式任务,但是该任务无法从优先级最低的队列移动到优先级最高的队列,那么这个交    互式任务可能永远得不到运行,这对于用户来说将是灾难性的。

尝试二:优先级提升

那么如何避免计算密集型任务线程饥饿呢?我们只需要简单粗暴的在隔一段时间后将所有任务全部扔到优先级最高的队列中,使得它们都能够被运行。所以我们可以得出规则5:

      • 规则 5: 在某个时间段S后,把所有的任务全部移动到优先级最高的队列(最顶部的队列)。

上面的规则解决了之前3个问题中的(1)、(3)两个问题。我们来看使用规则5后的算法与未使用的算法的对比。左侧的图片未使用优先级提升,会使计算密集型任务产生饥饿,而右侧使用了规则5的算法则没有这个问题(每50ms提升一次优先级)。

                

ospf cos值40 怎么算出来的_时间片_13

尝试三:优化运行时间分配

在尝试二中我们已经解决了两个问题,那么还剩一个问题没有解决:如何保证安全问题?在这里我们让调度器记住为每一个任务分配的运行时间,在这段时间中,不管该任务放弃多少次CPU执行权,只要该时间一到,就会降低优先级。因此我们可以将规则4a、4b合并成规则4:

• 规则 4: 不管任务放弃了多少次CPU,这要这段分配的时间运行完,该任务的优先级就会降低。

下面左图为使用4a、4b规则的调度器,可以看出聪明的黑客可以很轻松的让灰色任务独占CPU使用权。而在右图中,可以看出灰色任务即使在运行中发出了I/O在时间运行完后优先级也被降低了,这样就不可能使它独占CPU使用权。

                          

ospf cos值40 怎么算出来的_ospf cos值40 怎么算出来的_14

总结

MLFQ可以为短期运行的交互式作业提供出色的整体性能(类似于SJF / STCF),并且对于长时间运行的CPU密集型任务是公平的,因此MLFQ被广泛应用到各种操作系统中。MLFQ的5条规则可以总结为:

• 规则 1: 如果Priority(A) > Priority(B),那么A先运行,B不运行. 

• 规则 2: 如果Priority(A) = Priority(B),那么A和B使用RR算法轮流运行

• 规则 3: 当一个任务进入系统时,他会被排在优先级最高的队列中(最顶上的那个队列)

• 规则 4: 不管任务放弃了多少次CPU,这要这段分配的时间运行完,该任务的优先级就会降低。

• 规则 5: 在某个时间段S后,把所有的任务全部移动到优先级最高的队列(最顶部的队列)。