Skip to content

CPU 调度

概念

CPU 调度是操作系统内核的核心功能之一:当多个进程(或线程)同时处于就绪状态时,调度器决定下一个获得 CPU 时间片的是谁

调度器分为两层:

  • 长期调度器(作业调度器):控制多少进程进入内存,影响系统并发度。
  • 短期调度器(CPU 调度器):从就绪队列中选出下一个要运行的进程,频率极高(毫秒级)。

调度时机:进程从运行态切换到等待态、就绪态,或进程终止时,调度器介入。

核心原理

1. 调度基本概念

抢占式 vs 非抢占式

类型描述代表算法
非抢占式进程一旦获得 CPU,只有主动放弃(等待 I/O 或退出)才会让出FCFS、非抢占 SJF
抢占式调度器可在任意时刻强制剥夺当前进程的 CPURR、SRTF、优先级抢占

抢占式调度响应性更好,适合交互式和实时系统;但引入了上下文切换开销竞态条件

CPU 密集型 vs I/O 密集型

  • CPU 密集型:大部分时间在做计算,I/O 少(如视频编码、矩阵运算)。
  • I/O 密集型:频繁发起 I/O 请求,大部分时间在等待(如 Web 服务器、数据库查询)。

好的调度策略会优先调度 I/O 密集型进程,让其尽快发起 I/O,同时让 CPU 密集型进程在后台占满 CPU。

调度指标

指标含义
周转时间(Turnaround Time)进程从提交到完成的总时间
等待时间(Waiting Time)进程在就绪队列中等待的累计时间
响应时间(Response Time)提交请求到首次得到响应的时间
吞吐量(Throughput)单位时间内完成的进程数
CPU 利用率(CPU Utilization)CPU 处于忙碌状态的时间比例

不同场景的优化目标不同:批处理系统追求吞吐量和周转时间;交互式系统追求响应时间;实时系统追求截止期满足率。


2. CPU 负载与利用率(高频排查必备)

面试和线上排查最常被问的一组概念:负载(Load)≠ 利用率(Utilization)。前者衡量"有多少任务在排队",后者衡量"CPU 时间花在哪"。

Load Average(平均负载)

uptime / top 第一行的 load average: 1.20, 0.95, 0.80 是过去 1 / 5 / 15 分钟 的平均负载——指处于可运行(R)+ 不可中断(D)状态的任务平均数

load average: 1.20, 0.95, 0.80
              └1min └5min └15min
1min > 5min > 15min  → 负载在上升(要警惕)
1min < 5min < 15min  → 负载在回落

⚠️ Linux 的特殊点:D 状态也计入负载

其他 Unix 只算 R(等 CPU)的任务,Linux 把 D 状态(不可中断睡眠,通常在等磁盘 / NFS I/O)也算进 load。 所以会出现 load 飙到 100+ 但 CPU 利用率很低 的诡异现象 → 大概率是磁盘 / 网络 I/O 卡住,一堆进程卡在 D 状态,不是 CPU 忙。见 进程状态 D

怎么判断 load 高不高:要除以核数。

load average 8.0 在 1 核机器 = 严重过载(800%)
load average 8.0 在 16 核机器 = 很轻松(50%)

经验阈值: load / 核数 持续 > 1  → 排队,可能要扩容
          load / 核数 持续 > 0.7 → 开始关注

CPU 利用率的 8 个分量(必背)

top / mpstat 把 CPU 时间拆成 8 块,看哪一块高,直接定位瓶颈方向

