1. 调度器基础:操作系统调度器的作用、调度策略分类与进程状态机

大家好,我是你们的老朋友。今天咱们聊聊调度器——这个操作系统里最忙的“交通指挥官”。

说实话,我早年刚接触内核时,觉得调度器不就是“谁排队谁先上”嘛。直到我在一个嵌入式项目中,因为调度策略选错,导致实时任务频频超时,产品差点跳票。嗯,从那以后,我再也不敢小看调度器了。

1.1 调度器到底在干什么?

调度器的核心任务,说白了就一句话:决定下一个该谁用CPU。

你想想看,一台机器上跑着几十上百个进程,但CPU就那么几个核。谁先跑、谁后跑、跑多久——这些全是调度器说了算。

我个人习惯把调度器比作一个“多路复用器”:

  • 输入端:所有就绪态的进程
  • 输出端:当前选中的进程
  • 控制逻辑:调度策略 + 优先级 + 时间片

调度器要解决的三个核心问题:

  1. 公平性:每个进程都能分到CPU时间,别饿死谁
  2. 效率:尽量减少上下文切换的开销
  3. 响应性:交互式任务要能快速响应

重要提醒:调度器不是万能的。它只能管理“就绪态”的进程。如果一个进程在等I/O,调度器再牛也帮不了它。

1.2 调度策略分类:抢占式 vs 非抢占式

这是调度器设计中最基础的分水岭。我当年面试新人时,第一个问题经常就是:“你说说抢占式和非抢占式有什么区别?”

非抢占式调度

进程一旦获得CPU,就会一直运行,直到它主动放弃(比如等待I/O、主动sleep、或者运行结束)。

优点很明显:实现简单,上下文切换少。缺点也很致命:一个进程如果死循环,整个系统就卡死了。

我记得早期的一些嵌入式RTOS(比如uC/OS早期的版本)就支持这种模式。在资源极度受限的场景下,它确实够用。

抢占式调度

操作系统可以在任意时刻打断当前进程,把CPU分配给另一个进程。打断的时机通常由时钟中断触发。

这是现代通用操作系统的主流选择。Linux、Windows、macOS 全是抢占式的。

为什么?因为用户希望:你一边下载文件,一边还能流畅地打字。没有抢占,这是做不到的。

特性 非抢占式 抢占式
控制权 进程主动让出 操作系统强制切换
响应时间 不可预测 可预测(有上限)
实现复杂度 低 高
适用场景 简单嵌入式系统 通用OS、实时系统
典型代表 早期uC/OS Linux、FreeRTOS

我的经验:在Linux内核中,抢占式调度还分“内核抢占”和“用户抢占”。内核抢占意味着即使进程正在执行系统调用,也可能被抢走CPU。这大大提升了响应性,但也增加了内核同步的复杂度。我曾经在调试一个驱动时,就因为没处理好抢占导致竞态条件,查了整整两天。

1.3 调度器与进程状态机的关系

调度器不是凭空工作的。它依赖进程状态机来知道“谁可以跑、谁在等、谁在睡”。

进程的经典三态模型:

  • 运行态(Running):正在使用CPU
  • 就绪态(Ready):万事俱备,只差CPU
  • 阻塞态(Blocked):在等某个事件(I/O、锁、信号量)

调度器只关心“就绪态”的进程。阻塞态的进程,调度器根本不会看它一眼。

下面这张图,是我画的一个简化版进程状态转换图,你可以直观地看到调度器在其中的位置:

就绪态 运行态 阻塞态 调度器选择 时间片用完/抢占 等待I/O/锁 I/O完成/锁释放 调度器核心区域

这张图里,调度器就站在“就绪态”到“运行态”的那条箭头上。它负责从就绪队列中挑一个进程,让它上CPU跑。

为什么会这样?因为调度器只做一件事:从就绪队列里选一个进程。至于进程怎么进入就绪队列、怎么从运行态变成阻塞态——那是进程自己或内核其他模块的事。

避坑指南:我曾经在调试一个多线程应用时,发现某个线程明明优先级很高,却总是得不到CPU。查了半天,发现它一直在阻塞态等一个锁,而锁的持有者优先级更低,被别的进程抢占了。这就是典型的“优先级反转”。调度器本身没毛病,但状态机之间的交互出了问题。

1.4 调度器的工作流程(简化版)

调度器每次被调用时,大致走这么几步:

  1. 保存当前进程的上下文(寄存器、PC、栈指针)
  2. 将当前进程移出运行态(放回就绪队列或阻塞队列)
  3. 从就绪队列中选一个进程(根据调度策略)
  4. 恢复选中进程的上下文
  5. 跳转到该进程的PC继续执行

这五步,就是一次完整的上下文切换。在Linux中,核心函数是 context_switch()。

我个人习惯把调度器触发时机归纳为四类:

  • 时钟中断:时间片到了,强制切换
  • 进程主动让出:调用 sched_yield() 或 sleep()
  • 进程阻塞:等待I/O、锁、信号量
  • 进程创建/退出:新进程加入就绪队列,或旧进程终止

嗯,这里要注意:时钟中断是抢占式调度的“心脏”。没有它,抢占就无从谈起。

1.5 小结

调度器的基础,说白了就三件事:

  • 调度器的作用:决定下一个谁用CPU
  • 调度策略:抢占式 vs 非抢占式,各有各的适用场景
  • 与进程状态机的关系:调度器只操作就绪态,其他状态由内核其他模块管理

这些概念看起来简单,但它们是后面所有调优工作的地基。我见过太多人一上来就调优先级、改时间片,结果越调越乱——因为连调度器什么时候被触发都没搞清楚。

下一节,我们会深入Linux的CFS调度器,看看它到底是怎么“公平”地分配CPU时间的。到时候我会分享一些我在生产环境中调优CFS参数的真实案例。


可可精品资料库