Files
912-notes/thu_os/chp15.md
2019-11-27 16:47:25 +08:00

141 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
进程管理2进程调度策略
=========================
## 进程调度的概念
> 第一个问题还是,为什么需要对进程进行调度?
在前面一篇[进程管理1进程和线程的概念](chp11.md)中已经提出进程这个概念是在多道程序的背景下产生的之所以引入进程就是为了让操作系统可以更好地管理这些并发的任务。而我们知道进程的并发并不可能是真正的并行因为无论是在单处理机还是多处理机中进程的数量都远远多于CPU的内核数因此进程的并发本质上只是对CPU资源的时分复用而已。一个CPU在任何时刻还是只能执行一个程序。
这样当一个进程执行完一定的时间或者主动放弃了CPU资源以后操作系统就需要选择另一个进程到CPU上去执行。怎么从众多进程中选择这一个进程就是对进程的调度问题。
> 进程调度的时机。
进程调度还要解决的一个问题就是进程调度的时机即应该在什么时候对进程进行调度。很自然可以想到当一个进程因为各种原因主动放弃CPU资源时比如因为等待某一个资源而进入阻塞比如因为执行完毕了而退出等都应该进行进程的调度。
除此以外对于基于抢占式的进程调度算法在一个更高优先级的进程进入就绪队列以后也应该让这个高优先级进程抢占正在CPU上执行的低优先级进程让它开始执行。但是这种情况不同于进程主动放弃CPU的情况因为正在执行的进程无法知晓一个高优先级进程进入了就绪状态因此就需要操作系统的介入借助中断机制打断正在执行的低优先级进程并且在中断服务例程响应完毕时进行进程的切换。事实上`ucore`中,的确也是采取这两种进程调度的时机的。
## 进程调度策略的评价
和前面的页面置换算法一样,你尽可以设计各种各样的进程调度算法,但是又应该如何评价这些算法的优劣,并且对它们进行改进呢?所以在讨论具体的进程调度算法之前,应该首先关心它的一些评价指标。
从操作系统和计算机系统的角度来看自然是希望设计的调度算法可以更高的资源利用率。这里的资源利用率指标可以用CPU利用率以及系统的吞吐量来表示。CPU利用率是指CPU处于忙状态的时间百分比因为进程的运行模式一般是CPU运算和I/O操作交替进行如果CPU在不断运行则说明计算机系统的资源具有一个比较高的利用率。吞吐量是指单位时间内完成的进程数量是一个比较宏观的目标吞吐量越高则说明系统完成服务效率越高。此外还有一个周转时间指标表示一个进程从初始化到结束的总时间其中也包括处于等待状态的时间。
但是从用户的角度来看则具有完全不同的评价指标。对于用户来说更加注重的是一种用户体验也就是一个进程的等待时间和响应时间。等待时间是指进程在就绪队列中等待CPU的总时间响应时间是指从提交请求到产生响应所花费的时间。这两个指标对于提升用户体验的作用不言而喻例如如果用户移动了鼠标光标却没有移动用户可能会直接砸键盘。
应该指出,吞吐量指标和响应时间(延迟指标)并不是矛盾的,实际上它们是相互独立,可以并行不悖的。这就好比你打开水龙头想要喝水,你刚一开水就出来了,这是低延迟。开了水龙头以后水就倾泻而出,这是高带宽,也就是高吞吐量。这两个指标是可以同时兼顾的。
为了提升系统的吞吐量,一方面可以通过减小系统内部的开销,如进程切换时的上下文切换开销,以及操作系统本身的开销。另一方面,则可以通过减小每个进程等待时间来实现。此外,操作系统需要保证吞吐量不受用户交互的影响,即在存在许多交互任务时,还是需要不时进行调度。
而为了降低系统的响应时间,可以从调度算法的策略来着手,比如让交互式进程具有比较高的优先级,以及时处理用户的请求。此外,还需要减少平均响应时间的波动,让系统具有较好的可预测性。
最后调度策略还有一个公平性指标比如在多用户的情况下让各个用户几乎均分CPU的使用权。有一种策略是保证每个进程占用CPU的时间相同但是这种策略显然经不起推敲因为如果一个用户创建更多进程他就可以获得更多的CPU时间。为了解决这个问题真正的策略应该是保证每个进程的等待时间相同。需要指出的是为了保证系统的公平性往往需要付出一定的代价比如平均响应时间会增加。
## 进程调度算法
以下分析具体的进程调度算法。
### 先来先服务算法FCFS, First Come First Served)
顾名思义啊就是按照进程进入就绪队列的次序依次让它们得到CPU的使用权。
这种算法应该是最简明的一种算法了,显然它具有很多缺陷。首先就是这个算法的性能具有很大的波动性,考察一种情况,现有三个进程$P_1, P_2, P_3$它们需要的计算时间分别是1233。如果是$P_1$先于$P_2, P_3$到达,则具有一个相当长的平均周转时间;反之,如果是$P_1$最后到达,平均周转时间会有一个显著的下降。如下图所示:
![FCFS](images/FCFS.png)
在平均周转时间以外CPU密集型进程长进程如果优先到达还会导致系统的资源利用率低。因为该进程长时间占用CPU导致后面的I/O密集型进程没有机会进行I/O操作此时I/O设备就会闲置。
### 短进程优先算法SPN, Shortest Process Next
短进程优先算法是对FCFS的改进。在上面的例子中容易注意到当运行时间长的进程最后到达时系统具有一个非常优化的性能。短进程优先算法就是基于这样的分析让进程执行的顺序按照进程执行的时间由短到长依次执行。SPN就是选择就绪队列中执行时间最短进程占用CPU进入运行状态。
事实上,可以证明,当采取短进程优先算法时,系统具有最优的平均周转时间。如下图所示:
![SPN](images/SPN.png)
此外还可以将SPN改进成可抢占的版本即短剩余时间优先算法SRT, Shortest Remaining Time。在该算法中需要管理每个进程还需要的剩余执行时间如果在一个进程执行时有一个拥有更短的剩余执行时间的进程加入就绪队列则会抢占当前进程的执行。
容易注意到无论是SPN还是SRT算法都具有一个问题在短进程持续不断地到达的情况下早已经进入就绪队列的长进程迟迟得不到执行就会导致长进程的饥饿现象。
此外两种算法都需要预先知道进程的执行时间即需要预测未来这是难以做到的一种做法是向用户询问但是用户也许为了更快得到执行而给出一个很短的执行时间来欺骗操作系统在这种情况下可以无论用户进程是否执行完毕时间到了总是进行调度。另一种做法和页面置换算法中的LRU算法类似利用历史数据来估计未来一种可行的实现方法是总是保存该进程历史执行时间利用下面的公式来预估未来的执行时间
$$
\tau_{n+1} = \alpha t_n + (1 - \alpha)\tau_n
$$
这里的$t_n$是第$n$次的CPU执行时间$\tau_n$是第$n$次的CPU计算时间估计这样对进程执行时间的预估本质上是以$\alpha$为系数对历史各次CPU执行时间的加权平均
$$
\tau_{n+1} = \alpha t_n + (1 - \alpha)\alpha t_{n-1} + (1 - \alpha)(1 - \alpha)\alpha t_{n-2} + \cdots
$$
利用该算法对进程执行时间的预估如下图所示:
![estimate_time](images/estimate_time.png)
### 最高响应比优先算法HRRN, Highest Response Ratio Next
最高响应比优先算法是在SPN的基础上提出的。前面我们指出SPN算法对于长进程有可能产生饥饿现象。HRRN算法就是为了解决这个问题而在SPN的基础上改进提出的。
最高响应比优先算法将一个进程的等待时间也作为进程调度的指标,提出了响应比的概念:
```
Response_ratio = (service_time + waiting_time) / service_time
```
HRRN算法就是在每次调度时总是选择响应比最高的进程让它获得CPU的使用权。可以注意到在等待时间`waiting_time`相同时该算法还是倾向于选择执行时间更短的进程即还是对短进程有利。但是对于始终得不到CPU资源的长进程随着它等待时间的增加它的响应比也随之增加当它的等待时间增加到一定的程度后该长进程就会具有足够大的响应比从而被调度。这样就防止了SPN算法中长进程无限期等待的现象。
### 时间片轮转调度算法RR, Round Robin
时间片轮转算法的思想也非常简单即让每个进程只能占用CPU一个固定的时间即时间片随后就会被调度将CPU的控制权转交给其他进程。这里的调度算法就是简明的FCFS算法选择就绪队列的队首进入执行如果被调度的进程仍然处于就绪状态则重新进入就绪队列的末尾。
可以看到时间片轮转算法实际上是时间片和FCFS的一个综合算法。由于引入了时间片相对于前面的调度算法RR算法增加了额外的进程切换也就是多了额外的上下文切换的开销。此外RR算法的平均等待时间性能较差如下图的实例所示
![RR_waiting_time](images/RR_waiting_time.png)
可以看到RR的平均等待时间介于最好情况与最坏情况之间一般说来还是比较糟糕。
还有一个问题RR算法中时间片长度的选择也需要重点考虑。如果时间片取得过大比如任何进程都可以在一个时间片内完成执行则RR算法就退化成了FCFS算法而如果时间片取得过小则会产生频繁的进程切换这些额外的开销会影响到系统的吞吐量。前面提到过进程的运行模式通常是CPU运算和I/O操作交替进行如下图所示
![process_running](images/process_running.png)
可以看到每次CPU运行时间几乎总是在8ms以内。如果时间片取得过短则进程可能在本次的CPU运算完成之前就被迫放弃CPU的使用权下次进入调度时又只能运行很短的时间就会因为等待I/O资源而被阻塞。在这种情况下进程执行效率很低资源的利用率比较低下。因此时间片的选择以应该恰好可以覆盖一次CPU的运行时间为宜并且维持上下文切换具有较小的开销比如在1%以内。
## 多级队列调度算法MQ, Multi-level Queues和多级反馈队列调度算法MLFQ, Multi-Level Feedback Queues
多级队列调度算法是维护了多个就绪队列,各个队列之间具有不同的优先级,也可以具有不同的调度策略。进行进程调度时,总是选取最高优先级队列的首进程进入运行。
多级队列调度算法的思想是将具有不同优先级的进程分别放在不同的队列中使得高优先级进程有更多机会得到调度此外由于不同队列的调度算法不同也可以做到比较灵活。例如可以将交互式进程放在优先级高的队列中采用RR调度算法将后台CPU密集型进程放在优先级低的队列中采用FCFS算法。
很明显MQ算法是会产生饥饿现象的。为了解决这个问题可以利用时间片轮转为每个就绪队列都分配一定的运行时间总额比如可以将80%的CPU时间用于前台20%的CPU时间用于后台。
多级反馈队列调度算法是在MQ算法的基础上进行了改进使得进程可以在不同优先级的队列间移动时间片大小随着优先级的降低而增加。它的执行策略是所有进程在刚创建时都进入最高优先级的队列一旦用完了当前的时间片则加入下一级的就绪队列。这样CPU密集型进程很快就会下降到最低的优先级队列而I/O密集型进程则留在最高优先级队列。它的执行流程如图所示
![MLFQ](images/MLFQ.png)
### 公平共享调度FSS, Fair Share Scheduling
公平共享调度的首要准则是公平。为此FSS控制用户对系统资源的访问它的策略是使得一些用户组比其他用户组更重要保证不重要的组无法垄断资源。没有使用的资源按比例分配没有达到资源使用率目标的组将获得更高的优先级。公平共享调度的示意如下图所示
![FSS](images/FSS.png)
## 优先级反置Priority Inversion)
设想一种情况,在基于可抢占式的调度算法中,一个优先级较低的进程$P_1$当前占用CPU在执行同时它还占用了一个共享资源`S`。此时有一个优先级很高的进程$P_2$在运行中申请共享资源`S`,由于$P_1$已经占用了该资源,$P_2$只能进入阻塞状态等待资源`S`。而这时出现了第三个进程$P_3$,它的优先级介于$P_1, P_2$之间,但是由于它并不需要资源`S`$P_3$将抢占CPU进入运行。如果$P_3$的执行时间很长,那么$P_2$这个具有最高优先级的进程将不得不一直等待低优先级的进程执行,这就是优先级反置现象。
为了解决这样一种情况,我们的想法是让占用资源的进程$P_1$迅速得到执行,以释放资源。为此,就需要暂时提升$P_1$的优先级,让它可以不被其他进程抢占。基于这种思想,就产生了下面的两种算法。
> 优先级继承
在上面的例子中,一种解决方案是将$P_1$的优先级提升到$P_2$的优先级,这样$P_1$就可以率先得到执行,从而释放资源。这种策略就是优先级继承。
优先级继承的思想是,只有在高优先级进程申请低优先级进程占用的资源,且在在低优先级进程被阻塞后,才提升低优先级进程的优先级,可以看出,这是一种懒惰的思想。
> 优先级天花板协议
优先级天花板协议是指,对于每一个共享资源`S`,都预先定义一个优先级天花板,其值为可能申请该资源的所有进程中的最高优先级。在一个进程获得了该资源以后,无论是否出现了阻塞,都将该进程的优先级提升到这个优先级天花板。这样,该进程就不可能被优先级更低的进程所阻塞了。
## 实时调度策略
(目前还不会,日后添加......