服务延迟上涨,运维第一反应”机器不够”——但常常不是。真相可能是:CPU 够用,但任务排队等 CPU 等久了。本文介绍怎样把”调度延迟”从总延迟里分离出来,以及生产中调度相关的常见故障与诊断手法。
一、先看图:三种延迟
flowchart LR
EV[事件到来<br/>例如 I/O 完成] --> W[wakeup<br/>task 入 runqueue]
W -->|runqueue 等待| S[schedule<br/>task 被选中]
S -->|CPU 跑| E[处理完成]
W -.wakeup latency.-> S
classDef sig fill:#f0883e22,stroke:#f0883e,color:#adbac7;
classDef run fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class EV,W sig
class S,E run
三个指标:
- wakeup latency:从
try_to_wake_up到实际schedule()切入运行的时间 - runq latency:任务在 runqueue 里等 CPU 的时间(同类含义)
- block time:任务主动睡眠(等 I/O、锁、timer)的时间
总响应时间 ≈ block time + runq latency + 运行时间。前者是”外部依赖”,后者是”调度器 + CPU 占用率”。
二、wakeup / runq latency 测量
2.1 bpftrace runqlat
bpftrace -e '
tracepoint:sched:sched_wakeup,
tracepoint:sched:sched_wakeup_new {
@qt[args->pid] = nsecs;
}
tracepoint:sched:sched_switch /@qt[args->next_pid]/ {
@usecs = hist((nsecs - @qt[args->next_pid]) / 1000);
delete(@qt[args->next_pid]);
}'输出直方图:runq latency (μs) 分布。
bcc-tools 里有现成的
runqlat.py、runqlen.py。
2.2 perf sched
perf sched record -- sleep 30
perf sched latency # 每 task avg/max 等待
perf sched timehist # 时间线
perf sched map # 每 CPU 运行图perf sched latency 的关键列:
Runtime:实际跑的时间Switches:次数Max delay:worst case 等 CPU
max delay > 10ms 就值得关注。
2.3 schedviz
Google 开源可视化工具,把 perf sched 数据画成彩带图,一眼看出某 CPU 空转而其他排队。
三、block time 的分离
bpftrace 抓 sched_switch 的
prev_state = D 或 S 即 block。
bpftrace -e '
tracepoint:sched:sched_switch /args->prev_state == 2/ { // D state
@block[args->prev_pid] = nsecs;
}
tracepoint:sched:sched_wakeup {
if (@block[args->pid]) {
@blocktime = hist((nsecs - @block[args->pid]) / 1000);
delete(@block[args->pid]);
}
}'block time 高一般是 I/O / 锁。C-26 不深入,但用
offcputime、perf record -e sched:sched_switch -g
可抓 off-CPU 火焰图。
四、PSI:系统级延迟压力
cgroup v2 的 PSI 给出 CPU/IO/MEM 的 “some/full” 压力。
/proc/pressure/cpu
some avg10=0.12 avg60=0.08 avg300=0.05 total=...
含义:“过去 10 秒里 12% 时间至少有一个 runnable 任务在等 CPU”。
生产告警阈值:某 cgroup
cpu.pressure some avg10 > 30% → CPU
不足或节流。
systemd-oomd 也读 PSI 做 OOM 预判。
五、生产故障模板
5.1 CFS quota throttle
cgroup v2 cpu.max = 100000 1000000(0.1
核)。任务用完额度 → 被 throttle 到下周期。
症状:延迟 > 100ms 且周期性
诊断:cat /sys/fs/cgroup/.../cpu.stat
看
nr_throttled、throttled_usec
解决:放宽 quota 或 period 调小(100ms → 10ms 可降 worst case)
5.2 NUMA 迁移抖动
症状:进程间歇延迟飙,numastat
显示 remote 访问
诊断:perf stat -e migrations
或 bpftrace sched_migrate_task
解决:numactl --cpunodebind --membind
固定
5.3 RT 饿死
SCHED_FIFO 任务死循环,SCHED_OTHER
全饿。
症状:ssh 不响应;top 看 RT
任务占 100%
救命:echo 950000 > /proc/sys/kernel/sched_rt_runtime_us(恢复节流);手动
chrt -o 降级
5.4 HT / LLC 争用
同 core 两 HT 争 L1/L2;同 socket LLC miss 高。
诊断:perf stat -e cpu-cycles,instructions,cache-misses
看 IPC 降;perf c2c 看 false sharing
解决:isolate CPU、禁用 HT(或
nosmt)、cache partitioning (Intel RDT)
5.5 autogroup 意外
桌面用户并行 compile + 浏览器共 session → autogroup 相同 → 没拉开
诊断:cat /proc/<pid>/autogroup
解决:systemd-run --scope
开单独 scope
六、方法论:三问
Q1: 真实运行时 vs 等待时?
cat /proc/<pid>/schedstat 或
/proc/<pid>/sched 看
sum_exec_runtime vs
wait_sum。wait_sum 大 = 等 CPU。
Q2: 等 CPU 是因为 CPU 忙还是被 throttle?
PSI cpu some;cgroup cpu.stat nr_throttled。
Q3: 迁移/cache miss 占多少?
perf stat 看 IPC、migrations;若 IPC 低但 CPU 不忙,怀疑 cache。
按顺序排查能解大多数”CPU 不忙但服务慢”的案子。
七、工具对照表
| 目标 | 首选工具 |
|---|---|
| runq latency 分布 | bpftrace runqlat.bt / bcc runqlat |
| off-CPU 时间 | bcc offcputime + flamegraph |
| 事件时间线 | perf sched timehist / schedviz |
| 系统压力 | /proc/pressure/ + dashboard |
| cgroup throttle | cpu.stat + bpftrace |
| 迁移 | sched_migrate_task tracepoint |
| cache | perf stat + perf c2c |
八、从 trace 到 flamegraph
perf record -F 99 -g -- ./app
perf script | stackcollapse-perf.pl | flamegraph.pl > cpu.svgoff-CPU flamegraph(Brendan Gregg 首倡):
bcc/tools/offcputime -f -p <pid> 30 > out.stacks
flamegraph.pl --color io < out.stacks > offcpu.svgon-CPU 看哪里在跑;off-CPU 看哪里在等——两条合起来”时间都花哪了”一目了然。
九、观测成本
- tracepoint:几十 ns 每事件;生产可常开
- kprobe/uprobe:200ns+;对热路径小心
- perf record -F 99:可长期,但文件大
- bpftrace aggregate:几乎零成本(每事件更新 map)
经验:tracepoint + bpftrace 聚合是生产推荐;perf record 用于深度调查。
十、常见误诊
- “CPU 用满 = CPU 瓶颈”:可能是
iowait/ spin-wait / scheduler - “loadavg 高 = 忙”:loadavg 包含 D 状态;IO-bound 也高
- “延迟高就是调度”:大多数是上游依赖或 GC;先按三问排
十一、小结
- runq / wakeup / block 三类延迟分开测
- bpftrace + perf + PSI 是诊断主力
- cgroup throttle、NUMA 抖动、RT 饿死、LLC 争用 是高频场景
- on-CPU + off-CPU flamegraph 看时间分布
- 先数据再猜
下一篇 C-27 讲 idle governors——“什么都不做”怎样省电又不丢响应。
参考文献
- Gregg, B. Systems Performance, 2nd ed., Ch. 6
- Gregg, B. “off-CPU analysis” 博客系列
- Corbet, J. “PSI: Pressure Stall Information.” LWN.net 2018
Documentation/trace/events.rstperf-sched(1)man page- bcc/libbpf-tools README
工具
- bcc:
runqlat、runqslower、offcputime、cpudist perf sched、perf stat -e ...trace-cmd record -e sched- schedviz、kernelshark
bpftrace一行命令行
上一篇:big.LITTLE / Intel P+E 下一篇:idle governors:空闲管理
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】cgroup v2:资源控制的统一模型
cgroup v2 把 v1 的多 hierarchy 统一成单一树。本文讲 unified hierarchy、controller 清单、cpu.weight/io.weight/memory.max、PSI 压力指标、systemd slice/scope/service 层级、cgroup delegation 与 rootless、以及 cgroup v2 的诊断姿势。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。