﻿# top —— 实时进程/系统资源总览

## 这个工具是做什么的

最常用的入门工具，一屏看全整机和各进程的 CPU、内存占用，是排查的"第一现场"。适合快速定位"哪个进程/线程在耗资源、耗的是哪一类 CPU"，但要落到函数级还得靠 `perf`。

## 可以回答什么问题

| 问题 | 怎么用 |
|------|--------|
| 现在系统整体忙不忙？负载趋势如何？ | 看顶部 `load average` 和 `%Cpu(s)` 行 |
| CPU 主要耗在用户态还是内核态？有没有在等 IO？ | `%Cpu(s)` 行：`us`/`sy`/`wa` 三个核心指标 |
| 哪个进程在吃 CPU / 内存？ | 进程列表区默认按 `%CPU` 排序，按 `M` 切到内存排序 |
| 多线程应用中哪个线程在烧 CPU？ | `top -H -p <PID>` 展开线程视图 |
| 负载压在单个核心还是均匀分布？ | 按 `1` 把 `%Cpu(s)` 展开成每个核心一行 |
| 有没有僵尸进程？ | 第 2 行 `zombie` 字段 |

## 数据来源

- **来源文件**：整机 CPU 取自 `/proc/stat`（各类 jiffies）；每个进程/线程取自 `/proc/<pid>/stat`、`/proc/<pid>/status`（utime/stime、内存、状态）；内存汇总取自 `/proc/meminfo`；负载取自 `/proc/loadavg`。
- **采集方式**：每个刷新周期把这些文件读一遍，与上一次的累计值**做差**，再除以间隔时间算出 `%CPU`、`%MEM` 等瞬时百分比。
- **由此决定的特性**：因为要"两次采样做差"，**刚启动的第一屏 `%CPU` 是拿开机以来的累计值算的、通常不准**，看第二次刷新起的数值才对；这也是所有"读 /proc 做差"类工具（pidstat/vmstat/iostat/mpstat）的共性。

## 一、启动参数

```bash
top                      # 实时刷新（默认 3 秒一屏）
top -d 1                 # -d 指定刷新间隔（秒），可用小数 top -d 0.5
top -p <PID>             # 只监控指定进程，逗号分隔多个：top -p 1234,5678
top -H                   # 线程模式：把线程当独立行显示
top -H -p <PID>          # 只看某进程的各线程（多线程排查必用）
top -u <user>            # 只看某用户的进程
top -c                   # 显示完整命令行（而不仅是进程名），便于区分同名进程
top -b -n 1              # 批处理模式跑 1 次（-n 指定次数），配合重定向存快照/给脚本解析
top -b -d 5 -n 12 > top.log   # 每 5 秒一次共 12 次，采集 1 分钟写日志
top -o %MEM              # 启动即按指定列排序（新版支持）
```

## 二、读懂输出的两大部分

### 1) 顶部汇总区（5 行）

```bash
top - 15:04:21 up 12 days,  3:11,  2 users,  load average: 1.25, 0.98, 0.75
Tasks: 218 total,   2 running, 216 sleeping,   0 stopped,   0 zombie
%Cpu(s):  6.3 us,  2.1 sy,  0.0 ni, 90.8 id,  0.5 wa,  0.0 hi,  0.3 si,  0.0 st
MiB Mem :  15889.2 total,   1024.5 free,   4820.1 used,  10044.6 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.  10600.3 avail Mem
```

**第 1 行**——`load average: 1.25, 0.98, 0.75` 是过去 1/5/15 分钟的平均运行队列长度。经验判读：把它和 CPU 核数比，`load ≈ 核数` 表示满载，`load > 核数` 表示有任务在排队等 CPU（或等 D 状态 IO）。三个数看趋势：递增=负载在涨，递减=高峰已过。

**第 2 行**——`running` 是正在运行/可运行数；`zombie` 非 0 说明有子进程退出后父进程没回收（`wait`），需查父进程。

**第 3 行 `%Cpu(s)` 是排查 CPU 类型的核心**：

| 字段 | 全称 | 含义与判读 |
|------|------|-----------|
| `us` | user | 用户态：业务代码、纯计算。高 → 看 `perf` 用户态函数 |
| `sy` | system | 内核态：系统调用、内核逻辑。高 → 频繁 syscall/锁/缺页，用 `strace`/`perf` |
| `ni` | nice | 被 `nice` 调过优先级的用户态时间 |
| `id` | idle | 空闲 |
| `wa` | iowait | CPU 空闲且在等磁盘/网络 IO。高 → IO 瓶颈，转 `iostat`/`iotop` |
| `hi` | hardirq | 硬中断（网卡/磁盘控制器等硬件触发） |
| `si` | softirq | 软中断。高 → 网络小包风暴、网卡收发，转 `mpstat -P ALL` 看是否集中单核 |
| `st` | steal | 虚拟机被宿主偷走的时间。高 → 宿主超卖，同机其他 VM 在抢 CPU |

