计算机 · 2026 年 11 月 11 日

CPU 跑到 100% 时,Linux 上到底发生了什么

CPU 100% 不一定是故障,load 很高也不一定是 CPU 不够。从 /proc/stat 到 cgroup 限流,拆开看内核到底在忙什么。

告警只有一行:CPU 使用率 100%。登上机器,top 里某个 Java 进程占着 780%,uptime 却是 load average: 32.15。但接口 P99 只从 80ms 涨到 110ms,业务没挂。这种时候,100% 是需要立刻处理的故障,还是一个被误读的数字,取决于你接下来看哪几个文件。

反过来的事故更常见:CPU 只有 20%,load 到了 40,整台机器都在超时。两个数字指向完全不同的内核行为,先把它们分开。

100% 的分母是「一个核」#

top 里进程的 %CPU 以单核为分母。16 核机器上一个死循环线程显示 100%,整机理论上限是 1600%。所以看到一个进程 100%,先问:它是单线程打满,还是多线程均摊。

单线程打满通常是代码问题:忙等、正则回溯、序列化大对象。多线程均摊才更像容量问题。用 top -H -p <pid> 展开线程,如果只有一个线程接近 100%,其它线程在睡觉,方向就明确了。

首行的 %Cpu(s) 才是整机视角,按 1 可以展开成每核。nproc 给出可用核数,但容器里它返回的往往是宿主机核数,算容量时要看 cpu.max 而不是 nproc。

另外两个工具的数字经常对不上:top 给的是采样区间内的瞬时占比,ps -o pcpu 给的是进程启动至今的平均值。长跑进程在 ps 里看着温和,实际可能刚刚飙起来,判断当前状态以 top 或 pidstat 1 为准。

load average 里混着 D 状态#

cat /proc/loadavg 输出五个字段:1、5、15 分钟的平均值,当前运行实体数/总进程数,以及最近分配的 PID。前三个数不是 CPU 使用率,而是处于 R(可运行)和 D(不可中断睡眠)状态的任务数的指数衰减平均。

关键在 D。进程卡在磁盘 IO、NFS、某些内核锁上时,一点 CPU 都不消耗,但照样计入 load。所以 load 高、CPU 低,第一件事是找 D 状态进程:

bash
ps -eo stat,pid,comm | awk '$1 ~ /^D/'
vmstat 1 5

vmstat 的 r 列是可运行队列长度(不含 D),b 列是阻塞任务数。r 持续大于核数,说明 CPU 真的排满了;r 很小而 b 不小,问题在 IO。

按核数折算的经验值:load 等于核数时队列刚好排满,到两倍开始明显排队。但这只是经验,IO 密集的机器 load 常年偏高,不能直接当 CPU 指标用。

us、sy、wa、si 各说明什么#

vmstat 1 的 CPU 列里,us 是用户态时间,sy 是内核态,wa 是等 IO 的空闲(新内核里统计口径有变化),st 是被虚拟化层偷走的时间。

sy 高通常有三种来源:系统调用太频繁(大量小 read/write)、上下文切换过多、软中断集中。si 那一列高,说明 CPU 在收包,检查 /proc/interrupts 是否集中在 0 号核,网卡多队列或 RPS 没配好是常见原因。

写监控时别直接读 /proc/loadavg 的瞬时值。/proc/stat 第一行的 cpu 字段是 jiffies 累计值,除以 getconf CLK_TCK(多数系统是 100)换算成秒,两次采样做差才是准确的利用率。

想按进程拆开看,用 pidstat 1 的 %usr、%system、%iowait 三列;想看到调用栈级别,用 perf top。如果榜首出现 native_queued_spin_lock_slowpath 这类符号,基本就是锁竞争让 CPU 空转了,加核并不能提高吞吐。

整机平均还会掩盖单核瓶颈。mpstat -P ALL 1 能列出每个逻辑核的占用,如果只有一两个核接近 100%、其余大量 idle,先查中断亲和性和线程绑核,而不是扩容。

