预备知识库:计算机组成原理(如内存 Hierarchy、CPU 基本组成),操作系统原理(kernel/user space、资源抽象、进程状态抽象等)。

以下是很零碎、经验化的思路,总结下来方便回顾。

1. CPU 分析

业界主流的对于 CPU 忙碌情况的指标是 “Load Average”,定义为 处于可运行状态(running)和不可中断睡眠(uninterruptible sleep,通常在等待 I/O)的进程数平均值。相关知识参见 附录 II

Linux 上使用 top 类指令查看到的 load average 通常有 3 个数字,分别代表在过去 1 分钟、5 分钟、15 分钟内的 load average。

例:load average: 2.35, 1.87, 1.25 表示 1 分钟平均有 2.35 个进程在等待 CPU 或 I/O 资源(粗略认为)。

一般情况正常工作的理想状况 load average 约等于 CPU 核心数。load average 两倍于 CPU 核心数超过 5 分钟认为过载预警,4 倍于 CPU 核心数认为严重过载。

另一个指标是 CPU 利用率(CPU 时间分配比例)。也可通过 top 类指令观察到:

  • %usrus):用户进程占用 CPU 时间百分比(应用程序)
  • %niceni):手动调整过优先级的用户进程占 CPU 时间百分比
  • %syssy):内核进程占用 CPU 时间百分比(系统调用、中断处理)
  • %idleid):CPU 空闲时间百分比
  • %iowaitwa):CPU 等待 I/O 完成的时间百分比(不是 CPU 被占用,而是空闲但有未完成的 I/O 请求
  • %softit / %harditsi / hi):处理软中断/硬中断的 CPU 时间占比
  • %stealst):虚拟机等待物理 CPU 的时间百分比(仅虚拟化环境)

此外 vmstat 指令能提供更多全局数据,如:

1
2
3
4
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 0 0 123456 78901 234567 0 0 0 0 123 4567 85 10 5 0 0
  • r (running):等待 CPU 的进程数,持续 >> CPU 核心数表示 CPU 瓶颈
  • b (blocked):等待 I/O 的进程数,持续 > 0 可能存在 I/O 瓶颈
  • us + sy持续 > 90% 表示 CPU 资源紧张
  • cs (context switch):context switch 次数,> 10 万/秒 可能存在资源竞争问题

排除 CPU 瓶颈的手段:

  1. 确认 CPU 大致状况:

    1
    2
    3
    top -H  # -H 线程级别统计
    htop
    vmstat 1 # interval 1s, procs/memory/swap/io/cpu 统计
  2. 确认哪个进程、哪些 CPU:

    1
    2
    mpstat -P ALL 1  # 按 CPU 核心统计
    pidstat -u 1 # -u 按进程 CPU 使用
  3. 追踪进程内的瓶颈,需使用 perf 生成 flame graph;

  4. 想知道某些进程的 CPU 亲和性,或者处于性能测试的原因想将进程绑定到特定 CPU 上,可以使用 taskset -c 0-3 ./app 这样的方法;

  5. 想手动调整特定进程的优先级,可以使用这样的指令来告诉调度器:nice -n -20 ./critical_app(nice 值越小意味着越倾向于让出 CPU 资源,因此优先级越高,范围 -20 到 19,默认 0);

2. 内存分析

内存是否到达“极限”的核心指标:可用内存(available)和 swap 使用率

一般可以通过 free 指令查看全局的粗略信息:

1
2
3
4
$ free -h
total used free shared buff/cache available
Mem: 62Gi 15Gi 1.2Gi 1.2Gi 45Gi 45Gi
Swap: 16Gi 166Mi 16Gi

注:shared 表示 tmpfs 等共享内存使用量

[!IMPORTANT]

注意可用内存:avail = free + buff/cache - reclaimable

因此在判断内存瓶颈时,不能只看 free 命令的 free 列,因为 Linux 会充分利用空闲内存作为缓存。

除了 freevmstat 也能查看内存相关稍详细一点的全局信息:

1
2
3
4
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 166400 123456 78901 234567 0 0 12 34 56 78 10 5 85 0 0
  • swpd:已使用的交换分区大小(KB)
  • si(swap in):每秒从交换分区读入内存的数据量(KB/s)
  • so(swap out):每秒从内存写入交换分区的数据量(KB/s)
  • in(interrupt):每秒发生的中断总数(page fault 中断包含其中)
  • 关键阈值si + so > 10MB/s 持续 5 秒以上,表示存在严重的 thrashing,working set 大于现有内存资源。

如果想了解更详细关于 page fault 的情况来掌握内存使用情况,不止需要了解 swap,还需要了解 page fault 频率等等。

通过 sar 指令查看 page 的交换和缺页异常的情况:

1
2
3
4
5
6
7
8
$ sar -W 1
Linux 5.4.0-91-generic (server) 01/17/2026 _x86_64_ (8 CPU)

