进程都希望自己能够占用CPU进行工作,那么这涉及到前面说过的进程上下文切换。
一旦操作系统把进程切换到运行状态,也就意味着该进程占用着CPU在执行,但是操作系统把进程切换到其他状态的时候,就不能在CPU中执行了,于是操作系统会选择下一个要运行的进程。
选择一个进程运行这一功能是在操作系统中完成的,通常称为调度程序(scheduler)。
在进程的生命周期中,当进程从一个运行状态到另一个状态变化的时候,其实就会触发一次调度。
比如:
因为,这些状态变化的时候,操作系统是需要考虑是否要让新的进程给CPU运行,或者是否让当前进程从CPU上退出来而换到另一个进程运行。
另外如果硬件时钟提供某个频率的周期性中断,那么可以根据如何处理时钟中断,把调度算法分为两类:
原则一:如果运行的程序发生了I/O事件的请求,那么CPU使用率肯定很低,因为此时进程在阻塞等待硬盘的数据返回。这样的过程势必会造成CPU突然的空闲。所以,为了提高CPU利用率,在这种发送I/O事件致使CPU空闲的情况下,调度程序需要从就绪队列中选择一个进程来运行。
原则二:有的程序执行某个任务花费的时间会比较长,如果这个程序一直占用着CPU,会造成系统吞吐量(CPU单位时间内完成的进程数量)的降低。所以,要提高系统的吞吐率,调度程序要权衡长任务和短任务进程的运行完成数量。
原则三:从进程开始到结束的过程中,实际上是包含两个时间,分别是进程运行时间和进程等待时间,这两个时间总和就称为周转时间。进程的周转时间越小越好,如果进程的等待时间很长而运行的时间很短,那周转时间就很长,这不是我们所希望的,调度程序应该避免这种情况发生。
原则四:处于就绪队列的进程,也不能等太久,当然希望这个等待的时间越短越好,这样可以使得进程更快的在CPU中执行。所以,就绪队列中进程的等待时间也是调度程序所需要考虑的原则。
原则五:对于鼠标、键盘这种交互式比较强的应用,我们当然希望它的响应时间越快越好,否则就会影响用户体验。所以,对于交互式比较强的应用,响应时间也是调度程序需要考虑的原则。
针对上面的五种调度原则,总结成如下:
不同的调度算法适用的场景也是不同的。
单核CPU系统中常见的调度算法:
先来先服务调度算法
最简单的一个调度算法,就是非抢占式的先来先服务算法(FCFS):
每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行。
这似乎很合理,但是当一个作业先运行了,那么后面的短作业等待的时间就会很长,不利于短作业。
FCFS对长作业有利,适用于CPU繁忙型作业的系统,而不适用于I/O繁忙型作业的系统。
最短作业优先调度算法
最短作业优先(SJF)调度算法,它会优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。
这显然对长作业不利,很容易造成一种极端现象。
比如,一个长作业在就绪队列等待运行,而这个就绪队列有非常多的短作业,那么就会使得长作业不断地往后推,周转时间边长,致使长作业长期不会被运行。
高响应比优先调度算法
前面的 [先来先服务调度算法] 和 [最短作业优先调度算法] 都没有很好的权衡短作业和长作业。
那么,高响应比优先(HRRN)调度算法主要是权衡了短作业和长作业。
每次进行进程调度时,先计算 [响应比优先级] ,然后把 [响应比优先级]最高的进程投入运行, [响应比优先级]的计算公式:
从上面的公式可以发现:
但是进程要求的服务时间是不可知的,所以高响应比优先调度算法是理想型的调度算法,现实中是实现不了的。
时间片轮转调度算法
最古老,最简单,最公平且最广泛的算法就是时间片轮转(RR)调度算法。
每一个进程被分配一个时间段,称为时间片,即允许该进程在该时间段中运行。
另外,时间片的长度就是一个很关键的点:
一般来说,时间片设为 20ms~50ms 通常是一个比较合理的折中值
最高优先级调度算法
前面的 [时间片轮转算法] 做了个假设,即让所有的进程同等重要,大家的运行时间都是一样。
但是,对于用户计算机系统,它们希望调度是有优先级的,即希望调度程序能从就绪队列中选择最高优先级的进程进行运行,这称为最高优先级(HPF)算法。
进程的优先级可以分为,静态优先级和动态优先级:
该算法也有两种处理优先级的方法,非抢占式和抢占式:
但是依然有缺点,可能会导致低优先级的进程永远不会运行。
多级反馈队列调度算法
多级反馈队列(MFQ)调度算法是 [时间片轮转算法] 和 [最高优先级算法] 的综合和发展。
工作方式:
可以发现,对于短作业可能可以在第一级队列很快被处理完成。对于长作业,如果在第一级队列处理不完,可以移入下次队列等待被执行,虽然等待时间变长了,但是运行时间也变长了,所以该算法很好的兼顾了长短作业,同时有较好的响应时间。