操作系统的心脏:深入理解进程调度算法
在现代操作系统中,进程调度(Process Scheduling)是核心功能之一。想象一下,你的电脑上同时运行着浏览器、音乐播放器、文档编辑器、后台下载等多个程序。然而,大多数计算机只有一个或几个CPU核心。操作系统必须决定在任何给定时刻,哪个进程能够使用CPU,以及可以使用多长时间。这个决策过程,就是进程调度。
调度的目标有很多,包括最大化CPU利用率、最大化吞吐量(单位时间内完成的进程数量)、最小化周转时间(进程从提交到完成的总时间)、最小化等待时间(进程在就绪队列中等待CPU的时间)、最小化响应时间(从提交请求到第一次产生响应的时间),以及确保公平性。不同的调度算法在这些目标之间进行权衡。
本文将深入探讨几种常见的调度算法:
- 先来先服务 (FCFS)
- 短作业优先 (SJF),包括非抢占式和抢占式 (SRTF)
- 优先级调度 (Priority Scheduling)
- 时间片轮转 (Round Robin)
- 多级队列调度 (Multilevel Queue Scheduling)
- 多级反馈队列调度 (Multilevel Feedback Queue Scheduling)
我们将通过具体的例子来理解每种算法的工作原理,并分析它们的优缺点。
1. 先来先服务 (First-Come, First-Served - FCFS)
这是最简单、最直观的调度算法。顾名思义,FCFS 按照进程请求CPU的顺序进行调度。它是一个非抢占式算法,一旦一个进程获得CPU,就会一直运行直到完成或因I/O请求而阻塞。
工作原理:
操作系统维护一个就绪队列,新到达的进程会排在队尾。调度器总是选择队头的进程执行。
优点:
- 实现简单。
- 公平(按照到达顺序)。
缺点:
- 平均等待时间可能很长,特别是有少量长作业排在大量短作业之前时,会导致"护航效应"(Convoy Effect)。
- 对交互式系统不利,短的交互式请求可能需要等待很长时间。
示例:
假设有以下三个进程,都在时间 0 到达就绪队列,CPU突发时间(执行所需时间)如下:
进程 | CPU突发时间 |
---|---|
P1 | 24 |
P2 | 3 |
P3 | 3 |
使用FCFS调度,进程按照 P1 -> P2 -> P3 的顺序执行。
- P1 在时间 0 开始执行,在时间 24 完成。
- 等待时间 = 0 (到达时CPU空闲)
- 周转时间 = 24 (完成时间) - 0 (到达时间) = 24
- P2 在时间 24 CPU空闲后开始执行(因为它排在 P1 之后),在时间 24 + 3 = 27 完成。
- 等待时间 = 24 (开始时间) - 0 (到达时间) = 24
- 周转时间 = 27 (完成时间) - 0 (到达时间) = 27
- P3 在时间 27 CPU空闲后开始执行(因为它排在 P2 之后),在时间 27 + 3 = 30 完成。
- 等待时间 = 27 (开始时间) - 0 (到达时间) = 27
- 周转时间 = 30 (完成时间) - 0 (到达时间) = 30
平均等待时间 = (0 + 24 + 27) / 3 = 51 / 3 = 17
平均周转时间 = (24 + 27 + 30) / 3 = 81 / 3 = 27
如果进程顺序是 P2 -> P3 -> P1 呢?
- P2 (3) -> P3 (3) -> P1 (24)
- P2: 等待时间 0, 周转时间 3.
- P3: 等待时间 3, 周转时间 3+3=6.
- P1: 等待时间 6, 周转时间 6+24=30.
- 平均等待时间 = (0 + 3 + 6) / 3 = 9 / 3 = 3
- 平均周转时间 = (3 + 6 + 30) / 3 = 39 / 3 = 13
可以看到,尽管是相同的进程,FCFS下不同的到达顺序会极大影响性能指标。这证明了FCFS的护航效应问题。
2. 短作业优先 (Shortest-Job First - SJF)
SJF算法选择就绪队列中下一个CPU突发时间最短的进程执行。SJF算法在理论上可以证明是具有最小平均等待时间的算法。
SJF有两种变体:非抢占式和抢占式。
2.1 非抢占式 SJF
工作原理:
当CPU空闲时,调度器从就绪队列中选择CPU突发时间最短的进程。一旦选中,该进程会一直运行直到完成。新到达的进程如果CPU突发时间比当前运行进程短,也不能中断当前进程。
优点:
- 相较于FCFS,平均等待时间更短。
- 对于已知突发时间的批处理系统非常有效。
缺点:
- 难以准确预测下一个CPU突发时间(通常使用历史数据进行估计)。
- 可能导致饥饿(starvation),长时间运行的进程可能永远得不到执行,如果不断有短进程进入就绪队列。
示例:
假设有以下进程及它们的到达时间和CPU突发时间:
进程 | 到达时间 | CPU突发时间 |
---|---|---|
P1 | 0 | 7 |
P2 | 2 | 4 |
P3 | 4 | 1 |
P4 | 5 | 4 |
使用非抢占式SJF调度:
- 时间 0: P1 到达。就绪队列: {P1(7)}。P1 开始执行。
- 时间 2: P2 到达。就绪队列: {P2(4)}。P1 正在运行,继续执行。
- 时间 4: P3 到达。就绪队列: {P2(4), P3(1)}。P1 正在运行,继续执行。
- 时间 5: P4 到达。就绪队列: {P2(4), P3(1), P4(4)}。P1 正在运行,继续执行。
- 时间 7: P1 完成执行。就绪队列: {P2(4), P3(1), P4(4)}。选择剩余突发时间最短的 P3(1)。P3 开始执行。
- 时间 8: P3 完成执行。就绪队列: {P2(4), P4(4)}。P2 和 P4 突发时间相同,按 FCFS 原则(或任意 tie-breaking 规则),选择 P2。P2 开始执行。
- 时间 12: P2 完成执行。就绪队列: {P4(4)}。P4 开始执行。
- 时间 16: P4 完成执行。
执行顺序:P1 (0-7) -> P3 (7-8) -> P2 (8-12) -> P4 (12-16)
计算等待时间和周转时间:
- P1: 到达 0, 完成 7. 周转时间 = 7 - 0 = 7. 等待时间 = 周转时间 - 突发时间 = 7 - 7 = 0.
- P2: 到达 2, 完成 12. 周转时间 = 12 - 2 = 10. 等待时间 = 10 - 4 = 6. (P2 在时间 2 到达,直到时间 8 才开始,等待了 6 个时间单位)
- P3: 到达 4, 完成 8. 周转时间 = 8 - 4 = 4. 等待时间 = 4 - 1 = 3. (P3 在时间 4 到达,直到时间 7 才开始,等待了 3 个时间单位)
- P4: 到达 5, 完成 16. 周转时间 = 16 - 5 = 11. 等待时间 = 11 - 4 = 7. (P4 在时间 5 到达,直到时间 12 才开始,等待了 7 个时间单位)
平均等待时间 = (0 + 6 + 3 + 7) / 4 = 16 / 4 = 4
2.2 抢占式 SJF (Shortest-Remaining-Time First - SRTF)
SRTF 是 SJF 的抢占式版本。调度器选择就绪队列中剩余CPU突发时间最短的进程。如果一个新到达的进程的CPU突发时间比当前正在运行进程的剩余时间还要短,当前进程就会被抢占。
工作原理:
调度器始终监控就绪队列中的所有进程(包括新到达的进程和被抢占的进程)。每当有新进程到达或当前进程完成时,调度器就重新评估,选择剩余时间最短的进程。
优点:
- 相较于非抢占式 SJF,平均等待时间更短。
- 对短作业响应更快。
缺点:
- 上下文切换开销较大。
- 仍然需要预测下一个CPU突发时间。
- 长时间运行的进程仍然可能面临饥饿问题,虽然不如非抢占式 SJF 那么严重。
示例:
使用与非抢占式 SJF 相同的进程及到达时间和CPU突发时间:
进程 | 到达时间 | CPU突发时间 |
---|---|---|
P1 | 0 | 7 |
P2 | 2 | 4 |
P3 | 4 | 1 |
P4 | 5 | 4 |
使用 SRTF 调度:
- 时间 0: P1 到达 (突发 7)。就绪队列: {P1(7)}。P1 开始执行。
- 时间 0-2: P1 执行了 2 个时间单位。P1 剩余时间 = 7 - 2 = 5。
- 时间 2: P2 到达 (突发 4)。当前运行 P1 剩余 5。P2 突发 4 < P1 剩余 5。P1 被抢占。就绪队列: {P1(5), P2(4)}。选择 P2。P2 开始执行。
- 时间 2-4: P2 执行了 2 个时间单位。P2 剩余时间 = 4 - 2 = 2。
- 时间 4: P3 到达 (突发 1)。当前运行 P2 剩余 2。P3 突发 1 < P2 剩余 2。P2 被抢占。就绪队列: {P1(5), P2(2), P3(1)}。选择 P3。P3 开始执行。
- Time 4-5: P3 执行了 1 个时间单位。P3 剩余时间 = 1 - 1 = 0。P3 完成执行。就绪队列: {P1(5), P2(2)}。选择剩余时间最短的 P2(2)。P2 恢复执行。
- Time 5: P4 到达 (突发 4)。当前运行 P2 剩余 2。P4 突发 4 > P2 剩余 2。P2 继续执行。就绪队列: {P1(5), P4(4)}。
- Time 5-7: P2 执行了 2 个时间单位。P2 剩余时间 = 2 - 2 = 0。P2 完成执行。就绪队列: {P1(5), P4(4)}。选择剩余时间最短的 P4(4)。P4 开始执行。
- Time 7-11: P4 执行了 4 个时间单位。P4 剩余时间 = 4 - 4 = 0。P4 完成执行。就绪队列: {P1(5)}。选择 P1。P1 恢复执行。
- Time 11-16: P1 执行了 5 个时间单位。P1 剩余时间 = 5 - 5 = 0。P1 完成执行。
执行顺序:P1 (0-2) -> P2 (2-4) -> P3 (4-5) -> P2 (5-7) -> P4 (7-11) -> P1 (11-16)
计算等待时间和周转时间:
- P1: 到达 0, 完成 16. 周转时间 = 16 - 0 = 16. 等待时间 = 16 - 7 = 9. (P1 运行了 [0,2] 和 [11,16], 实际等待了 2+(11-2)=11个时间单位? No, 等待时间是 total_wait_time - burst_time. Total_wait_time = (2-0) + (11-2) = 11 is incorrect calculation. Let's use Turnaround - Burst. )
- P1 运行了 [0,2] 和 [11,16]。总等待时间 = (从 P1 被 P2 抢占的时间点 2 到 P1 恢复执行的时间点 11) - (P2+P3+P2 运行的总时间) = (11-2) - ( (4-2) + (5-4) + (7-5) ) = 9 - (2+1+2) = 9-5=4. 等待时间 = 4.
- P2: 到达 2, 完成 7. 周转时间 = 7 - 2 = 5. 等待时间 = 5 - 4 = 1. (P2 运行了 [2,4] 和 [5,7],等待时间是 [4,5] 这一段,共1个时间单位)
- P3: 到达 4, 完成 5. 周转时间 = 5 - 4 = 1. 等待时间 = 1 - 1 = 0. (P3 运行了 [4,5],没有等待)
- P4: 到达 5, 完成 11. 周转时间 = 11 - 5 = 6. 等待时间 = 6 - 4 = 2. (P4 在时间 5 到达,直到时间 7 才开始,等待了 2 个时间单位)
平均等待时间 = (4 + 1 + 0 + 2) / 4 = 7 / 4 = 1.75
与非抢占式 SJF 的平均等待时间 4 相比,SRTF 的 1.75 确实更低。
3. 优先级调度 (Priority Scheduling)
优先级调度算法为每个进程分配一个优先级。调度器总是选择就绪队列中优先级最高的进程执行。如果多个进程具有相同的最高优先级,则通常使用 FCFS 进行调度。
优先级可以是内部定义的(如基于时间限制、内存需求、I/O与CPU突发时间比)或外部定义的(如由用户或系统管理员设置)。
与 SJF 类似,优先级调度也可以是非抢占式或抢占式。通常讨论的是抢占式优先级调度:当一个具有更高优先级的进程到达时,会抢占当前正在执行的进程。
工作原理:
维护一个按优先级排序的就绪队列。调度器选择优先级最高的进程。如果采用抢占式,新进程到达时,其优先级会与当前进程比较,必要时发生抢占。
优点:
- 可以优先执行重要的任务。
缺点:
- 低优先级进程可能面临饥饿问题(长时间无法执行)。
- 需要解决饥饿问题,例如通过"老化"(Aging)机制逐步提升在系统中等待时间过长的进程的优先级。
- 可能导致优先级反转问题(Priority Inversion),一个高优先级进程被一个或多个低优先级进程阻塞。
示例:
假设有以下进程,都在时间 0 到达,CPU突发时间和优先级如下(数字越小表示优先级越高):
进程 | 到达时间 | CPU突发时间 | 优先级 |
---|---|---|---|
P1 | 0 | 10 | 3 |
P2 | 0 | 1 | 1 |
P3 | 0 | 2 | 4 |
P4 | 0 | 1 | 5 |
P5 | 0 | 5 | 2 |
使用抢占式优先级调度(尽管所有进程都在时间 0 到达,没有后续到达导致的抢占,但我们可以展示其按优先级的执行顺序):
- 时间 0: 所有进程到达。就绪队列 (按优先级排序): {P2(1), P5(2), P1(3), P3(4), P4(5)}。选择优先级最高的 P2。P2 开始执行。
- 时间 0-1: P2 执行完成。就绪队列: {P5(2), P1(3), P3(4), P4(5)}。选择优先级最高的 P5。P5 开始执行。
- 时间 1-6: P5 执行完成。就绪队列: {P1(3), P3(4), P4(5)}。选择优先级最高的 P1。P1 开始执行。
- 时间 6-16: P1 执行完成。就绪队列: {P3(4), P4(5)}。选择优先级最高的 P3。P3 开始执行。
- 时间 16-18: P3 执行完成。就绪队列: {P4(5)}。选择优先级最高的 P4。P4 开始执行。
- 时间 18-19: P4 执行完成。就绪队列: {}。
执行顺序:P2 (0-1) -> P5 (1-6) -> P1 (6-16) -> P3 (16-18) -> P4 (18-19)
计算等待时间和周转时间:
- P2: 到达 0, 完成 1. 周转时间 = 1. 等待时间 = 1 - 1 = 0.
- P5: 到达 0, 完成 6. 周转时间 = 6. 等待时间 = 6 - 5 = 1. (在 P2 执行期间等待了 1 个时间单位)
- P1: 到达 0, 完成 16. 周转时间 = 16. 等时时间 = 16 - 10 = 6. (在 P2 和 P5 执行期间等待了 6 个时间单位)
- P3: 到达 0, 完成 18. 周转时间 = 18. 等待时间 = 18 - 2 = 16.
- P4: 到达 0, 完成 19. 周转时间 = 19. 等待时间 = 19 - 1 = 18.
平均等待时间 = (0 + 1 + 6 + 16 + 18) / 5 = 41 / 5 = 8.2
关于老化(Aging):
为了解决饥饿问题,可以使用老化。例如,系统每隔一段时间就将就绪队列中等待的进程的优先级提高一级。如果一个进程等待了足够长的时间,它的优先级最终会变得足够高,从而获得CPU。
4. 时间片轮转 (Round Robin - RR)
时间片轮转算法是专门为分时系统设计的。它类似于 FCFS,但加入了抢占。每个进程被分配一个固定的时间单位,称为时间片(time quantum)或量子。
工作原理:
就绪队列中的进程按照 FCFS 顺序等待。调度器选择队头进程,允许其运行不超过一个时间片。
- 如果进程在时间片结束前完成,它就释放CPU。
- 如果进程在时间片结束时尚未完成,调度器会抢占它,将它放回就绪队列的队尾,然后调度下一个进程。
优点:
- 对所有进程相对公平(在 CPU 时间分配上)。
- 提供良好的响应时间,特别适合交互式应用。
- 不会出现饥饿。
缺点:
- 上下文切换开销较大,特别是时间片太小时。
- 如果时间片太大,RR 会退化为 FCFS。
- 对 CPU 突发时间较短的进程可能不够友好(可能需要多次上下文切换才能完成)。
示例:
假设有以下三个进程,都在时间 0 到达,CPU突发时间如下。时间片 = 4。
进程 | CPU突发时间 |
---|---|
P1 | 24 |
P2 | 3 |
P3 | 3 |
使用 RR 调度 (时间片=4):
- 时间 0: 所有进程到达。就绪队列: {P1, P2, P3}。
- 时间 0-4: P1 运行 4 个时间单位 (P1 剩余 20)。P1 被抢占,回到队尾。就绪队列: {P2, P3, P1}。
- 时间 4-7: P2 运行 3 个时间单位 (P2 剩余 0)。P2 完成。就绪队列: {P3, P1}。
- 时间 7-10: P3 运行 3 个时间单位 (P3 剩余 0)。P3 完成。就绪队列: {P1}。
- 时间 10-14: P1 运行 4 个时间单位 (P1 剩余 16)。P1 被抢占,回到队尾 (但队列只有 P1 自己)。就绪队列: {P1}。
- 时间 14-18: P1 运行 4 个时间单位 (P1 剩余 12)。P1 被抢占,回到队尾。就绪队列: {P1}。
- 时间 18-22: P1 运行 4 个时间单位 (P1 剩余 8)。P1 被抢占,回到队尾。就绪队列: {P1}。
- 时间 22-26: P1 运行 4 个时间单位 (P1 剩余 4)。P1 被抢占,回到队尾。就绪队列: {P1}。
- 时间 26-30: P1 运行 4 个时间单位 (P1 剩余 0)。P1 完成。就绪队列: {}。
执行顺序:P1(4) -> P2(3) -> P3(3) -> P1(4) -> P1(4) -> P1(4) -> P1(4) -> P1(4)
完成时间:P2=7, P3=10, P1=30.
计算等待时间和周转时间(所有进程到达时间为 0):
- P1: 到达 0, 完成 30. 周转时间 = 30. 等待时间 = 30 - 24 = 6.
- P2: 到达 0, 完成 7. 周转时间 = 7. 等待时间 = 7 - 3 = 4.
- P3: 到达 0, 完成 10. 周转时间 = 10. 等待时间 = 10 - 3 = 7.
平均等待时间 = (6 + 4 + 7) / 3 = 17 / 3 ≈ 5.67
平均周转时间 = (30 + 7 + 10) / 3 = 47 / 3 ≈ 15.67
与 FCFS 相比,短进程 P2 和 P3 的周转时间明显缩短(从 27, 30 到 7, 10),但长进程 P1 的周转时间增加了(从 24 到 30)。平均等待时间也通常优于 FCFS(17 vs 5.67)。
5. 多级队列调度 (Multilevel Queue Scheduling)
多级队列调度将就绪队列分成多个独立的队列。通常根据进程的类型或属性(如系统进程、交互式进程、批处理进程)将其永久分配到某个队列。
工作原理:
- 就绪队列被划分为多个固定优先级队列(例如:最高优先级给系统进程,其次是交互式进程,最低优先级给批处理进程)。
- 每个队列可以使用不同的调度算法(例如,交互式队列使用 RR 提供快速响应,批处理队列使用 FCFS 减少切换开销)。
- 队列之间的调度可以是严格的优先级抢占(高优先级队列为空时才调度低优先级队列的进程)或时间片分配(CPU时间按比例分配给不同队列)。
优点:
- 可以根据进程类型应用合适的调度策略。
- 降低了调度的开销(进程在队列之间不移动)。
缺点:
- 如果队列间采用严格优先级,低优先级队列可能面临饥饿。
- 进程被永久分配到队列,缺乏灵活性(如果一个交互式进程突然变成了计算密集型进程,它也不能移到批处理队列)。
示例:
假设有两个队列:
- 队列 1: 系统进程/交互式进程 (优先级高,使用 RR, 时间片=2)
- 队列 2: 批处理进程 (优先级低,使用 FCFS)
队列间采用严格优先级调度(队列 1 非空时,CPU 只分配给队列 1)。
进程如下(都在时间 0 到达):
进程 | 类型 | CPU突发时间 |
---|---|---|
P1 | 交互式 | 5 |
P2 | 批处理 | 10 |
P3 | 批处理 | 8 |
P4 | 交互式 | 3 |
使用多级队列调度:
- 时间 0: 所有进程到达。队列 1: {P1, P4} (RR)。队列 2: {P2, P3} (FCFS)。
- 优先调度队列 1。队列 1 的顺序是 P1 -> P4 (按 FCFS)。
- 时间 0-2: P1 运行 2 个时间单位 (P1 剩余 3)。P1 被抢占,回到队列 1 队尾。队列 1: {P4, P1}。
- 时间 2-4: P4 运行 2 个时间单位 (P4 剩余 1)。P4 被抢占,回到队列 1 队尾。队列 1: {P1, P4}。
- 时间 4-5: P4 运行 1 个时间单位 (P4 剩余 0)。P4 完成。队列 1: {P1}。
- 时间 5-7: P1 运行 2 个时间单位 (P1 剩余 1)。P1 被抢占,回到队列 1 队尾 (队列只有 P1 自己)。队列 1: {P1}。
- 时间 7-8: P1 运行 1 个时间单位 (P1 剩余 0)。P1 完成。队列 1: {}。
- 队列 1 为空。开始调度队列 2 (FCFS)。队列 2: {P2, P3}。
- 时间 8-18: P2 运行 10 个时间单位 (P2 剩余 0)。P2 完成。队列 2: {P3}。
- 时间 18-26: P3 运行 8 个时间单位 (P3 剩余 0)。P3 完成。队列 2: {}。
完成时间:P4=5, P1=8, P2=18, P3=26.
计算等待时间和周转时间(所有进程到达时间为 0):
- P4: 到达 0, 完成 5. 周转时间 = 5. 等待时间 = 5 - 3 = 2. (P4 运行了 [2,4] 和 [4,5],等待了 0+[2-2]+[4-4]=0? No. Total waiting = (2-0) + (4-2) = 2. P4 runs [2,4] and [4,5]. It waited from 0 to 2. Wait time = 2).
- P1: 到达 0, 完成 8. 周转时间 = 8. 等待时间 = 8 - 5 = 3. (P1 运行了 [0,2] 和 [5,8], 等待了 [2,5] 共 3 个时间单位).
- P2: 到达 0, 完成 18. 周转时间 = 18. 等待时间 = 18 - 10 = 8. (P2 在时间 0 到达,直到时间 8 才开始,等待了 8 个时间单位).
- P3: 到达 0, 完成 26. 周转时间 = 26. 等待时间 = 26 - 8 = 18. (P3 在时间 0 到达,直到时间 18 才开始,等待了 18 个时间单位).
平均等待时间 = (2 + 3 + 8 + 18) / 4 = 31 / 4 = 7.75
这个例子展示了批处理进程 (P2, P3) 如何因为高优先级队列的存在而长时间等待。
6. 多级反馈队列调度 (Multilevel Feedback Queue Scheduling)
多级反馈队列调度是多级队列调度的扩展,它允许进程在队列之间移动。这使得系统可以根据进程的行为动态调整其优先级。它是最通用的调度算法,也是许多现代操作系统(如 Unix、Linux)的基础。
工作原理:
- 设置多个队列,每个队列通常有不同的优先级和调度策略(例如,高优先级队列使用小时间片的 RR,低优先级队列使用大时间片的 RR 或 FCFS)。
- 规则 1: 新进程进入最高优先级的队列。
- 规则 2: 如果进程在一个队列中用完了其时间片但未完成,它会被降级到下一个较低优先级的队列。
- 规则 3: 如果进程在一个队列中完成了其 CPU 突发(在时间片结束前),它保持在同一队列或被移到更高优先级队列(不常见,但可以实现)。
- 规则 4(老化): 为防止饥饿,在较低优先级队列中等待时间过长的进程会被提升到更高优先级队列。
队列之间的调度通常是严格优先级抢占的。
优点:
- 灵活,可以适应不同类型的进程。
- 通过规则 2 和 3,可以近似 SJF(短作业在高优先级队列快速完成)。
- 通过规则 4 (老化),可以防止饥饿。
- 不需要事先知道进程的 CPU 突发时间。
- 对交互式进程响应快(通常在最高优先级队列获得小时间片)。
缺点:
- 实现复杂,需要调整许多参数(队列数量、每个队列的调度算法、升级/降级规则、老化参数等),系统性能对这些参数非常敏感。
示例:
假设有三个队列:
- Q1: RR, 时间片 = 2
- Q2: RR, 时间片 = 4
- Q3: FCFS
队列间严格优先级 (Q1 > Q2 > Q3)。降级规则:进程用完时间片但未完成时,移到下一个较低优先级队列。所有进程都在时间 0 到达。
进程 | CPU突发时间 |
---|---|
P1 | 8 |
P2 | 12 |
P3 | 5 |
使用多级反馈队列调度:
- 时间 0: P1, P2, P3 到达。全部进入 Q1。Q1: {P1, P2, P3}。Q2: {}, Q3: {}。
- 优先调度 Q1。调度 P1。
- 时间 0-2: P1 运行 2 个时间单位 (P1 剩余 6)。P1 降级到 Q2。Q1: {P2, P3}。Q2: {P1}。Q3: {}。
- 调度 Q1 中下一个进程 P2。
- 时间 2-4: P2 运行 2 个时间单位 (P2 剩余 10)。P2 降级到 Q2。Q1: {P3}。Q2: {P1, P2}。Q3: {}。
- 调度 Q1 中下一个进程 P3。
- 时间 4-5: P3 运行 1 个时间单位 (P3 剩余 4)。P3 用完 Q1 时间片,降级到 Q2。Q1: {}。Q2: {P1, P2, P3}。Q3: {}。
- Q1 为空。调度 Q2。Q2 使用 RR, 时间片=4。Q2 顺序: P1 -> P2 -> P3 (FCFS)。
- 时间 5-9: P1 运行 4 个时间单位 (P1 剩余 2)。P1 用完 Q2 时间片,降级到 Q3。Q1: {}。Q2: {P2, P3}。Q3: {P1}。
- 调度 Q2 中下一个进程 P2。
- 时间 9-13: P2 运行 4 个时间单位 (P2 剩余 6)。P2 用完 Q2 时间片,降级到 Q3。Q1: {}。Q2: {P3}。Q3: {P1, P2}。
- 调度 Q2 中下一个进程 P3。
- 时间 13-17: P3 运行 4 个时间单位 (P3 剩余 0)。P3 完成。Q1: {}。Q2: {}。Q3: {P1, P2}。
- Q1, Q2 为空。调度 Q3。Q3 使用 FCFS。Q3 顺序: P1 -> P2。
- 时间 17-19: P1 运行 2 个时间单位 (P1 剩余 0)。P1 完成。Q1: {}。Q2: {}。Q3: {P2}。
- 时间 19-25: P2 运行 6 个时间单位 (P2 剩余 0)。P2 完成。Q1: {}。Q2: {}。Q3: {}。
执行过程简述:
P1: runs 0-2 (Q1) -> 5-9 (Q2) -> 17-19 (Q3). Finishes at 19.
P2: runs 2-4 (Q1) -> 9-13 (Q2) -> 19-25 (Q3). Finishes at 25.
P3: runs 4-5 (Q1) -> 13-17 (Q2). Finishes at 17. (Wait, P3 burst was 5. 1 unit in Q1, 4 units in Q2. Total 5. P3 finishes at 17).
让我们重新计算完成时间:
- Time 0-2: P1 (rem 6) -> Q2. Q1:{P2,P3}, Q2:{P1}
- Time 2-4: P2 (rem 10) -> Q2. Q1:{P3}, Q2:{P1,P2}
- Time 4-5: P3 (rem 4) -> Q2. Q1:{}, Q2:{P1,P2,P3}
- Q1 empty. Schedule Q2 (RR, Q=4). Q2 order: P1, P2, P3.
- Time 5-9: P1 (rem 2) -> Q3. Q2:{P2,P3}, Q3:{P1}
- Time 9-13: P2 (rem 6) -> Q3. Q2:{P3}, Q3:{P1,P2}
- Time 13-17: P3 (rem 0). P3 finishes. Q2:{}, Q3:{P1,P2}
- Q2 empty. Schedule Q3 (FCFS). Q3 order: P1, P2.
- Time 17-19: P1 (rem 0). P1 finishes. Q3:{P2}
- Time 19-25: P2 (rem 0). P2 finishes. Q3:{}
完成时间:P3=17, P1=19, P2=25.
计算等待时间和周转时间(所有进程到达时间为 0):
- P3: 到达 0, 完成 17. 周转时间 = 17. 等待时间 = 17 - 5 = 12. (P3 运行了 [4,5] 和 [13,17]. 等待时间 = (4-0) + (13-5) = 4+8=12).
- P1: 到达 0, 完成 19. 周转时间 = 19. 等待时间 = 19 - 8 = 11. (P1 运行了 [0,2], [5,9], [17,19]. 等待时间 = (5-2) + (17-9) = 3+8=11).
- P2: 到达 0, 完成 25. 周转时间 = 25. 等待时间 = 25 - 12 = 13. (P2 运行了 [2,4], [9,13], [19,25]. 等待时间 = (9-4) + (19-13) = 5+6=11).
平均等待时间 = (12 + 11 + 13) / 3 = 36 / 3 = 12.
平均周转时间 = (17 + 19 + 25) / 3 = 61 / 3 ≈ 20.33.
这个例子展示了进程如何在队列间移动,以及短作业 P3 即使中间有被抢占和降级,也能相对较早完成。长作业 P1 和 P2 则会逐渐降级到更低优先级队列执行。
总结
我们探讨了操作系统中的几种主要调度算法。每种算法都有其独特的权衡:
- FCFS: 简单公平,但可能导致长等待时间。
- SJF (非抢占式/SRTF): 理论上最优的平均等待时间,但难以实现且可能导致饥饿。SRTF通过抢占改善了响应时间。
- 优先级调度: 可以优先处理重要任务,但需要老化机制防止饥饿。
- RR: 为分时系统设计,公平且响应时间好,但上下文切换开销大。
- 多级队列: 根据进程类型应用不同策略,实现简单但不够灵活且可能饥饿低优先级队列。
- 多级反馈队列: 最灵活复杂的算法,能动态调整优先级,近似 SJF 并避免饥饿,是现代操作系统的常见选择。
在实际操作系统中,往往会结合这些算法的思想,形成更复杂和精密的调度策略,以达到更好的整体性能和用户体验。选择哪种算法(或其组合)取决于系统的具体需求和设计目标。理解这些基本算法是理解操作系统如何高效管理其宝贵CPU资源的关键。