本文主要讨论单处理器上的进程调度算法,在讨论具体的算法之前先假设存在一个具体的操作系统比如pkzd。
假设该系统pkzd并不存在抢占进程调度,然后我们考虑接下来的情况:
假设在内存中同时存在进程p1, p2, p3(此处为了方便不讨论进程换出的情况), p1, p2, p3的运行时间分别为10ms, 100ms, 1000ms, 如果系统依次运行进程,那么平均的等待时间约为40ms, 如果进程达到的顺序为p3, p2, p1,那么平均的等待时间约为370ms。进程的平均等待时间因为进程达到的顺序而存在巨大的差距,更有甚者,如果存在一个进程的运行时间为无穷,那么它将占用所有的cpu时间,这明显是不合理的,所以必须引入一种机制来保证公平调度。注: 非抢占式系统存在很多的调度算法,有的算法能缩小因为进程达到顺序不同而造成的平均等待时间之间的差距,但是有的算法实现并非很简单,比如SJF算法。另外所有的非抢占式系统都无法很好的处理永远不会终止的进程(比如系统中存在很多的死循环)。
单处理器中进程调度所要达到的目标就是让所有的进程公平的分配cpu时间,我们先来讨论其中一种比较古老又简单质朴的办法:
RR算法,该算法主要的想法是给所有的进程分配一个时间片,一个进程至多占用一个时间片,一旦时间片用完就必须调用其他的进程。可以用链表实现该算法(当然这不是最好的实现办法, 也不一定是最简单的,之所以用链表只是因为本人觉得用链表比较简单,不过你不一定这么认为):
head---p1----p2---p3------pn
head指向进程链表的头部,p1, p2至pn代表进程。假设操作系统给每个进程分配一个固定的时间片10ms(注:该值的大小在用RR算法的系统中对整个操作系统的性能会有很大的影响,该值大小的设定必须要慎重,最后10ms是一个不错的可以考虑的时间片大小的值),然后内核首先调用进程p1,在运行一段时间后(小于等于10ms, 如果进程p1阻塞于某个事件,那么运行事件将小于10ms, 一种极端情况可能为几ns),内核将p1添加到进程链表的尾部,并且重新设置进程p1的时间片为10ms,然后内核继续调用进程p2,就这样内核不停的调用不同的进程来保证公平。
RR算法简单有效的实现了一种公平的调度机制,不过该算法存在一些问题,比如以下情况:
存在进程p1、 p2, 假设p1进程是一个正在运行的shell, p2则是一个暴力破解的程序(可能会运行几天)。首先内核调用进程p1,因为用户没有在键盘输入任何数据所以p1运行1ns就陷入睡眠,然后内核调用p2运行,在运行一段时间后(因为p1在睡眠中,所以运行的程序一直是p2),用户输入一些字符(可能是一个命令ls),然后内核唤醒进程p1,等待p2的时间片结束,之后调用p1,p1执行命令ls后继续陷入睡眠(这次执行的时间假设为1ms)。内核就这样周而复始的不断调用p1和p2, 可能你已经发现,在这种情况下p2占用的cpu时间会远远大于p1,这明显是不公平的。另外对于交互式系统,用户总是希望在按下键的时候得到响应,用户通常不会关心后台进程是否正在运行,所以一般认为交互式进程应该得到更高的优先级以能被尽快调度。
一种改进的机制是加入优先级,实现方式可能是按优先级将所有的进程分类,比如等待键盘输入的进程放到类I/O字符中,等待内存的放到类mem中,等待磁盘的放到类I/O块中,内核每次调度都先从I/0字符类(该类的进程一般都和叫魂有关,所以有更高的优先级)开始查找能运行的进程,如果找不到就去下一个类中寻找,最后如果所有的类都不存在可运行的进程,内核就从idle类中找出一个空转进程并运行它。将所有的进程分类的办法可以让I/0密集进程等到更公平的对待,并且由于I/O密集进程的大部分时间都在睡眠,所以cpu密集的进程也能得到较好的对待。不过这样的实现同样存在问题:
考虑以下情况,操作系统存在10万个进程,5万个I/O密集进程, 5万个CPU密集进程,那么可能发生一种情况,内核将总是从I/O类中调度进程而不会转入cpu类中,如此将会有5万个进程被不公平的对面,并且可能永远都不会被运行。对于这种情况,一种可能的改进方法是增加一些计数变量来观测所有的进程类,在内核将要调度进程时内核根据计数变量来选择合适的类(比如类中的进程所占用的总的cpu数目,进程数,调度次数等),当然这将是一个复杂的实现。
接下来说明一下linux的调度机制,linux与一般的时间片调度算法不同,它所采用的是一种比例的方法来对所有的进程分配时间片,linux的算法很好的保证了所有进程的一个调度(虽然在进程数目暴增时,它的保证将不再有效)。
至于线程调度,这是一个更加复杂的话题,一个简答的例子,对于不同进程的线程是否要同等对待就是一个值得考虑的问题,因为本文字数有限就不讨论了。
最后,调度算法对整个操作系统的性能会产生很大的影响,并且对于不同的进程,内核如何差别对待并且保证公平也是个值得考虑的问题,比如PC上的交互式进程一般情况应该比cpu密集的进程获得更多的调度机会,而一些特殊的情况,比如一个核武器的演算进程应该比一个交互进程获得更多的cpu才对。不管如何调度机制要保证的是所有的进程被公平的对待,对于I/O密集的进程应该保证其运行时间而给予更多的调度次数,而cpu密集的进程应该适当的减少其调度次数来减少其占用cpu的时间。