02:30:01 PM pswpin/s pswpout/s
02:30:02 PM 0.00 0.00

02:30:01 PM pgpgin/s pgpgout/s fault/s majflt/s
02:30:02 PM 123.00 456.00 789.00 0.00
  • pswpin/s, pswpout/s:每秒交换入/出的页数
  • fault/s:每秒 Page Fault 总数
  • majflt/s:每秒 Major Page Fault 数量


排除内存瓶颈的手段:

  1. 使用 top / free / vmstat / sar 类型的指令查看全局的内存主存、swap 使用情况,包括占用/增速;

  2. 定位哪个进程正在造成这个问题:

    1
    2
    3
    4
    # 按 RSS(Resident Set Size) 排序
    ps -eo pid,user,rss,cmd --sort=-rss | head -10
    # -r 显示内存统计信息
    pidstat -r 1
  3. 针对这个进程了解内存使用情况:

    1
    2
    3
    4
    # 监控特定进程的内存增长
    watch -n 1 "ps -p <pid> -o rss,vsz,cmd"
    # 查看进程的详细内存映射
    cat /proc/<pid>/smaps | less
  4. 更细粒度(进程甚至线程内部)的判断则需应用相关的调优手段。例如对 Java 应用可以通过 JVM 提供的 perf 工具(如 jstat/jstack/jmap 等)。

[!NOTE]

值得关注的是,在粒度细化到针对特定进程时,我们还有一些指标来衡量内存使用情况(下面表格为 LLM 生成内容):

内存指标 定义 是否包含共享内存 是否包含 Swap 使用场景
RSS (Resident Set Size) 驻留在物理内存中的进程内存大小 ✅ 完整计入 ❌ 仅物理内存 快速查看进程内存占用
VSZ (Virtual Memory Size) 进程虚拟地址空间总大小 ✅ 完整计入 ✅ 包含 了解进程地址空间需求
PSS (Proportional Set Size) RSS + 按比例分摊的共享内存 ✅ 按进程数均摊 ❌ 仅物理内存 精确计算进程真实内存开销
USS (Unique Set Size) 进程独占的物理内存大小 ❌ 仅独占部分 ❌ 仅物理内存 分析内存泄漏、优化独占内存

3. 磁盘 I/O

硬盘是否到达“极限”的指标:IOPS吞吐量

  • IOPS 极限:每秒能处理的请求数(一般小文件随机读写瓶颈);
  • Throughput 极限:每秒能传输的数据量(一般大文件顺序读写瓶颈);

[!IMPORTANT]

在判断一个硬盘到达极限时应该遵循指标分析,方便后续优化工作。

另外 IOPS 还受制于 CPU 性能、Throughput 还受制于内存性能(看具体场景)。

这套指标不仅适用于存储 I/O,还适用于网络 I/O。

使用 iostat -x [interval] 观察硬盘的指标(-x 打印 extended 信息,-z 不打印为 0 的数据):

1
2
Device      r/s     w/s   rkB/s    wkB/s  %util
nvme0n1 48000 12000 192000 48000 85.00

r/s(读 IOPS)、w/s(写 IOPS)之和是 IOPS 数据;

rkB/s(读吞吐量)、wkB/s(写吞吐量)之和是 Throughput 数据;


  • iostat%util 指标无法直接判断硬盘 I/O 极限。因为 %util 的定义是 “磁盘忙碌时间占比”。

    • 例:该磁盘 1 秒内 0.98 秒在处理请求,则当前 %util 值为 98%;

    • 对机械硬盘(HDD):每次只能处理一个请求(磁头只有一个),%util 为 100% 意味着满负荷(即到达 I/O 极限);

    • 对现代 SSD / NVMe:多 channels 设计,可并行处理上百请求,%util 为 100% 代表每时每刻都不是完全空闲的;

      一个 NVMe 盘能同时处理 128 个请求,现在有 10 个请求一直在队列中处理,%util 为 100%,但是 90% I/O 能力 not fully utilized;

  • iostatawait 指标能间接判断硬盘的 I/O 极限。因为 await 的定义是请求从发出到完成的平均时间(即平均周转时间 ATT),单位毫秒;

    await 异常升高(相对于同型号正常值)证明当前 workload 接近 I/O 极限。注意不同磁盘的 baseline(正常值基准)不一样。

    可以使用 fio 工具测试磁盘的 IOPS / 吞吐量极限:

    1
    2
    3
    4
    5
    6
    7
    # 测随机读 IOPS
    fio --name=randread --ioengine=libaio --direct=1 --bs=4k \
    --iodepth=64 --rw=randread --size=1G --runtime=30

    # 测顺序写吞吐量
    fio --name=seqwrite --ioengine=libaio --direct=1 --bs=1m \
    --iodepth=32 --rw=write --size=1G --runtime=30

