写在前面
在了解了进程的基本概念之后,我们开始学习进程调度算法。本文讨论了硬件和操作系统是如何支持进程调度的,并列举了一些进程调度算法。希望本文能帮助读者快速建立起对进程调度的认识。
硬件和操作系统的支持
进程的调度主要有两种方式:硬件切换和软件切换。目前主流方式是使用软件切换,也就是依靠操作系统来进行进程的调度。我们接下来主要研究的就是依靠操作系统的软件调度。
我们知道,进程是直接运行在CPU上的,这样做可以使进程运行得很快。当CPU上跑了一个进程时,此时操作系统是没有运行的。那么,操作系统该怎样重新获得CPU的控制权,并执行进程调度呢。
有两种方法解决这个问题:协同和抢占。
协同式切换
所谓协同,就是当正在CPU上运行的进程执行了某些系统调用时,此时操作系统就会重新获得CPU的控制权,操作系统可以借此机会运行调度程序,完成进程调度。但是,如果一个进程从始至终都没有执行过系统调用,协同的方法就没有用了。现在基本用协同式切换,所以我们只需要知道有这个东西就好。
抢占式切换
现在主流方法是使用抢占式任务切换。所谓抢占,就是使用某些机制,直接打断正在运行的进程,把CPU的控制权抢过来,并交给操作系统,这样一来操作系统就可以执行进程调度了。
为了实现抢占式任务切换,我们需要硬件的支持。
硬件的支持
为了实现抢占式切换,我们使用了中断。关于中断的内容,详情请参考浅述中断机制
在这里,我们仅仅解释与进程调度密切相关的部分。在计算机主板上,有一块名为RTC的实时时钟电路,这个芯片可以提供周期性的中断功能,我们利用这个时钟中断,在该中断处理程序中,把CPU的控制权交换给操作系统,操作系统就可以借此机会运行进程调度程序。

此外,在发生中断的时候,硬件还把进程的寄存器的值保存到该进程的内核栈中,然后切换到内核模式。
操作系统做了什么
现在CPU正在运行内核程序(也就是控制权在操作系统手中),当调度程序决定切换进程时,操作系统会把该进程中的内核栈保存到存储着进程信息的内存中。硬件执行的保存操作和操作系统执行的保存操作完成后,就完成了所谓的上下文切换。
进程调度VS线程调度
实际上,进程调度算法和线程调度算法在一定程度上是通用的,但是由于进程和线程之间的差异,在执行具体的调度算法时,在优化方面会有一些不同。
在主流操作系统中,线程是调度的最小单位,进程是分配资源的最小单位。
调度算法
在了解了硬件和操作系统是怎样对进程调度提供支持后,接下来我们开始讲述一些调度算法。本节的内容只是简单的对一些算法进行介绍,并给出了算法优劣性的结论,忽略了证明部分,感兴趣的读者可以阅读操作系统相关的书籍。
需要考虑的指标
为了比较不同调度算法的优劣性,我们需要明确一些需要考虑的指标:周转时间和响应时间。前者是一个代表性能的指标,后者是一个代表公平的指标。通常情况下,这两种指标是相互冲突的。
- 周转时间=完成时刻-到达时刻
- 响应时间=开始时刻-到达时刻
到达时刻是指任务准备运行的时刻,这时的任务已经在CPU需要运行的任务队列中了,但是还没有开始运行。完成时刻是任务执行完毕时刻。开始时刻是任务首次运行的时刻。
平均周转时间=所有任务周转时间之和/任务数
评价响应时间=所有任务响应时间之和/任务数
FCFS
FCFS是First Come First Sever的缩写,意思是先到先服务。
我们假设任务都是同时到达的(准备好运行),并且它们都不进行I/O操作,那么计算它们的平均周转时间就是所有任务的周转时间/任务数量。
暂时不考虑时钟中断,如果第一个运行的任务一直在运行那么后面的任务将永远也无法运行,即使它们只运行很短的时间。
SJF
Shortest Job First,最短任务优先。即使任务是同时到达的,调度程序会根据任务需要运行时间的长短决定任务的运行顺序。
在只考虑周转时间且所有任务同时到达的情况下,SJF比FCFS更好。
但是,如果一个运行时间很长的任务先比运行时间非常短的任务先到达,那么后面的任务就不得不等待这个耗时很长的任务先运行完。
STFC
为了解决SJF的问题,我们采用了上文提到的中断机制。利用中断机制,在短耗时任务到达时,调度程序可以抢占长耗时任务,并且运行短耗时任务。
加入了中断机制后,平均周转时间大大缩小了。可以说,在只考虑周转时间的情况下,STCF调度策略就是最优的。
但是,上文提到过另一个度量指标:响应时间。STFC策略在响应时间方面(公平性)方面是做得不足的。试想一下,如果你想打开一个网页,但是CPU中有几个运行时间和打开网页操作时间相同或更短的任务先于打开网页到达,那么很遗憾,你的打开网页任务只能在这些任务完成后才能得到CPU处理。
轮转
考虑到响应时间的问题,我们采用了被称为轮转(Round-Robin)的调度策略。轮转策略是这样调度任务的:在一个时间片内(20ms或30ms等),CPU运行一个任务,然后CPU运行另一个任务,如此循环。
为了能在一个时间片结束之后,CPU能运行另一个任务,时间片必须是时钟中断周期的整数倍,这样才能利用中断机制运行调度程序。
那么应该如何确定时间片的长度呢?如果时间片长度过短,那么任务上下文切换时花费的时间会影响整体性能;如果时间片长度过长,那么一些操作就不能得到及时的响应。所以选择合适的响应时间是非常重要的。
RR策略也并非完美,它在周转时间上是没有什么优势的。
任务有I/O时
目前我们所考虑的任务只是简单地在CPU上运行,没有I/O操作。现在我们考虑任务会进行I/O操作,再来考虑一下调度策略。
当一个任务执行I/O操作时,它是不占用CPU的,该任务会从运行态转换到阻塞态,此时我们就可以趁机运行另一个任务,这样就实现了重叠(overlap)。
最后
到目前为止,我们所讨论的所有调度算法都基于一个基本假设:每个任务的运行时间是可以预测的,实际上,这是无法做到的。在下一篇文章中,我们将不使用这个假设,再给出一些调度策略。