CPU 怎么决定下一个运行谁?进程调度揭秘

你的电脑同时开着浏览器、编辑器、音乐播放器——CPU 只有一个(核),凭什么"同时"服务它们?答案是:每秒切换成百上千次。但"下一个轮到谁"不是随机的,背后是一套精密的裁判规则——进程调度。今天讲透它。

一、调度器在解决什么

就绪队列里排着一长串进程,调度器要回答:这一刻,把 CPU 给谁?

它背负着好几个相互拉扯的目标:

  • 公平:每个进程都觉得自己在被照顾
  • 吞吐:单位时间干最多的活
  • 响应:你按了键,程序多久有反应
  • 防饥饿:别让某个进程永远轮不到

这些目标并不总是一致——最典型的拉扯在"公平"和"吞吐"之间。

二、公平 vs 吞吐:一对天生的矛盾

公平优先:尽量让每个进程平分 CPU。好处是交互程序(编辑器、终端)及时响应;坏处是频繁切换——每次切换都有代价(搬寄存器、刷 TLB、冷缓存),切换太勤,大量时间花在"换人"而不是"干活"上。

吞吐优先:让一个进程一口气多跑一会儿。缓存热、切换少,总产出高——批处理、科学计算最喜欢;坏处是长任务占着 CPU,短任务干等,响应差甚至饿死。

像团队排班:轮流上(公平)都不累但交接成本高;一个人干到底(吞吐)总产出高但其他人闲死。调度器就是调"交接频率"的旋钮。

三、两个核心指标

  • 周转时间:进程从进队到彻底干完的总时长——衡量吞吐
  • 响应时间:进程从进队到第一次被调度——衡量交互体验

这两个指标常常冲突,后面每个调度算法本质都是在它们之间找平衡。

四、代码演示:三种调度策略对比

5 个进程同时到达,运行时间分别是 8、4、2、1、5(单位:时间片):

burst = [8, 4, 2, 1, 5]  # 运行时间

def fifo(burst):       # 先来先服务
    comp, t = [], 0
    for b in burst:
        t += b; comp.append(t)
    return comp

def sjf(burst):        # 短作业优先
    return fifo(sorted(burst))

def rr(burst, q=1):    # 时间片轮转
    rem = burst[:]; comp = [0]*len(burst); t = 0
    while any(rem):
        for i in range(len(rem)):
            if rem[i] > 0:
                run = min(q, rem[i])
                rem[i] -= run; t += run
                if rem[i] == 0: comp[i] = t
    return comp

def stats(comp):
    n = len(comp)
    return sum(comp)/n, (sum(comp)-sum(burst))/n

for name, fn in [("FIFO(先来先服务)", fifo), ("SJF(短作业优先)", sjf), ("RR(时间片=1)", rr)]:
    comp = fn(burst)
    tat, wait = stats(comp)
    print(f"{name:<18} 完成时间{comp} | 平均周转 {tat:.1f} | 平均等待 {wait:.1f}")

运行输出:

FIFO(先来先服务)    完成时间[8, 12, 14, 15, 20] | 平均周转 13.8 | 平均等待 9.8
SJF(短作业优先)     完成时间[1, 3, 7, 12, 20] | 平均周转 8.6 | 平均等待 4.6
RR(时间片=1)        完成时间[20, 14, 8, 4, 17] | 平均周转 12.6 | 平均等待 8.6

看懂三个数字背后的取舍:

  • FIFO:简单但长作业让短作业干等(平均等待 9.8)
  • SJF:平均等待最优(4.6)——但注意运行 8 的长作业被推到 20 才完成,有饥饿风险
  • RR:每个进程轮流沾光(8 的进程 20 完成、1 的 4 完成),响应均匀但总周转略差

没有完美的调度器,只有按系统定位选平衡点。

五、Linux 的答案:CFS

Linux 从早期的 O(n) 扫描、O(1) 多级队列,演进到 2007 年的 CFS(完全公平调度器):用一棵按"虚拟运行时间"排序的红黑树,让每个进程获得均等的 CPU 份额——公平优先的集大成。而实时领域(汽车、工业)走另一条 RM/EDF 路线,把"错过时限"当作不可接受的失败。

六、避坑清单

  1. 别迷信"优先级越高越好":优先级反转(低优先级进程持有锁导致高优先级阻塞)是经典坑,需要优先级继承
  2. 交互系统看响应、批处理看吞吐:选错指标等于选错调度策略
  3. 上下文切换不是免费的:时间片设太小,CPU 全花在换人上(RR 的教训)
  4. I/O 密集 vs CPU 密集要区分:I/O 进程主动让出 CPU,调度器要识别这种"好市民"
  5. 实时调度别用普通 Linux 内核:要 PREEMPT_RT 补丁或 RTOS,普通内核给不了硬实时保证

七、想系统学操作系统?

本文精选自 ima 知识号【Kruptos】《操作系统内核与驱动详解》订阅库(第 018 期调度基础、第 021 期优先级调度、第 025 期用 Python 仿真调度器等 100 期系统教程,从进程线程、内存管理、调度算法到文件系统与驱动开发,每期配可运行代码)。

📚 完整系列 100 期 + 配套代码,已在 ima 知识号发布

本文只是系列的一个切片。完整系列(100 期系统教程 + 每期可运行代码)在 ima 知识号【Kruptos】持续更新中:

  • 🗂 67+ 技术知识库:信号与系统、SDR 软件无线电、数字信号处理、操作系统、AI Agent、大模型微调……几乎覆盖全部软硬件技术栈
  • 🧠 8 款 AI 技能:系列生产、知识库管理、CMMI 受管开发、自进化 Agent 等,已在 ima 技能广场上架,即装即用
  • ✅ 全部免费订阅,后续更新自动推送

🔍 订阅方式:打开 ima(腾讯智能工作台)→ 搜索「Kruptos」→ 一键订阅。或在 ima 内直接搜索《操作系统内核与驱动详解》等知识库名称。

💬 你线上遇到过调度相关的问题吗(CPU 飙高/响应慢)?评论区聊聊——想看 CFS 细节还是优先级反转,点赞高的安排。


作者:Kruptos(西电毕业,13 年无线通信/DSP/嵌入式科研)|原创内容,转载注明出处

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