> 按 `1` 可把这一行**展开成每个逻辑核一行**，判断负载是均匀还是压在单核。按 `t` 切换这行的呈现样式（数字/进度条）。

**第 4/5 行内存**：重点看 `buff/cache`（可回收的缓存，不算真占用）和第 5 行末的 `avail Mem`（真正可用内存）。判断内存紧不紧**只看 `avail Mem`，别被 `free` 小吓到**。

### 2) 进程/线程列表区

```bash
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 8921 derek     20   0  812400  45120   3200 R  99.7   0.3   1:04.55 cpu_demo
```

| 列 | 含义与判读 |
|----|-----------|
| `PID` | 进程号（`-H` 下这里是线程 TID）|
| `PR`/`NI` | 内核优先级 / nice 值（-20~19，越小越优先）|
| `VIRT` | 虚拟内存总量（含未真正分配的映射，通常偏大，别当真实占用）|
| `RES` | **常驻物理内存**（真正占的 RAM，看内存就看它）|
| `SHR` | 共享内存（共享库等）|
| `S` | 进程状态：`R` 运行、`S` 可中断睡眠、`D` **不可中断睡眠**（几乎都是卡在磁盘/网络 IO，`D` 多 + load 高 = IO 拖累）、`T` 暂停、`Z` 僵尸 |
| `%CPU` | 用户+内核 CPU 之和。**默认按整机单核 100% 计**，多核跑满多个线程可 >100%（如 8 核满载 = 800%）|
| `%MEM` | `RES` 占物理内存比例 |
| `TIME+` | 累计占用 CPU 时间（百分之一秒精度），进程存活期总账 |
| `COMMAND` | 进程名（`-c` 显示完整命令行）|

## 三、交互快捷键（运行中直接按）

| 键 | 作用 |
|----|------|
| `P` | 按 `%CPU` 排序（默认）|
| `M` | 按 `%MEM` 排序 |
| `T` | 按累计 `TIME+` 排序 |
| `H` | 切换线程视图（等价 `-H`）|
| `0` | 零值抑制开关：把值为 0 的字段显示出来 / 隐藏为空白（**不是 CPU 相关**，别和下面 1/2/3 混）|
| `1` | `%Cpu` 行：单行汇总 ↔ 每个逻辑核一行，来回切换 |
| `2` | 按 **NUMA 节点**汇总显示 CPU（仅 NUMA 机器有效）|
| `3` | 展开**指定某个 NUMA 节点**：按下后输入节点号，只看该节点的核 |
| `t`/`m` | 切换 CPU 行 / 内存行的显示样式（数字↔进度条）|
| `c` | 切换显示完整命令行 |
| `f` | 字段管理器：空格增删列、`s` 设排序列（可加 `nTH` 线程数、`P`最后运行核、`SWAP`）|
| `x`/`y` | 高亮排序列 / 高亮运行中的行 |
| `k` | 输入 PID 杀进程 |
| `r` | renice 改优先级 |
| `u` | 只看指定用户 |
| `V` | 树状视图（看父子进程关系）|
| `W` | 把当前配置存到 `~/.toprc`，下次自动生效 |
| `q` | 退出 |

### CPU 汇总/NUMA 显示键 `0` `1` `2` `3` 详解

这几个键常被一起问，但 `0` 和 `1/2/3` 其实是两回事：

| 键 | 作用 | 说明 |
|----|------|------|
| `0` | **零值抑制**（Zero Suppress）| 切换"是否把值为 0 的字段显示为空白"。和 CPU 显示无关，纯粹让满屏的 0 变干净，看非零数据更省眼。|
| `1` | **单行汇总 ↔ 逐核** | 默认 `%Cpu(s)` 是所有核的一行平均；按一下变成 `%Cpu0 / %Cpu1 / ...` 每核一行，再按回到汇总。**判断"负载是均匀还是压在单核"最常用**。|
| `2` | **按 NUMA 节点汇总** | 在多路 NUMA 机器上，按节点(node)分组显示各节点的 CPU 汇总，看负载是否跨节点倾斜。非 NUMA 机器按了会提示不可用。|
| `3` | **展开指定 NUMA 节点** | 按下后提示输入节点号，只显示该节点内各核的明细，用于深入某一个节点。|

> 记忆：`1` = 逐核，`2` = 按 NUMA 节点，`3` = 单个 NUMA 节点，三者是"CPU 展开粒度"从细到聚焦；`0` 是独立的零值显示开关。cpu_demo 场景1（单线程死循环）按 `1` 展开，会看到只有一个核满、其余空闲。

> `2`/`3` 涉及的 NUMA 概念（节点、本地/远程内存访问）见 [../numa/numa.md](/concepts/numa/numa.md)。

## 四、结合本仓库实操

```bash
make && ./cpu_demo        # 另开终端运行靶子，按提示选 1/2/3
# 观测终端：
top -H -p $(pgrep cpu_demo)
```

