1. 什么是调度(Scheduling)
操作系统中的调度器(Scheduler)负责决定在任意时刻哪个进程将使用 CPU 资源。调度器主要在以下四种情况下进行决策:
- 当一个进程从运行状态切换到等待状态(例如发起 I/O 请求)
- 当一个进程终止或执行完成
- 当一个进程因中断等原因从运行状态切换到就绪状态
- 当一个进程完成 I/O 操作或获得所需资源,从等待状态回到就绪状态
前两种情况相对简单:调度器只需从就绪队列中选择下一个进程即可。非抢占式调度只允许在这两种情况下进行切换;而抢占式调度则允许在第3和第4种情况下也进行调度决策,这就带来了更大的灵活性。
2. 非抢占式调度(Non-Preemptive Scheduling)
非抢占式调度是一种进程一旦开始执行,就会一直运行到结束或进入等待状态的调度策略。 在此期间,该进程不会被中断。
常见的非抢占式调度算法包括:
- 先来先服务(First-Come-First-Serve, FCFS):按照进程到达的顺序依次执行。
- 最短作业优先(Shortest-Job-First, SJF):优先执行预计运行时间最短的进程。
所有非抢占式调度算法的工作流程大致如下:
新进程通常插入到队列尾部(如 FCFS),调度器根据算法选择一个进程执行。一旦开始执行,除非该进程主动释放 CPU(如进入 I/O 等待),否则不会被打断。
如果进程在运行过程中需要进行 I/O 操作或等待资源,则会进入等待状态。一旦 I/O 完成或资源可用,该进程将立即回到就绪队列头部,等待再次执行。
2.1 优点
✅ 实现简单:非抢占式调度逻辑清晰,易于实现。
✅ 调度开销小:没有频繁的上下文切换,调度效率高。
✅ 吞吐量较高:单位时间内完成的进程数量相对较多。
2.2 缺点
❌ 响应时间差:因为必须等待前面的进程执行完毕,平均响应时间较长。
❌ 缺乏灵活性:进程一旦运行,无法中断,即使有更高优先级的进程到达也无法及时响应。
❌ 难以支持优先级调度:比如一个低优先级但运行时间很长的进程正在执行,此时一个高优先级进程到达,只能等待,无法抢占。
❌ 容易导致死锁(Deadlock):如果两个进程互相等待对方持有的资源,且无法抢占,就可能进入死锁状态。非抢占是死锁产生的必要条件之一。
3. 抢占式调度(Preemptive Scheduling)
抢占式调度允许调度器在进程运行一段时间后主动中断它,将其放回就绪队列,以便为其他进程提供执行机会。
常见抢占式调度算法包括:
- 轮转法(Round Robin):每个进程分配一个固定的时间片,轮流执行。
- 最短剩余时间优先(Shortest-Remaining-Time-First, SRTF):优先执行剩余时间最短的进程。
- 基于优先级的调度(Priority Scheduling):优先级高的进程可中断当前运行的低优先级进程。
其一般流程如下:
当时间片用完或有更高优先级进程到达时,当前进程会被中断,返回就绪队列等待下一次调度。
如果进程需要执行 I/O 或等待资源,也会进入等待状态,完成后重新回到就绪队列。
3.1 优点
✅ 响应时间更优:高优先级或短任务可以更快获得 CPU。
✅ 避免 CPU 被独占:不会让一个进程长时间占用 CPU。
✅ 支持动态优先级调整:可以根据进程状态变化动态调整执行顺序。
✅ 避免死锁:允许中断进程,有助于打破死锁的必要条件。
3.2 缺点
❌ 上下文切换开销大:频繁中断和切换进程会增加系统开销。
❌ 可能导致进程饥饿(Starvation):低优先级进程可能长时间得不到执行机会,尤其在高优先级进程持续到达时。
4. 抢占式 vs 非抢占式调度对比
特性 | 抢占式调度 | 非抢占式调度 |
---|---|---|
是否允许中断 | ✅ 是 | ❌ 否 |
进程切换时机 | 运行 → 就绪 / 等待 → 就绪 | 仅运行 → 等待 / 结束 |
是否支持优先级 | ✅ 是 | ❌ 否 |
响应时间 | 更好 | 更差 |
吞吐量 | 相对较低 | 较高 |
CPU 利用率 | 高 | 低 |
是否可能导致饥饿 | ✅ 是 | ❌ 否 |
是否可能导致死锁 | ❌ 否 | ✅ 是 |
调度开销 | 高 | 低 |
5. 总结
本文详细介绍了抢占式调度和非抢占式调度两种调度策略的原理、特点和适用场景。
- 非抢占式调度简单高效,适用于对响应时间要求不高的场景,但灵活性差,难以支持优先级和避免死锁。
- 抢占式调度更灵活,响应时间更优,适合需要高响应性的系统,但带来了额外的上下文切换开销。
在实际操作系统设计中,往往根据需求选择合适的调度策略,或者结合两者优点设计混合调度机制。
⚠️ 踩坑提醒:在设计调度策略时,务必考虑系统对响应时间、公平性和资源利用率的综合需求,避免盲目选择单一调度方式导致性能瓶颈或资源饥饿问题。