上下文切换:在等,还是被抢#

pidstat -w -p <pid> 1 输出两列:cswch/s 是自愿切换,进程主动让出 CPU,多半在等锁、等 IO、sleep;nvcswch/s 是非自愿切换,时间片用完被内核抢占。

前者高说明在等资源,后者高说明 CPU 不够分。grep ctxt /proc/<pid>/status 能看到累计值,两次相减除以间隔就是速率。cat /proc/<pid>/schedstat 的第三个字段是任务在可运行队列里等待 CPU 的累计纳秒数,它持续增长而进程自身的 CPU 时间不涨,说明线程在排队,而不是在干活。

有个容易被忽略的组合:线程数远大于核数,每个线程都抢同一把锁。nvcswch 飙升,sy 上升,吞吐下降,但 CPU 看着只有 60%。此时加机器没用,要减线程或缩短临界区。

现象 先看什么 常见原因
单进程 %CPU 100%,只有一核满 perf top -p、top -H 死循环、正则回溯、单线程热点
load 高、CPU 低、b 列有值 iostat -x 1、D 状态进程 磁盘慢、NFS 卡住、同步写日志
sy 高、cs 高 pidstat -w、perf trace 锁竞争、线程过多、频繁系统调用
si 集中在少数核 /proc/interrupts 网卡单队列、RPS 未配置
容器内 CPU 不高但延迟抖动 cpu.stat 的 nr_throttled cgroup 配额被打满后限流

容器里还有一层:throttling#

cgroup v2 下 cat /sys/fs/cgroup/cpu.stat,nr_throttled 和 throttled_usec 持续增长,说明进程在配额周期内被限流过。cpu.max 写成 200000 100000,意思是每 100ms 周期最多使用 200ms CPU,等价 2 核。

被限流的线程会停顿几十毫秒,表现为 P99 抖动,而平均 CPU 只有 50%——top 完全看不出来。这也是「容器里 CPU 没用满但就是慢」最常见的原因。

docker run --cpus=2 最终写进 cpu.max 的也是同样的 200000 100000。注意容器内的 top 仍按宿主机核数算百分比,2 核配额跑满时整机只显示 6%,拿它判断容器压力会严重低估。

想确认热点,用采样而不是猜:perf record -F 99 -g -p <pid> -- sleep 30,再生成火焰图看 on-CPU 的栈。机器上进程很多时把 -p 换成 -a,先看全局热点,再缩小到具体 PID。strace -c 能统计系统调用耗时,但会给目标进程带来明显开销,生产环境只在短时间窗口内用。

perf top 的三个列分别是被采样到的比例、所属共享对象和符号名。看到比例集中在 libc 的 memcpy,问题通常不在 memcpy 本身,而在于调用它的地方在做大块复制;加上 -g 打开调用图,才能顺着栈找到真正的业务函数。

云端还要看 steal time#

虚拟机里 %Cpu(s) 有一列 st,表示物理核被别的租户占走、本该给你却没给的时间。st 长期大于 0,说明宿主机超卖,这时候容器内怎么调优都没用,该换规格或者换宿主机。top 的 st 只在整机视角显示,单进程看不到。

结论#

  • 先分清整机 %Cpu(s) 和单进程 %CPU 的分母,再看是单线程打满还是多核均摊。
  • load average 包含 D 状态,load 高不等于 CPU 忙;用 vmstat 的 r 和 b 列区分排队与阻塞。
  • sy 高查系统调用与上下文切换,si 高查中断分布,别一律归因于「代码慢」。
  • 容器场景必须看 cgroup 的 cpu.stat,限流造成的抖动在宿主机监控里是隐形的。
  • 定位热点靠采样(perf),不靠重启和猜测;重启只能清空现场。

完

#Linux#性能#排障#内核
回到顶部 ↑