如果计算机支持多道程序设计,那么它会经常碰到多个进程或者线程在同一时刻竞争CPU。只要当两个进程同时进入就绪状态,这种情况就会发生。但是CPU只有一个,那么这时候就需要做一个选择:到底接下来该选择哪个进程运行。操作系统做这个选择的部分就叫做调度器(scheduler),而使用到的算法叫做调度算法(scheduling algorithm)。
很多对进程调度使用的东西对于线程调度同样适用。如果线程是内核管理的线程,那么调度的单位就是线程,而不管这个线程属于哪个进程。
1.调度简介
在以前还是批处理系统的时候,调度算法非常简单:无非是运行磁带上下一个作业(job)而已。但是对于多道程序设计的系统来说,调度算法变得比较复杂,这主要是因为多个用户在同时等待服务,因此需要调度器决定到底是接下来是选择一个批处理作业还是与远程终端用户交互。由于CPU属于稀缺资源,因此,一个优秀的调度器能够提升系统性能以及用户满意度。
但是随着PC的发展,这种从两方面情况发生了变化。第一,大多数时间都只有一个活跃的进程。毕竟一般来说,你在使用Word编辑文件时,不太可能还同时让系统在后台编译程序。这样的情况下,调度器其实不需要做很多工作来决定到底运行哪个进程——Word几乎就是唯一的选择。第二,计算机的速度越来越快,以至于CPU可能已经不再那么稀缺了。对于很多PC的程序来说,其限制主要在于用户能进行输入的比例,而不是CPU能够处理的比例。甚至就算是两个程序真的同时进行,其实先调度哪个运行也无伤大雅,例如,一个Word和一个Excel同时在运行,先调度哪个都行,因为用户可能同时在等待这两个程序的输出。
但是对于网络服务器来说,这种情况又不一样了。非常多的程序竞争CPU资源,因此,调度又变得非常重要了。举例来说,调度器需要决定到底是让日常统计进程运行还是让响应用户请求的进程运行。
调度器除了需要考虑选择哪个进程运行,还需要考虑到系统的性能。毕竟,进程之间的切换是开销很大的,这涉及到从用户态到内核状态的切换,进程状态的保存、恢复等等操作。
2.进程行为
几乎所有的进程都在计算和I/O操作之间交替进行。如图所示:
一般来说,CPU运行一段时间,然后调用一个系统调用来读或者写文件,之后CPU继续运行,如果还需要数据则再进行I/O操作。有些I/O活动实际上是作为计算看待。例如,如果CPU将数据拷贝给显卡内存来更新屏幕,这时候就不能作为I/O看待,因为这时候CPU还在运算。这里所说的I/O是指当一个进程等待外部设备完成工作而进入阻塞状态的状态。
对于上面图中的a部分,大部分时间用子啊计算上,我们称之为计算倾向(compute-bound);而b图大部分时间在等待I/O操作,因此称作I/O倾向的(I/O-bound)。
随着CPU速度变快,进程逐步向I/O倾向转变。因此,对I/O倾向进程的调度会成为未来一个非常重要的课题。
3.调度的时机
对于进程调度来说,一个重要的问题是何时进行调度。
第一,创建新进程的时候,到底是让父进程运行还是让子进程运行,这就需要进行进程调度,在这种情况下,选择哪个进程都无伤大雅。
第二,当有进程退出时,需要进行进程调度,从其他的就绪状态的进程集合中挑选一个运行。如果说没有进程处在就绪状态,那么系统提供一个system-idle的进程运行。
第三,当有进程因为I/O、或者信号量或者别的什么原因阻塞时,需要进行进程调度。有的时候,进程阻塞的原因可能会印象调度决定。比如,进程A非常重要,它需要等待进程B离开其临界区。因此,调度器需要调度进程B运行,以便其离开临界区,并且最终使得进程A继续运行。但是可惜的是,调度器往往无法获得足够的信息来做这些决定。
第四,当I/O中断发生,需要做进程调度。假设进程A因为I/O请求而进入阻塞状态,等待I/O设备完成操作。这时,别的就绪进程可能被调度器选中,开始运行。当I/O设备就绪,I/O中断发生,这时,调度器往往需要将正在运行的进程阻塞,而选择之前等待I/O设备的进程重新进入运行态。当然,到底是让之前的进程重新运行还是让正在运行的进程继续运行都是可行的,这具体取决于调度器。
第五,假设硬件始终提供50-60Hz频率的时钟中断,那么,在每个时钟中断,或者每第k个时钟中断需要进行进程调度。从调度算法如何应对时钟中断,可以将调度算法分为两种。第一种调度算法选中了一个进程运行后,会一直让这个进程运行,直到它因为I/O或者其他原因而阻塞,或者说它自愿让出CPU资源。我们称之为非抢占式的(nonpreemptive)。事实上,进程调度是不会在时钟中断的过程中做出。往往是当时钟中断的处理结束之后,中断前运行的进程此时会被恢复。另外一种是抢占式的,这种调度算法往往选择一个进程,让它运行一个固定的最大时间。如果时间到了,它还在运行,调度器暂停这个进程,并且选择一个其他的进程运行。
4.调度算法的类别
不同的环境下是需要不同的调度算法的。这是因为,不同的应用环境有不同的目标。这里主要需要讨论三方面的环境:
- 批处理系统
- 交互式系统
- 实时系统
批处理系统目前依然广泛应用于商业和工业控制,特别是金融、保险等行业。在批处理环境下,没有用户坐在终端前等待响应。因此,非抢占式或者抢占式但是有非常长的时间间隔的调度算法是可以接受的。这种情况下减少了进程切换,因此性能得到了提升。
对于交互式系统,抢占式是有必要的,这样能避免一个进程霸占CPU不放,而别的进程得不到服务。服务器也进入这个范畴,因为服务器需要服务非常多的用户,而这些用户都很忙。
在那些有实时限制的系统中,抢占式基本上没什么必要,因为进程本身就知道它自己无法运行很长的时间,因此它们会赶紧干活,然后快速的阻塞。实时系统和交互式系统不同之处在于实时系统一般是为了将目前的应用程序发展到极致,而交互式系统是通用的,它可以运行各种程序。
5.调度算法的目标
下面列出一些调度算法的目标,换句话说,判断一个调度算法优劣的标准和依据:=
- 所有系统
公平(Fairness):给所有进程一个公平的获取CPU的机会与份额。
强制实施(Policy Enforcement):声明的措施必须实施。
平衡(Balance):使系统所有部分保持忙碌。
- 批处理系统
吞吐量(Throughput):每小时最多的作业。
回转时间(Turnaround Time):将提交任务和任务结束之间的时间间隔最小。
CPU使用率(CPU Utilization):使CPU一直处在忙碌状态。
- 交互式系统
响应时间(Response Time):快速响应请求。
均衡性(proportionality):满足用户的预期。
- 实时系统
赶上截至时间(Meet deadlines):避免丢失数据。
可预期(Predictability):在多媒体系统中避免品质退化。
上面的这些目标都还算比较显而易见,因此就不一一详细说了。
6.批处理系统的调度算法
- 先到先服务(First-come-first-served)
非抢占式的调度算法中,这种可能是最简单的。现请求CPU资源的先安排运行。基本上只需要一个简单的队列存放所有就绪的进程。新到的进程放入队尾,从队首取出先来的就绪进程,让它运行,不管运行多久都让它运行。这种算法的巨大优势在于非常简单,易于理解和实现。此外,类似于早排队买火车票的人先买到票,从这种角度来说,这个算法有其合理性。
这个算法也有其巨大的劣势。假设有一个计算倾向的进程,每次运行一秒,有很多I/O倾向的进程,它们使用很少的CPU资源,但是需要执行1000次I/O操作才能结束。现在第一个进程运行了1秒,然后读取磁盘数据,这时其他进程开始进行I/O操作,等到第一个进程获取到了磁盘数据,于是它再运行1s,然后其他进程紧接着读取数据。假设一个I/O操作需要1s,那么一个进程结束就需要1000s,这是很致命的。
- 短作业优先(Shortest Job First)
这种批处理系统的调度算法也是一种非抢占式的调度算法,这种算法假设进程的运行时间是提前知道的。现在举例说明。假设在一个保险公司,我们很清楚一个处理1000条理赔的批处理作业需要多少时间(这种重复的工作每天都在进行,因此时间是可以提前知道的)。现在我们假设有ABCD四种类型的理赔批处理作业,需要的时间分别是8,4,4,4分钟。如下图所示:
如果调度算法调度的顺序如图a所示,那么A的回转时间(Turnaround Time)是8,B的回转时间是12,C是16,D是20,平均回转时间是14分钟。现在看如图b所示的调度顺序,也就是短作业优先的调度算法。B的回转时间是4,C的是8,D的是12,A的是20,那么平均回转时间是11分钟,这样的结果好于a图所示的情况。现在我们做一个一般性的分析,假设四个作业需要的时间是a,b,c,d。第一个的回转时间是a,第二个是a+b,第三个是a+b+c,第四个是a+b+c+d。平均回转时间是(4a+3b+2c+a)/4。很明显,a占的比重最大,想要整体平均回转时间低,a必须是最少的,以此类推,b应该是次少的。需要特别指出的是,最短作业优先的算法需要所有作业同时处在待调度运行的状态才行。也就是,在0时刻,所有作业都是就绪的。
- 最短剩余时间优先(Shortest Remaining Time Next)
这个算法是短作业优先算法的抢占式版本。这个算法下,调度器始终选择完成工作剩余时间最短的进程来运行,一个作业需要的时间必须提前知道。当一个新的作业进来,调度器会将它需要的运行时间和正在运行的作业完成工作的剩余时间作比较,如果新作业的时间更短,那么调度器暂停正在运行的进程,转而运行新作业。
转载请注明:江海志の博客 » 《现代操作系统》读书笔记之——进程调度(一)