排除硬盘压力/瓶颈的手段:

  1. 确认是否是 I/O 问题:top 选项的 wa%iowait percentage)指标:15%-20% 记为高占用。

  2. 定位硬盘的具体问题,哪块设备、什么瓶颈:

    1
    iostat -x 1
  3. 哪个进程正在造成这个问题:

    1
    2
    3
    4
    # 通过检查 kernel 提供的 /proc 中的 I/O 统计信息
    sudo iotop -oP # -o 仅正在执行 I/O 操作的进程;-P 仅进程(不包括默认显示的线程)
    # sysstat 工具包一部分
    pidstat -d 1 # -d I/O 统计信息,interval 1s 采样间隔
  4. 这个进程正在读写什么文件:

    1
    2
    lsof -p <pid>
    strace -p <pid> -e trace=read,write,open -f

Appendix I. 场景实战

Scene A. 不可中断睡眠

关于是否可中断睡眠的辨析:

  • 块设备操作,如磁盘读写操作涉及复杂的硬件交互,必须保证操作的完整性,所以进程操作时一定位于不可中断睡眠状态;
    • 错误。D/S 区分与“是不是磁盘 I/O” 无关。有些 I/O 操作需要 D 状态是因为 Kernel 打断后没法恢复状态/ roll back,与硬件性质也无关。
  • 观察到与 NFS 相关的大量进程长期进入 D 状态 是没关系的,因为 NFS 锁需要不可中断睡眠来保证操作完整性。
    • 半对,应该检查具体原因。NFS 历史上会导致很多 D 状态是设计缺陷,现在的 kernel 可以尽量避免出现 D 状态;
  • 使用不可中断睡眠的其中一个原因就是,kernel 在执行 nested signal handlers 时会出问题。
    • 错误。不可中断睡眠与内核处理嵌套信号句柄无关。本质上就是 Signals 默认会强制退出进程睡眠状态,如果 kernel 不能在此时 abort 操作(回到用户态为时尚早),那么使用 S 就是不安全的,这个进程需要 D 状态。

Scene B. CPU 瓶颈

下面的情况:

1
2
3
4
5
6
7
8
9
10
$ uptime
14:30:01 up 10 days, 3:22, 2 users, load average: 12.45, 8.76, 4.32

$ mpstat -P ALL 1
Linux 5.4.0-91-generic (server) 01/17/2026 _x86_64_ (8 CPU)
02:30:02 PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
02:30:03 PM all 85.21 0.00 9.87 0.00 0.00 4.92 0.00 0.00 0.00 0.00
02:30:03 PM 0 99.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
02:30:03 PM 1 98.00 0.00 2.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
...

注意到:

  • 这是一个 8 核机器,一分钟内 load average 说明存在 CPU 资源压力;
  • %usr = 85.21% 表明是用户进程导致的 CPU 压力;
  • 接下来需要使用 pidstat -u 1 或其他指令查看具体进程使用情况;

Scene C. I/O 等待会影响 Load Average

下面的场景:

1
2
3
4
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 5 0 45678 12345 678901 0 0 2345 6789 100 200 5 10 0 85 0

看起来 id=0% 是 CPU 资源受限吗?并非,wa=85% 说明 CPU 有 85% 时间在等待 I/O 完成,很可能是 I/O 瓶颈。

注意 bi=2345bo=6789:块设备每秒读写次数高,需要结合 iostat 进一步分析 I/O 瓶颈问题。

Scene D. 内存是否存在压力

1
2
3
4
5
6
7
8
9
$ free -h
total used free shared buff/cache available
Mem: 62Gi 58Gi 1.2Gi 1.5Gi 3.2Gi 2.1Gi
Swap: 16Gi 12Gi 4.0Gi

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
4 2 12582912 123456 78901 234567 8192 12288 123 456 789 1011 25 15 40 20 0

available 仅 2.1Gi(总内存 62Gi 的 3.4%),远低于 10% 的健康阈值,存在内存问题。

wa=20% 说明 CPU 有 20% 时间在等待 I/O 完成,但另外的数据证明了并非用户态程序的 I/O:si=8192KB/s, so=12288KB/s,持续的高 swap I/O 活动证明可能存在大规模的 Thrashing 现象,说明系统存在严重物理内存短缺问题,需要立即定位进程并查找原因:

1
2
3
4
5
6
7
$ pidstat -r 1
Linux 5.4.0-91-generic (server) 01/17/2026 _x86_64_ (8 CPU)

02:35:01 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
02:35:02 PM 1000 1234 150.00 5.00 4500000 3200000 5.00 java
02:35:02 PM 1000 5678 200.00 15.00 2000000 1800000 2.80 python
02:35:02 PM 0 999 50.00 20.00 1500000 1200000 1.90 mysqld