分量含义高了意味着
us(user)用户态代码业务计算重 / 死循环 / 序列化 / 正则回溯
sy(system)内核态系统调用频繁 / 上下文切换 / 中断多
ni(nice)调过 nice 的用户进程低优先级任务在跑
id(idle)空闲越高 CPU 越闲
wa(iowait)等待 I/O 完成磁盘 / 网络 I/O 瓶颈(不是 CPU 忙)
hi(hardirq)硬中断网卡 / 设备中断风暴
si(softirq)软中断高并发网络收发包(见 系统调用与中断
st(steal)被虚拟化偷走的 CPU宿主机超卖,云主机邻居太吵

排查口诀us 高查应用代码;sy 高查系统调用 / 上下文切换;wa 高查磁盘 I/O;si 高查网络;st 高换宿主机 / 找云厂商。

Load vs Utilization 的本质区别(经典面试题)

Load AverageCPU Utilization
衡量排队长度(多少任务在等 + 在跑)时间占比(CPU 忙的比例)
上限无上限(可以 100+)0~100%(每核)
Linux 口径R + D 状态只看 CPU 实际执行
典型背离I/O 卡住 → load 高 + util 低单核打满 → util 100% + load≈1

一句话答:利用率是"CPU 有多忙",负载是"有多少人在排队"。CPU 100% 利用率但 load=1 说明刚好喂饱;load 远大于核数说明任务在排队、响应变慢。Linux 的 load 还包含 D 状态 I/O 等待,所以 load 高不一定是 CPU 问题。

云原生新指标:PSI(Pressure Stall Information)

传统 load average 有缺陷(含 D 状态、不分资源维度)。Linux 4.20+ 引入 PSI,按 CPU / 内存 / IO 三个维度精确量化"因为资源不足而被卡住的时间占比":

bash
cat /proc/pressure/cpu
# some avg10=2.04 avg60=0.50 ...   ← 部分任务因等 CPU 被阻塞的时间比例
cat /proc/pressure/io
cat /proc/pressure/memory

K8s / cgroup v2 用 PSI 做更精准的资源压力感知和驱逐决策,比 load average 更适合容器环境。


3. 调度算法的工作负载适配

调度器的核心矛盾来自两类工作负载(workload)

CPU 密集型(CPU-bound)I/O 密集型(I/O-bound)
特征大部分时间在算大部分时间在等 I/O
例子视频编码、矩阵运算、加解密Web 服务、数据库、文件上传
瓶颈CPU 核数磁盘 / 网络 / 下游延迟
调度诉求长时间片、少切换短时间片、快响应
线程池大小核数 + 1核数 ×(1 + 等待/计算时间),可远大于核数

线程池大小经验公式(Brian Goetz):

其中 = 等待时间 / 计算时间。纯 CPU 型 → 线程数≈核数;I/O 型等待远大于计算 → 线程数可以是核数的几十倍(这也是 虚拟线程 在 I/O 密集场景碾压传统线程池的原因)。

好的调度策略会优先调度 I/O 密集型进程,让它尽快发起 I/O 后去等待,同时让 CPU 密集型进程在后台占满 CPU——这正是下面 MLFQ 多级反馈队列的设计动机。


4. 经典调度算法

FCFS(先来先服务,First-Come First-Served)

按到达顺序排队,非抢占式。

示例:进程 P1(运行 24ms)、P2(3ms)、P3(3ms)按顺序到达。

进程到达时间运行时间完成时间周转时间等待时间
P102424240
P203272724
P303303027

平均等待时间 = (0 + 24 + 27) / 3 = 17ms

护航效应(Convoy Effect):一个长进程阻塞后面所有短进程,导致整体性能下降。


SJF / SRTF(最短作业优先 / 最短剩余时间优先)

  • SJF:非抢占,选择预计运行时间最短的进程。
  • SRTF:抢占式版本,若新到进程的剩余时间更短,立即抢占当前进程。

理论上 SJF 给出最优平均等待时间,但实际问题在于:

  • 无法准确预知运行时间(通常用历史均值指数平均估算)。
  • 饥饿(Starvation):长进程可能永远得不到 CPU,因为不断有短进程插入。

RR(时间片轮转,Round Robin)

每个进程分配固定时间片(quantum),时间片用完后强制切换到下一个就绪进程,循环执行。

时间片大小的权衡

时间片过小时间片过大
响应时间好上下文切换开销小
上下文切换频繁,CPU 利用率下降退化为 FCFS,响应时间差

经验值:时间片通常设为 10~100ms,上下文切换时间约 0.1ms,开销占比控制在 1% 以内。


优先级调度

每个进程附带优先级,调度器始终选择优先级最高的进程(可抢占或非抢占)。

问题:优先级反转(Priority Inversion)

高优先级进程 H 等待低优先级进程 L 持有的锁,而中优先级进程 M 抢占了 L,导致 H 间接被 M 阻塞。

解决方案:

  • 优先级继承协议(Priority Inheritance Protocol, PIP):L 持有锁期间临时继承 H 的优先级,阻止 M 插队。
  • 优先级天花板协议(Priority Ceiling Protocol):锁的优先级上限等于所有可能持有该锁的进程中最高优先级,获取锁时直接提升至该上限。
  • 老化(Aging):随时间推移逐渐提升低优先级进程的优先级,防止饥饿。

MLFQ(多级反馈队列,Multi-Level Feedback Queue)

综合了 RR 与优先级调度,是现代通用操作系统调度的基础思想。

规则

  1. 多条队列,优先级从高到低,高优先级队列时间片短。
  2. 新进程进入最高优先级队列。
  3. 若一个进程用完时间片仍未完成,降级到下一优先级队列。
  4. 若一个进程在时间片内主动放弃 CPU(等待 I/O),保持当前优先级。
  5. 定期将所有进程提升到最高优先级(防止饥饿)。

效果:短进程(I/O 密集型)始终在高优先级队列获得快速响应;长进程(CPU 密集型)自然沉降到低优先级队列。无需提前知道运行时间,自适应性强。


调度算法对比表

算法抢占性优点缺点适用场景
FCFS非抢占实现简单,公平护航效应,平均等待时间长批处理系统
SJF非抢占最优平均等待时间需预知运行时间,长进程饥饿批处理系统
SRTF抢占最优平均等待时间切换开销大,饥饿理论最优基准
RR抢占响应时间均匀,无饥饿时间片设置敏感,上下文切换多交互式系统
优先级两者皆可支持差异化服务优先级反转,低优先级饥饿实时 / 嵌入式
MLFQ抢占自适应,兼顾吞吐与响应实现复杂,参数调优难通用操作系统

5. Linux CFS(完全公平调度器)

Linux 内核(2.6.23+)的默认调度器,用于普通(SCHED_NORMAL/SCHED_BATCH)进程。

核心思想:不分配固定时间片,而是让每个进程获得等比例的 CPU 时间——即"完全公平"。

vruntime(虚拟运行时间)

每个进程维护一个 vruntime 值,表示该进程已经消耗的"加权虚拟时间":

  • :实际运行时间。
  • weight:由 nice 值映射而来,nice 值越低(优先级越高),weight 越大,vruntime 增长越慢,因而获得更多 CPU 时间。

红黑树(Red-Black Tree)组织就绪队列

CFS 用红黑树按 vruntime 排序所有可运行进程,最左节点就是 vruntime 最小的进程,即下一个应该运行的进程。选择复杂度为 ,插入/删除同为

nice 值与权重映射

nice 值范围 -20(最高优先级)到 +19(最低优先级)。内核维护一张预计算的 prio_to_weight 表,相邻 nice 值之间权重比约为 1.25,使得 nice 值差 1 对应约 10% 的 CPU 时间差异。

为什么叫"完全公平"?

理想情况下,n 个权重相同的进程各获得 的 CPU 时间。CFS 通过不断调度 vruntime 最小的进程,使所有进程的 vruntime 趋于相等,在宏观上实现了按权重比例的公平分配。


6. 实时调度

硬实时 vs 软实时

类型描述示例
硬实时必须在截止期内完成,错过即系统失效汽车 ABS、飞控系统
软实时尽量在截止期内完成,偶尔错过可接受视频播放、音频流

Linux 实时调度策略

Linux 提供两种实时调度策略,优先级高于 CFS:

  • SCHED_FIFO:实时 FCFS,无时间片。进程一旦运行,除非主动放弃或被更高优先级进程抢占,否则一直运行。
  • SCHED_RR:实时 RR,有时间片。时间片用完后,同优先级的实时进程间轮转。

实时优先级范围为 1~99(数字越大优先级越高),均高于所有普通进程(优先级为 0)。

优先级反转与优先级继承协议

Linux POSIX 互斥锁支持 PTHREAD_PRIO_INHERIT 属性,启用后实现优先级继承:当高优先级线程等待锁时,持锁的低优先级线程临时提升至高优先级,确保其尽快释放锁,避免中间优先级线程横插。


7. nice 值与生产调优实战

nice 是普通用户最常用的优先级控制工具,2025-2026 年 Linux 面试常考。

nice 值范围与影响

nice 取值: -20 ~ +19
  -20 = 最高优先级(CFS 权重最大)
  +19 = 最低优先级(CFS 权重最小)
   0  = 默认

权重映射: weight = 1024 / (1.25 ^ nice)
  nice = 0  → weight 1024
  nice = -20 → weight 88761  (约 86×)
  nice = +19 → weight 15     (约 1/68×)

实战命令

bash
# 启动时指定 nice
nice -n 10 ./batch-job.sh             # 低优先级跑批

# 修改运行中进程
renice -n -5 -p 12345                 # 提升 PID 12345 优先级(需 root)

# 实时调度
chrt -f 50 ./latency-critical.sh      # SCHED_FIFO, 优先级 50
chrt -r 30 ./video-server.sh          # SCHED_RR, 优先级 30
chrt -p <pid>                         # 查看进程调度策略

⚠️ 普通用户只能降低优先级

非 root 用户只能把 nice 调(降低优先级),不能调小。这是 Linux 防止用户进程抢系统资源的硬约束。

8. cgroups:容器化时代的 CPU 调度核心

cgroups(Control Groups) 是 K8s / Docker 实现 CPU 限制的底层机制。容器化运维必问——能讲清楚 CPU requests / limits 怎么映射到 cgroups,是 SRE 加分点。

cgroups v1 vs v2

版本状态关键差异
v1老版本,逐步淘汰每个子系统独立层级(cpu / memory / blkio 分开)
v2RHEL 9 / Ubuntu 22+ 默认统一层级(一个 cgroup 同时控制 CPU+内存+IO)

CPU cgroups 三种限制方式

1. cpu.shares (v1) / cpu.weight (v2):
   相对权重,CPU 紧张时按权重分配
   例: container-A weight=100, container-B weight=200
   → CPU 紧张时 A:B = 1:2 分配

2. cpu.cfs_quota_us / cpu.cfs_period_us (v1)
   cpu.max (v2): "200000 100000"  ← quota / period
   绝对上限: 每 period(默认 100ms) 最多用 quota 微秒
   K8s 的 limits.cpu=2 → quota=200000, period=100000

3. cpuset.cpus:
   绑定到指定 CPU 核心,避免跨核切换
   例: cpuset.cpus="0-3" → 只能在 0-3 号核运行

K8s CPU requests / limits 映射

yaml
resources:
  requests:
    cpu: "500m"      # 0.5 核
  limits:
    cpu: "2"         # 2 核
requests.cpu=500m  →  cpu.weight = 51 (v2)  ← 影响 CPU 紧张时调度优先级
limits.cpu=2       →  cpu.max = "200000 100000"  ← 硬上限

⚠️ CPU Throttling 陷阱

K8s 的 CPU limits硬上限——即使节点空闲,超 limits 也会被强制降速(throttle),导致响应延迟突刺

生产实践

  • ① 不要给延迟敏感服务设 CPU limits(只设 requests)
  • ② 或用 Burstable QoSrequests < limits,但理解会被 throttle
  • ③ 监控 container_cpu_cfs_throttled_seconds_total,超过 5% 立即扩容

9. EEVDF:CFS 的继承者(Linux 6.6+)

EEVDF(Earliest Eligible Virtual Deadline First) 是 Linux 6.6(2023.10)引入的新默认调度器,正在逐步替代 CFS。能讲到 EEVDF 是了解前沿动态的强信号。

维度CFSEEVDF
核心思想最小 vruntime 优先最早 virtual deadline 优先
延迟优化(每任务可配 latency-nice 期望延迟)
公平性长期公平保持公平 + 短期可定制延迟
状态Linux ≤ 6.5 默认6.6+ 默认

EEVDF 的关键优势:交互式任务可以声明"我希望 5ms 内被调度",调度器尽量满足;批处理任务声明"我不在乎延迟",给它更长的运行片提升吞吐。

面试常问 & 怎么答

Q1:常见的 CPU 调度算法有哪些?各有什么优缺点?

先列举算法名称,再抓核心特点:FCFS 简单但有护航效应;SJF 理论最优但无法预知运行时间且有饥饿;RR 响应均匀但时间片选择敏感;优先级调度灵活但有优先级反转风险;MLFQ 综合最优,现代 OS 的基础。面试时重点展开 MLFQ 的设计思路(多队列 + 反馈降级 + 定期提升)和 RR 的时间片权衡,体现系统性思维。

Q2:Linux 用的是什么调度算法?CFS 是怎么工作的?

回答分三层:① 背景——Linux 2.6.23 引入 CFS 替代旧的 O(1) 调度器;② 核心机制——vruntime 记录加权虚拟时间,红黑树排序就绪队列,始终调度 vruntime 最小的进程;③ 公平性——nice 值通过 prio_to_weight 表映射权重,权重影响 vruntime 增长速率,从而按比例分配 CPU。能画出红黑树示意图或写出 vruntime 公式会加分。

Q3:什么是优先级反转?怎么解决?

先描述场景:高优先级进程 H 因等待低优先级进程 L 持有的共享资源而阻塞,中优先级进程 M 抢占 L,导致 H 间接等待 M——这就是优先级反转。然后给出解决方案:优先级继承协议(L 临时继承 H 的优先级)和优先级天花板协议(获锁时提升至预设上限)。可以提 Mars Pathfinder 真实案例(1997 年 NASA 火星探路者遭遇优先级反转导致系统重启),展示工程经验。

Q4:load average 和 CPU 利用率有什么区别?load 很高一定是 CPU 忙吗?

利用率衡量"CPU 时间花在哪"(0~100%/核),load 衡量"有多少任务在排队"(无上限)。关键反例:load 很高但 CPU 利用率很低——因为 Linux 把不可中断 D 状态(等磁盘 / 网络 I/O)也计入 load,所以这种情况通常是 I/O 瓶颈而非 CPU 忙。判断 load 高不高要除以核数(load/核数 > 1 才算排队)。再补一句进阶:容器环境更推荐看 PSI(/proc/pressure/*),按 CPU/内存/IO 分维度量化资源压力。

Q5:线程池大小怎么定?

看工作负载类型。CPU 密集型 → 线程数≈核数+1(再多只会增加上下文切换);I/O 密集型 → 按 Goetz 公式 核数 ×(1 + 等待/计算),等待远大于计算时可以是核数的几十倍。本质是让 CPU 在某些线程等 I/O 时还有别的线程可跑。JDK 21+ 的虚拟线程把这个公式推到极致——I/O 密集场景直接每请求一线程、百万并发。

看到什么就先想到这类

关键词 / 场景联想到
进程等待时间长、短进程被阻塞FCFS 护航效应
调度算法理论最优SJF/SRTF,但注意饥饿
交互式系统、响应时间均匀RR 时间片轮转
load 高但 CPU 利用率低I/O 瓶颈 + D 状态进程,不是 CPU 忙
top 里 wa 高磁盘 / 网络 I/O 等待
top 里 st 高虚拟化超卖,宿主机邻居太吵
top 里 sy 高系统调用 / 上下文切换频繁
容器精准资源压力监控PSI(/proc/pressure)优于 load average
线程池大小怎么定CPU 密集≈核数+1;I/O 密集按等待/计算比放大
高优先级进程莫名阻塞优先级反转,考虑优先级继承
Linux 普通进程调度CFS + vruntime + 红黑树
Linux 实时进程SCHED_FIFO / SCHED_RR,优先级 1~99
系统需要同时兼顾响应和吞吐MLFQ 多级反馈队列
nice 值 / 进程优先级调整CFS 权重映射,影响 vruntime 增长速率