- 选 **场景 1（用户态死循环）**：`%Cpu(s)` 的 `us` 明显上升；`-H` 展开可见负载压在那个 detach 出来的 `work_thread` 线程上，主线程几乎空闲。
- 选 **场景 2（频繁 open/write/close）**：`sy` 上升（系统调用多），可能伴随少量 `wa`。
- 按 `1` 看逐核：场景 1 是单线程，只有一个核被打满，其余空闲——直观说明"加核无用，得优化代码或并行化"。

## 五、关键指标速查

| 指标 | 缩写/英文 | 正常值 | 异常信号 |
|------|----------|--------|---------|
| 系统负载 | LA / Load Average | LA ≈ CPU 核数 | LA 持续 > 核数 → 过载，任务排队 |
| 负载趋势 | 1min/5min/15min 对比 | 三者接近 | 1min 远大于另两个 → 负载在暴涨 |
| 用户态 CPU | `us` / %User | 视业务 | 单核 100% 其余空闲 → 单线程瓶颈 |
| 内核态 CPU | `sy` / %System | < 10% | > 30% → 频繁 syscall/锁/缺页，用 strace 排查 |
| IO 等待 | `wa` / %iowait | < 5% | > 20% → IO 瓶颈，转 iostat |
| 硬中断 | `hi` / %HardIRQ | < 1% | 异常高 → 中断风暴 |
| 软中断 | `si` / %SoftIRQ | 各核均匀 | 集中在单核 → 网卡多队列未开 |
| 被偷 CPU | `st` / %Steal | 0% | > 5% → 云主机超卖 |
| 空闲率 | `id` / %Idle | > 10% | 长期 → 0 → CPU 打满 |
| 常驻内存 | RES / Resident Memory | 视业务 | 持续单调增长 → 内存泄漏 |
| 进程状态 | S / State | R 或 S 为主 | D 状态多 → IO 阻塞；Z → 僵尸进程 |
| 运行任务数 | running | 视业务 | 持续 > 核数 → CPU 过载 |
| 僵尸进程 | zombie | 0 | > 0 → 父进程未 wait() 回收子进程 |

## 六、常见排查方法与分析

### 6.1 CPU 利用率高 → 定位到线程/函数

```bash
top → 按 P 排序找高 CPU 进程
  → top -H -p <PID> 展开线程
    → 找到高 CPU 的 TID
      → perf top -t <TID> 看热点函数
      → 若 sy 高 → strace -c -p <PID> 看系统调用
```

### 6.2 负载高但 CPU us/sy 都不高

可能原因：大量 D 状态进程卡 IO。确认方法：

```bash
top → 看 %Cpu 行 wa 是否高
  → 看进程列表 S 列是否有 D 状态
    → iostat -x 1 确认磁盘瓶颈
    → iotop -o 找 IO 大户
```

### 6.3 内存泄漏嫌疑

```bash
top → 按 M 排序
  → 持续观察 RES 列是否只增不减
    → pidstat -r -p <PID> 1 追踪 RSS 趋势
    → cat /proc/<PID>/status | grep VmRSS 看增长速率
```

### 6.4 单核打满 vs 多核均匀

```bash
top → 按 1 展开逐核 %Cpu
  → 只有一个核满 → 单线程瓶颈（加核无用，需并行化）
  → 多个核满 → 多线程利用率好，若仍不够需加核或优化算法
```

## 七、交叉引用

- **逐核分析**：[mpstat](/tools/cpu/mpstat.md)（更精确的逐核 + 中断分布）
- **进程级 CPU 拆分**：[pidstat](/tools/cpu/pidstat.md)（按 utime/stime 拆分，可下钻到线程）
- **调度观测**：[scheduling-observation.md](/tools/cpu/scheduling-observation.md)（从负载到上下文切换的完整分析）
- **函数级热点**：[perf](/tools/code/perf.md)（perf top/record 定位到函数和代码行）
- **系统调用追踪**：[strace](/tools/code/strace.md)（sy 高时定位具体 syscall）
- **内存详解**：[free](/tools/memory/free.md)（available 才是真正可用内存）
- **磁盘 IO**：[iostat](/tools/disk/iostat.md)（wa 高时进一步定位）
- **/proc 数据源**：[procfs](/tools/proc/procfs.md)（top 的数据怎么来的）

## 八、常见坑

- **`%CPU` 加起来超过 100%**：正常，多核按核累加。想看归一化到整机的百分比，`f` 里换用 Irix/Solaris 模式（按 `I` 切换：Irix 关掉后 `%CPU` 会除以核数）。
- **只看进程会漏掉线程问题**：多线程程序不加 `-H`，只能看到进程聚合值，定位不到具体线程。
- **top 自身也耗 CPU**：刷新间隔太小（如 `-d 0.1`）在进程数多时 top 自己就很吃 CPU，观测会失真。
- 想要更友好的界面用 `htop`（彩色、鼠标、可横向滚动、树视图），字段含义与 top 一致。