发现 PID 为 1234 的 Java 应用存在持续的 major page fault,应使用 JVM 相关工具对业务逻辑进行进一步排查。

Scene E. 磁盘 I/O 检查

1
2
3
4
$ iostat -x 1
Device r/s w/s r_await w_await aqu-sz %util
nvme0n1 15000 8000 0.08 0.12 1.84 99.80
sda 200 150 45.32 62.18 16.43 98.50

如上所示,可以发现:

  • nvme0n1 设备并不存在接近极限的问题:await 约 0.1 ms,队列长度 1.84;
  • sda 设备达到极限:await 高达 45-62 ms,队列积压 16 个请求;

Scene F. 磁盘 I/O 瓶颈估算

某 NVMe 硬盘标称 50 万 IOPS、3 GB/s 吞吐。估计:

  • “数据库大量随机读写,4KB 小块” 时的场景:先达到 IOPS 极限(50w × 4KB = 2GB/s),此时吞吐量未达上限;

  • “视频转码,顺序读写超大视频文件” 时的场景:大概率先达到吞吐量上限(3 GB/s 时 IOPS 仅数千);

Appendix II. Review: About Process States

经典操作系统理论中的进程状态(Created/Ready/Running/Blocked/Terminated)是一个抽象模型,描述了进程生命周期的基本阶段。而 Linux 进程状态(如下)是具体工程实现,提供了更细致和实际的状态描述。

1
2
3
4
5
6
7
S Interruptible sleep (waiting for an event to complete)
D Uninterruptible sleep (usually IO)
R Running or runnable (on run queue)
T Stopped, either by a job control signal or because it is being traced.
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z Defunct ("zombie") process, terminated but not reaped by its parent.

二者映射关系如下:

  • Ready + Running -> R

    • 理论中的 Ready(就绪)和 Running(运行)状态在 Linux 中合并为 R 状态,这是因为在 Linux 调度器实现中,这两个状态在内核数据结构层面难以严格区分;
    • R 表示 “Running or runnable (on run queue)”,即要么正在 CPU 上执行,要么在 run queue 中等待 CPU;
  • Blocked -> S + D

    • 理论中的 Blocked(阻塞)状态在 Linux 中细化为两种:

      • S (Interruptible sleep): 可中断睡眠,等待事件完成,可以被信号唤醒;
      • D (Uninterruptible sleep): 不可中断睡眠,通常是等待 I/O 操作完成,不能被 OS 的信号中断;
    • 这种细分反映了实际系统中阻塞的不同性质和处理需求。

      区分的根本意义辨析:D state is not primarily about protecting “critical kernel processes.”

      It is about protecting specific *wait contexts* where interruption would cause:

      • resource leaks;
      • inconsistent kernel state;
      • or impossible rollback;

      A task enters D when it sleeps while holding resources that cannot be safely released or unwound if a signal arrives.

      即:确保某些关键上下文不能被打断 (D),同时顺便保证系统性能 (S),与 I/O 或锁无关,只与 Kernel 有没有能力在 signal 打断后 context switch 原样恢复回去有关。举例场景:

      1. I/O Subsystem 的特殊性:

        • 虽然:Disk I/O is not inherently uninterruptible.

        • 但是:Many I/O paths used to require D because they could not be safely rolled back, NOT because of “hardware complexity”.

      2. Kernel Signal 的正确性(需要 D):防止在 kernel 关键路径上处理信号导致内核数据结构不一致、避免在持有重要锁时被中断导致死锁(持锁顺序问题)、确保资源清理工作能够完整执行等。这里需要 avoids partial execution states.

      3. 存在很多可以安全中断的等待(需要 S),如等待用户输入、等待定时器、等待非关键资源;

      4. 并不是所有持并发资源锁等待 / 等 IO 资源的进程都需要是 D 状态。甚至这么说:绝大多数持锁/或者等 IO 的进程(如 mutex_lock, rwsemaphore, futex waits, pipe reads, socker rw, poll/select/epoll, 绝大多数文件系统操作等)都不需要进入 D 状态。进入 D 状态的评判标准:

        • The task must sleep (cannot spin).

        • If a signal arrived at that sleep point, the kernel could not safely abort the operation and unwind all held state.

  • Terminated -> Z + X

    • Z (Zombie): 进程已终止但父进程尚未调用 wait() 回收其资源;
    • X (Dead): 完全终止状态,通常不会在 ps 这样的命令中看到;
    • 理论模型中的 Terminated 在 Linux 实现中被细化为资源回收的不同阶段;
  • Linux 特有状态

    • T (Stopped): “Stopped, either by a job control signal or because it is being traced”,这个状态在经典理论模型中没有直接对应,可以看作是 Blocked 的一种特殊情况,被 shell / 调试工具等控制的进程;
    • W (Paging): 在 Linux 2.6.xx 内核后已不再有效;