操作系统(五)—— 调度
概述
上节讲了操作系统内存管理,讲到由于内存空间有限,无法把全部的进程所需的资源都放入内存,所以提出了很多的页面置换算法,这一节面临一个新的问题,就是内存中那么多的进程,到底应该选择哪一个进程执行,这是一个很重要的问题。为了达到不同的目的,就会有不同的策略,比如在交互式系统中,我们肯定希望计算机的响应越快越好,而在大型的批处理系统中,对响应时间没有太多的要求,但是CPU利用率和系统吞吐量要求很高,下面就详细介绍一下针对不同的目的,各种不同的调度策略。
常见场景
- 交互式:需要和用户频繁交互的系统
- 实时:对时间要求比较严格的系统,比如要求某任务在规定的时间必须完成
- 批处理:往往提交大量的任务给计算机
调度算法的目标
- CPU利用率:利用率 = 总时间 / cpu工作时间
- 吞吐量:吞吐量 = 总共完成了多少道作业 / 总时间
- 响应时间:用户提交请求到首次响应的时间
- 周转时间:用户提交作业到作业完成时间
- 等待时间:周转时间 - 运行时间
每种场景对目标的要求
常见调度算法
批处理系统常用调度算法
先来先服务:这是一个非抢占式算法,哪个进程先进来,CPU就先把这个进程处理完,之后再运行下一个进程。这个算法优点就是简单,容易理解,缺点也很明显,如果某个线程90%的时间都在进行I/O操作,相当于90%时间CPU都在空闲,这样就太浪费了。
最短作业优先:这个算法适合进程运行时间可预知的系统,是非抢占式算法,这个算法的困难就在于预知每个进程的运行时间。
最短剩余时间优先:这个是上面最短作业时间的抢占式版本。但是这个算法依然需要知道每个进程的剩余运行时间。
交互式系统常用调度算法
轮转算法:这个就是使用最广,最公平,最古老的调度算法,为每个进程分配一个时间片,如果程序在给定的时间片中没有执行完,就切换到另一个进程执行。这个算法有一个有趣的地方就是时间片的大小设置,如果时间片设置的太小,会导致频繁的上下文切换,把很多时间浪费在上下文切换上,如果时间片设置过大,如果有很多的请求进来,会导致有些请求要等待非常久,就是常说的饥饿(饥饿就是有些进程长时间得不到时间片而无法运行的情况)。
优先级调度:为每个进程分配一个优先级,把不同优先级的进程放入不同的队列,然后先执行高优先级的进程。但是这样做会有一个问题,就是低优先级的进程可能长时间得不到执行,而导致饥饿。一种改进的策略是,每次执行一次高优先级的进程,就把该进程的优先级递减,直到他的优先级比次高优先级低,这样最低优先级的就有机会执行了。
多级队列调度:这个其实是对优先级队列的一种优化,把I/O密集型的任务设置成高优先级,把CPU密集型的任务设置成低优先级,因为I/O密集型的在进行I/O操作的时候,进程会进入阻塞状态,别的进程就可以进来了,如果接下来进来的是CPU密集型,这样就可以保证CPU和磁盘I/O都处于一种相对饱和的状态,而不至于某一个“很忙”,而另一个“很闲”。
公平共享调度:上面的所有的调度算法都是站在进程的角度调度,考虑下面一种情况,在一个大型计算机上,有多个用户访问,某个用户启动了100个进程,而另一个用户启动了1个进程,如果按照上面的策略,启动100个进程用户会获取更多的cpu时间,而且启动1个进程的用户可能很久都得不到执行,所以很不公平,所以就搞了一个用户级别的资源分配,为每个用户分配相对公平的资源,没有达到被分配资源的进程有更高优先级。
总结
先来先服务,不公平,排在后面的进程等待时间较长。最短作业优先,也不公平,但是平均等待时间最小,可能导致饥饿,同时需要准确预估程序的运行时间。轮转算法,很公平。多级队列调度,根据CPU和I/O状态动态调整进程的优先级。公平共享调度,也很公平。
参考:
《现代操作系统》
清华大学操作系统公开课