一、定义调度策略的准备
操作系统具有底层的机制与上层的调度策略,低层级制例如上下文切换已经在上一篇文章中有所了解,那么上层的策略调度又是什么呢?首先,我们如果要定义一种调度策略,自然首先要思考策略的运行环境以及调度指标。策划你的运行环境就是策略在执行期间会出现的情况,例如:
1. 每一个进程开始执行的时间
2. 每一个进程持续执行的时间
3. 执行中的进程是否会出现空转等待的情况
4. 在调用进程之前操作系统是否就能确认进程的相关信息
。。。。。。
等等,这些都是操作系统为进程制定策略之前需要考虑的问题;
另外对于制定出来的调度策略,还要有明确的衡量指标,包括:
1.周转时间(turnaround time)
周转时间就是任务完成的时间减去任务开始的时间,对于操作系统来说也就是进程执行占用CPU的时间。用公式表示就是:
周转时间 = 完成时间 - 开始时间
如果假设所有进程都从同一时刻开始执行,那么周转时间就等于完成时间;
对于同时需要执行的多个进程来讲,还可以计算他们的平均周转时间(average turnaround time),例如当前有A、B、C三个进程,如果ABC都是从0时刻开始执行,每个要执行10s,那么平均周转时间就等于: ((10-0)+(10-0)+(10-0))/3 = 10s;
2.响应时间(response time)
响应时间定义为从进程到达操作系统到首次运行的时间,用公式表示就是:
响应时间 = 首次运行时间 - 开始时间
例如:有三个进程ABC,其中A在时刻0到达,B和C分别在10与20到达,如果三个进程都需要执行20s,那么也就是A会在20s时执行完毕,开始执行B,B在40s时执行结束开始执行C,C在60s时执行结束。对应的响应时间就是:A 0; B 40 - 20 = 20; C 60 - 10 = 50;
二、先进先出(FIFO)
有了环境假设与衡量指标之后就可以开始定义策略了,在上面计算响应时间的时候ABC的例子其实就是一种策略,ABC按照来的顺序依次执行,只有等到前一个执行结束后一个才可以开始执行,这就是最基本的调度算法:先进先出调度(First In First Out, FIFO),也被称为先到先服务。这种算法便于理解也便于实现。那么试着衡量一下它:
情况1. 有3个进程ABC,假设几乎都在时刻0到达操作系统,但是A最先C最后,
那么可以得到他们的平均周转时间为:((10-0)+(20-0)+(30-0) )/3 = 20s,平均响应时间为:((0-0)+(10-0)+(20-0))/3 = 10;
简单来看还可以,那么再来一个例子:
情况2:还是3个进程ABC, 仍然假设几乎都在时刻0到达操作系统,A最先C最后,但是这次A要执行100s,
那么计算平均周转时间:((100-0)+(110-0)+(120-0))/3=110,平均响应时间:((0-0)+(100-0)+(110-0))/3 = 70;
这个结果与情况1相比就不是那么令人乐观了。这种问题学术上被称为“护航效应”(convoy effect),一些耗时较少的潜在资源消费者被排列在重量级的资源消费者之后 ,后续的消费者会浪费大量的时间。
三、最短任务优先(Shortest Job First, SJF)
根据护航效应,首先想到的最简单的处理方法就是让耗时少的消费者先进行,就像是在超市排队购物,你前面的人有着两辆推车的商品需要结算,而你只买了一瓶水,前面的人让你先结账这样你就剩下了等待的时间。这种调度策略被称为最短任务优先,这个名字很好记,因为这个策略内容就和名字一样,先运行最短的任务。
那么在试着衡量一下它:
情况1:条件不变;
但因为策略的改变此时先执行最短的,但ABC时间相同所以其实情况1下没有发生改变;
平均周转时间仍为20, 平均响应时间为10;
情况二:由于策略的改变,那么此时要先执行B,再执行C,最后执行A,那么ABC执行结束的时间就分别为:120,10,20
平均周转时间为((10-0)+(20-10)+(120-20)) /3 = 40,平均响应时间:((20-0)+(0-0)+(10-0))/3 = 10;
可以看到,相比FIFO提升巨大;这时可以想一下如果ABC到达时间不同呢?
情况3:A在时刻0到达,需要执行100s,BC在时刻10到达,都需要执行10s
此时由于0时刻只有A,那么A就是最短的开始执行A,当B与C到达时CPU已经被A占用,只能等到A执行结束B与C才能开始;
那么平均周转时间为((100-0)+(110-10)+(120-10)) /3 = 103.33,平均响应时间:((100-0)+(110-10)+(120-0))/3 = 106.66;
那么此时SJF也显得不是很棒了;
四、最短完成时间优先(Shortest Time-to-Completion First)
之前在思考操作系统是如何与其他进程抢占CPU的时候提到,硬件有时钟中断,那么既然可以通过时钟中断切换进程,自然就可以将进程的执行顺序修改。于是可以得到一种新的调度策略:最短完成时间优先,有时也被称为抢占式作业优先调度程序。他的原理是每当新工作进入操作系统,先确定剩余工作和新工作谁的剩余时间最少,那么就先执行谁。这时再考虑之前的例子,因为情况一已经几乎不再受到调度算法的影响,就先略过。
情况2:ABC同时到达,但BC执行时间段,所以先执行BC,之后执行A;
那么平均周转时间为((120-0)+(10-00)+(20-0)) /3 = 50, 平均响应时间:((20-0)+(0-0)+(10-0))/3 = 10;
情况3:此时A从0开始执行,BC到达后,开始执行BC,A最后执行
那么平均周转时间为((120-0)+(20-10)+(30-10)) /3 = 50,平均响应时间:((0-0)+(10-10)+(20-10))/3 = 3.33;
可以看到这种调度策略再存在执行时间差距很大的时候可以有效地降低平均时间,但是如果ABC是同时到达且耗时都很久呢?那么后到的就还是需要等待前面的进程执行结束之后才能执行。这样对于一些时间敏感的使用者来说是很难以接受的。那么想要解决这个问题就可以考虑让三个进程同时执行。
五、时片轮转算法(time slice Round-Robin)
时间片轮转算法的思想就是给每个进程同样的权利也就是同样的对CPU的使用时间,可以成为调度量子(scheduling quantum),每个进程执行一个时间片后就切换,将CPU让个下一个进程,所有的进程一直不停的切换,直到所有进程执行结束。因为每个时间片都很短,那么对于开发者来说就近乎是多个进程在同时执行。需要注意的是,时间片长度必须是时钟中断的整数倍,时间片长度不能过短也不能过长,过短会造成系统不停的切换进程不停的进行上下文切换,那么就会在切换操作上耗费大量的时间,甚至可能占到进程运行时间的百分之几那么长;时间片也不能过长,如果过长那么在某些情况下就变回了SJF。
其实总的来说,任何相对公平的调度策略,例如时间片轮转,在小规模时间内将CPU均匀分配给多个进程,具有良好的响应时间属性对于周转时间类的指标都表现不佳。但这是一种相对的割舍或者说是交换,如果开发者愿意也可以舍弃响应时间来换取周转时间的优化,那也就是类似于SJF之类的算法。