
进程调度算法怎么考?FCFS与SJF与HRRN核心解析
操作系统最核心的职责之一,就是在多个就绪进程之间分配CPU时间。这个分配决策的过程,就是进程调度。理解调度算法之前,必须先建立一个基本认知:CPU在同一时刻只能执行一个进程的指令流,而系统中往往有几十上百个进程同时在争抢这个唯一的执行资源。调度的本质问题,就是回答两个问题——下一个让谁跑,以及让它跑多久。
这两个问题看似简单,但回答它们的策略直接影响着整个系统的响应速度、吞吐量和公平性。不同的应用场景对这三个指标有不同的偏好:交互式系统更看重响应速度,批处理系统更看重吞吐量,实时系统则把可预测性放在首位。正是这种需求的分化,催生了多种截然不同的调度算法。软考对进程调度的考查,不仅要记住每种算法的规则,更要理解背后的权衡取舍——当周转时间和响应时间不可兼得时,算法选择了站在哪一边。
操作系统中,进程在其生命周期内会在多种状态之间迁移。就绪态是进程等待CPU调度时所处的状态,运行态是进程正在CPU上执行的状态,阻塞态则是进程等待某个事件完成而暂时无法继续执行的状态。调度算法只在就绪队列中选择下一个要运行的进程,而触发调度决策的时机主要有四种场景:当前运行进程主动放弃CPU、当前进程被更高优先级进程抢占、当前进程时间片用完、以及进程从阻塞态被唤醒进入就绪态。这四种场景分别对应非抢占调度和抢占调度的不同策略。
在评价任何一种调度算法之前,有三个核心指标必须精确理解。周转时间指的是从进程提交到进程完成所经历的全部时间,它等于完成时刻减去到达时刻。等待时间则是进程在就绪队列中等待CPU的时间总和,不包括进程实际在CPU上运行的时间,也不包括进程等待输入输出操作完成的时间。响应时间是交互式系统中最重要的指标,指从用户提交请求到系统首次产生响应的时间间隔。软考选择题特别喜欢在这三个概念之间设陷阱,比如出一道题说某个调度算法"平均等待时间最短",选项里塞一个"平均周转时间最短"来混淆视听——两个概念描述的是不同的东西,不能互换。
调度算法可以按是否支持抢占划分为两大类。非抢占调度的规则是:一旦CPU被分配给某个进程,该进程将一直持有CPU直到主动释放——可能是进程执行完毕正常退出,也可能是进程发起一次输入输出操作而主动进入阻塞状态。在此期间,即使有一个更紧急或更短小的进程到达就绪队列,也必须等待当前进程主动让出CPU。非抢占调度的缺点显而易见:一个长时间运行的CPU密集型作业会阻塞所有后续进程,但其优点也值得注意——实现简单,没有抢占引发的额外上下文切换开销。
抢占调度则允许操作系统在特定时机强行收回CPU控制权。最典型的抢占触发条件是时间片中断:当时钟中断到来且当前进程已用尽分配给它的时间片,调度器就被激活,把当前进程放回就绪队列尾部,从就绪队列中选择下一个进程运行。抢占调度需要额外的机制来保存和恢复进程上下文,单次切换开销虽然只有几十到几百微秒,但在频繁抢占的场景下累积起来不容忽视。
先来先服务调度算法,英文缩写为FCFS,是所有调度算法中最简单也最符合直觉的一种。它的规则只有一条:就绪队列按进程到达的先后顺序排列,每次调度选择队首进程分配CPU。这就像是排队买票,先到先得,天经地义。FCFS的实现可以用一个简单的先进先出队列来完成,整个过程不需要任何复杂的优先级计算或排序操作,调度决策的开销极低。
但FCFS有一个致命的弱点:它完全无视进程的执行时间差异。如果一个需要运行一百秒的进程排在了队列最前面,那么后面所有进程——哪怕只需要一两秒就能跑完——也要白白等待九十九秒。这种现象被称为"护航效应",就像一条缓慢的货船堵在运河入口,后面所有的快艇都只能按它的节奏缓慢通过。FCFS还有一个特性值得注意:它对CPU密集型进程天然有利,对输入输出密集型进程不够友好。一个典型的输入输出密集进程的运行模式是短时间使用CPU后主动放弃CPU去等待输入输出完成,在FCFS下它每次从阻塞返回后都需要重新排到队尾,与所有新到达的进程公平竞争。
FCFS的周转时间严重依赖于进程的到达顺序。假设有三个进程先后到达:进程A需要执行二十四个时间单位,B需要三个,C需要三个。如果到达顺序是A先到、然后B、最后C,那么A的周转时间为二十四,B必须等A跑完需要等二十四、周转为二十七,C要等A和B都跑完需要等二十七、周转为三十。平均周转时间为二十七。但如果顺序反过来——C先到、B第二个、A最后——则C周转三,B等待三周转六,A等待六周转三十,平均周转降到十三。同样的三个进程,仅因到达顺序不同,平均周转时间相差一倍以上。
短作业优先调度算法的英文缩写是SJF或者SPF。它的核心思想非常直接:在所有已到达的就绪进程中,挑选预估执行时间最短的那个优先分配CPU。如果几个进程的执行时间相同,则按FCFS规则选择最先到达的那个。这个算法的理论优势非常突出:在所有非抢占调度算法中,SJF能给出最短的平均等待时间。这不是经验观察,而是可以通过数学严格证明的最优性结论。
SJF的直觉依据很容易理解。如果把进程调度看作一个排队问题,让短作业排在长作业前面执行,那么短作业的等待时间被压缩到最小,长作业虽然要多等一会儿,但因为它本来就需要跑很久,多等的这点时间对它的总周转时间影响比例较小。反之如果让长作业先跑,所有短作业都要等上很长时间,每个短作业的周转时间都被不成比例地拉长,平均下来自然不理想。
但SJF在实际系统中面临一个根本性的困难:调度器怎么事先知道每个进程的执行时间?在批处理系统中,用户提交作业时可以给出估计的执行时间上限;在交互式系统中,可以通过进程的历史行为进行启发式预测——如果某个进程过去几次CPU突发的时间都很短,那么它下一次CPU突发大概率也比较短。但这种预测永远不可能是精确的,一旦估计偏差较大,SJF的优势就会大打折扣。软考中经常出现SJF的抢占版本,称为"最短剩余时间优先"——每当新进程到达时,比较新进程的剩余执行时间和当前运行进程的剩余执行时间,如果新进程的剩余时间更短,就抢占当前进程。
SJF的另一个严重缺陷是饥饿现象。如果系统中持续不断地有短作业到达,一个长作业可能永远得不到CPU。在实际系统中解决饥饿问题通常采用"老化"技术——一个进程等待的时间越长,它的优先级就逐步提高,最终总能获得执行机会。
高响应比优先调度算法,英文缩写为HRRN,是为克服SJF长作业饥饿问题而设计的一种折中方案。它的核心机制是为每个进程计算一个"响应比",响应比越高的进程优先获得CPU。响应比的计算公式体现了这个算法的哲学:响应比等于等待时间加上要求服务时间后除以要求服务时间,也就是一加上等待时间除以要求服务时间的商。
这个看似简单的公式蕴含了巧妙的权衡逻辑。对于一个短作业,分母很小,即使等待时间不长,响应比也会迅速攀升,因此短作业天然具有更高的响应比优势。但对于一个长作业,分母很大,如果等待时间也非常长了,分子中等待时间的贡献终将超过分母带来的劣势,响应比同样可以上升到较高水平。这意味着长作业不会永远饥饿——只要等得足够久,它的响应比终将超过那些刚刚到达的短作业。HRRN由此同时兼顾了短作业的周转效率优势和长作业的防饥饿需求。
HRRN的一个关键特性是它既不是单纯的FCFS也不是单纯的SJF,而是一种动态优先级算法。每个进程的优先级不是固定不变的,而是随着等待时间的增长而持续上升。调度器在每次需要选择下一个进程时都必须重新计算就绪队列中所有进程的响应比,并选出最高的那个。这个全量扫描的计算开销在就绪队列较大时不可忽略,因此HRRN在大规模系统中的应用受到一定限制。但在软考的命题语境中,HRRN的计算题是高频考点——给定一组进程的到达时间和执行时间,要求计算在HRRN调度下每个进程的完成时间、周转时间和平均周转时间。
时间片轮转调度算法,英文缩写为RR,是在FCFS基础上加入时间片概念演变而来的抢占式调度算法。就绪队列仍然采用先进先出结构,但每个进程每次最多只能连续运行一个固定长度的时间片。如果进程在时间片内完成了全部执行,它正常退出;如果时间片用完而进程尚未完成,调度器将该进程放回就绪队列的末尾,然后从队首选择下一个进程运行。
时间片长度的选择是RR算法设计的核心艺术。时间片设得太长,RR退化为FCFS,失去了抢占调度对交互式应用的响应优势;时间片设得太短,上下文切换频繁发生,CPU的大量时间被耗费在保存和恢复进程上下文的系统开销上,有效利用率急剧下降。实践中的经验法则通常将时间片设定在十到一百毫秒之间,保证绝大多数交互式请求能在一个时间片内完成处理,同时将上下文切换开销控制在CPU总时间的百分之一以下。假设有n个就绪进程,时间片长度为q,那么每个进程的最大等待时间上界为n减一的乘积乘以q,这意味着RR算法的响应时间被严格约束在一个确定性上界以内——这是FCFS和SJF都不具备的优良特性。
多级反馈队列是实践中应用最广泛的通用调度算法,现代操作系统的调度器——如Linux的完全公平调度器——虽然在实现细节上千差万别,但核心理念都继承自多级反馈队列。它的基本架构是维护多个不同优先级的就绪队列,优先级越高的队列分配的时间片越小,优先级越低的队列分配的时间片越大。新创建的进程首先进入最高优先级队列的末尾。如果进程在分配的时间片内完成了执行,它正常退出;如果时间片用完而进程仍未执行完毕,它被降级到下一级队列的末尾。只有当所有更高优先级队列都为空时,调度器才会从较低优先级队列中选择进程运行。
这种设计精妙地解决了一系列彼此冲突的需求。对于短作业和交互式作业——它们的CPU突发通常很短,往往在第一个时间片内就能完成——多级反馈队列提供了极快的响应速度和极低的周转时间。对于长作业——它们会逐渐被降级到低优先级队列——虽然响应时间变差,但获得了更大的时间片来摊销上下文切换开销,且不会饿死,因为当高优先级队列全部空出来时它们终究能获得CPU。不同优先级队列可以使用不同的调度策略——高优先级队列用RR保证交互式响应,低优先级队列用FCFS减少上下文切换开销。
多级反馈队列有四个关键的配置参数。第一个参数是队列的级数,通常设为三到五级。第二个参数是每一级队列的时间片大小,通常按指数增长——比如第一级八毫秒、第二级十六毫秒、第三级三十二毫秒。第三个参数是降级条件,最常用的是"用完当前时间片即降级"。第四个参数是是否允许进程升级,某些实现会在进程表现出交互行为时——比如主动放弃CPU去等待输入输出——将其提升回高优先级队列。软考对多级反馈队列的考查通常不要求掌握参数调优细节,但会考查对其工作原理的定性理解——典型考题会给出一个配置和一组进程的到达序列与执行时间,要求模拟出调度结果并计算完成时间和等待时间。
软考在进程调度算法上的命题思路有一个明显的特征:不考死记硬背,重在场景推理。仅仅背下每种算法的定义远远不够,必须能在给定一组进程参数的情况下手工推演出调度全过程。
第一类典型题型是给出三到五个进程的到达时刻和执行时间,分别要求在FCFS、SJF、RR和HRRN四种算法下计算平均周转时间和平均等待时间。这类题目的关键在于准确画出每个算法的时间调度图,避免遗漏进程到达时刻对调度决策的影响——比如在SJF非抢占模式下,当前进程正在运行时到达的新进程不能立刻抢占,必须等当前进程执行完毕后一起参与下一轮比较。很多考生的失分不是因为不懂算法规则,而是因为推演过程中漏掉了某个关键时间点,比如新进程到达时当前进程刚好处在某个中间状态,或者RR算法下进程重排队尾时与其他同时到达的进程之间的先后顺序处理有误。
第二类题型是已知某种算法的调度结果,反推进程的到达顺序或执行时间。比如题目告诉你"在非抢占SJF调度下四个进程的完成时间依次为三、八、十五和二十八",然后要求推断每个进程的执行时间。这类题目考的是对SJF规则的反向运用——既然完成时间依次递增,那么执行时间也必然依次递增,因为SJF在任意时刻选择的都是就绪队列中执行时间最短的进程。
第三类题型涉及抢占与非抢占的切换。经典考题是:设进程A先到达开始运行,在运行到一半时进程B到达且执行时间远小于A的剩余执行时间,问在非抢占SJF和抢占SJF两种策略下调度顺序有何不同。非抢占SJF会让A执行完再执行B,抢占SJF会立刻让B抢走CPU。但很多考生容易混淆的是:抢占SJF在每次新进程到达时重新评估"当前谁的剩余执行时间最短",而非抢占SJF在A主动放弃CPU之前什么都不做。
软考题目中频繁出现与"优先级"相关的术语,但它们的含义各不相同。调度优先级指的是进程在就绪队列中被调度的先后顺序,优先级数值小的优先还是大的优先取决于具体实现——在有些系统中零表示最高优先级,有些则相反。静态优先级在进程创建时确定且运行期间不变,动态优先级则根据进程行为或等待时间实时调整。HRRN算法中的响应比本质上就是一种动态优先级。
还有一个容易混淆的点是优先级反转,这是完全不同的概念——它特指在实时系统中一个高优先级进程因为等待被低优先级进程持有的共享资源而无限期阻塞的现象。优先级反转问题的解决方案是优先级继承协议,这与CPU调度优先级是两个层次的问题。软考命题人有时会故意在同一套卷子的不同题目中同时出现"进程调度优先级"和"优先级反转",导致审题不仔细的考生张冠李戴。
回顾整个进程调度算法的知识体系,可以把所有考点归纳为两条主线。第一条主线是算法的选择原则:理解五种经典算法各自解决了什么问题、又带来了什么新问题。FCFS最简单但不公平,SJF平均等待时间最优但会饿死长作业,HRRN在两者之间折中,RR保证了交互式公平但牺牲了周转时间,多级反馈队列则通过分层机制试图同时兼顾所有目标。软考不要求你在工程上实现这些算法,但要求你能在面对一组进程参数时准确地判断不同算法的调度结果差异。
第二条主线是调度性能的量化计算。每种算法的平均周转时间和平均等待时间都是可以精确计算的,尤其是FCFS、SJF和RR这三种,必须做到任何时候都能快速准确地手工推演。推演时注意把进程的到达时刻、开始运行时刻、完成时刻分别列出,用一个时间轴图辅助可视化。建议把本文中五种算法的推演过程至少独立手工练习三遍,直到能够不查阅任何资料就完整画出一组四进程场景下每种算法的完整时间调度图。
进程调度算法在软考中的分值虽然通常只有一到两分,但它是操作系统整章知识体系中承上启下的枢纽节点——向上连接着进程状态转换和进程控制块的数据结构,向下连接着死锁预防和并发控制。如果调度算法这部分的基础不牢,后续学习信号量机制和死锁检测时会发现很多底层逻辑理解起来格外吃力。建议考生在备考时将五种调度算法的判定条件、平均等待时间的推导公式以及抢占与非抢占的行为差异整理成一张对比表,对照历年真题反复练习推演,确保在考场上能在三分钟之内完成一组四进程场景下任意一种算法的调度结果计算。
本篇完!