﻿# strace —— 追踪进程的系统调用（定位 stime 高的元凶）

## 这个工具是做什么的

当 `pidstat`/`top` 显示 `%system`（内核态）高时，说明进程在频繁陷入内核。`strace` 基于 `ptrace` 拦截进程的**每一次系统调用**，告诉你它在调什么、调了多少次、每次多慢、返回什么错误。是排查 cpu_demo 场景2（频繁 open/write/close）这类问题的直接手段。

## 可以回答什么问题

| 问题 | 怎么用 |
|------|--------|
| 内核态 CPU 高，主要在调什么系统调用？ | `strace -c -p <PID>` 汇总表看 `% time` 和 `calls` |
| 每次系统调用多慢？哪个最慢？ | `strace -c` 的 `usecs/call` 列；`strace -T` 看每次耗时 |
| 系统调用是否返回了错误？ | `strace -c` 的 `errors` 列；`strace` 逐行看 `= -1 EXXX` |
| 程序卡住了，堵在哪个系统调用上？ | `strace -p <PID>` 看最后一行——`futex`=等锁，`read`=等数据 |
| 进程在频繁打开/关闭文件吗？ | `strace -e trace=openat,close -c -p <PID>` |
| 能不能只看文件/网络相关的调用？ | `strace -e trace=file` / `strace -e trace=network` |

## 数据来源

- **来源接口**：`ptrace(2)` 系统调用。内核在目标进程**每次进入和退出系统调用时**把它暂停下来，将调用号、参数、返回值、错误码交给 strace，再放行。
- **采集方式**：主动逐次拦截，不是读 `/proc` 的聚合统计——所以能拿到**每一次调用的完整明细**（参数字符串、耗时、errno）。
- **由此决定的特性**：每个 syscall 都要多两次上下文切换（陷入 strace 再返回），**开销很大，会显著拖慢目标进程**（几倍甚至数十倍）。因此只适合短时诊断，生产热路径慎用；高频场景改用低开销的 `perf trace` 或 eBPF（`bpftrace`）。

```bash
yum install strace / apt install strace
```

> ⚠️ **开销很大**：每个 syscall 都要陷入 ptrace，目标进程会被拖慢几倍甚至几十倍。只用于短时诊断，**别在生产热路径长时间挂着**；高频场景优先用低开销的 `perf trace` 或 eBPF（`bpftrace`）。

## 一、附加/启动

```bash
strace ./cpu_demo               # 启动并从头跟踪一个新进程
strace -p <PID>                 # 附加到运行中的进程，Ctrl-C 脱离（不会杀死目标）
strace -f -p <PID>              # -f 跟踪 fork 出的子进程 / 新建的线程（多线程/多进程必加）
strace -ff -o trace -p <PID>    # 每个线程/进程写单独文件 trace.<PID>
strace -o trace.log ./cpu_demo  # 输出重定向到文件（不混在 stderr）
```

## 二、最有用：`-c` 统计汇总

```bash
strace -c -p <PID>              # 附加后跟一段时间，Ctrl-C 打印汇总表
strace -c ./cpu_demo
strace -fc -p <PID>             # 含线程的汇总
```

输出样例（cpu_demo 场景2 大致长这样）：

```bash
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 52.31    0.412300           4     98120           write
 31.07    0.244900           2     98120           openat
 14.88    0.117300           1     98120           close
  1.74    0.013700           1     12030           ...
------ ----------- ----------- --------- --------- ----------------
100.00    0.788200                298290        0   total
```

| 列 | 含义 | 判读 |
|----|------|------|
| `% time` | 该 syscall 占所有 syscall 耗时的比例 | 从高往下看，第一名就是内核态开销大头 |
| `seconds` | 累计耗时 | |
| `usecs/call` | 平均每次微秒 | 单次很慢的 syscall（如某些锁/IO）在这列突出 |
| `calls` | 调用次数 | **次数高得离谱 = 该合批/复用却没做**（场景2 每轮都 open+write+close）|
| `errors` | 出错次数 | 大量 error 也白白消耗 CPU（如反复失败的 stat）|

> cpu_demo 场景2 的结论一目了然：`openat`/`write`/`close` 各调了近 10 万次——根因是"每次循环都重新打开+关闭文件"，改成打开一次、复用 fd、批量/带缓冲写，`%system` 立降。

## 三、只看关心的调用（`-e`）

```bash
strace -e trace=openat,write,close,read -p <PID>   # 指定若干 syscall
strace -e trace=file -p <PID>       # 文件相关一整类（open/stat/unlink...）
strace -e trace=network -p <PID>    # 网络类（socket/connect/send/recv...）
strace -e trace=desc -p <PID>       # 文件描述符相关
strace -e trace=%process -p <PID>   # 进程相关（fork/exec/wait/exit）
strace -e signal=all -p <PID>       # 跟踪信号
```

## 四、时间与详情

```bash
strace -T -p <PID>       # 每个 syscall 末尾标 <本次耗时秒数>——找慢调用
strace -tt -p <PID>      # 每行前加微秒级绝对时间戳
strace -r -p <PID>       # 显示相邻 syscall 的相对时间间隔（找卡顿点）
strace -s 200 -p <PID>   # 把 read/write 等的字符串参数打印到 200 字节（默认 32，太短看不全）
strace -y -p <PID>       # 把 fd 数字旁边标出对应的文件/socket 路径（非常实用）
strace -yy -p <PID>      # 更进一步，socket 显示 IP:端口
```

## 五、典型排查场景

- **stime/内核态高**：`strace -c` 看谁次数/耗时最多，通常是没批处理/没缓冲的小 IO、或反复失败的调用。
- **程序"卡住"不动**：`strace -p <PID>` 看它阻塞在哪个 syscall——
  - `futex(...)` 卡住 = 在等锁（互斥量/条件变量），多半是锁竞争或死锁。
  - `read`/`recvfrom` 卡住 = 在等数据（对端没发/磁盘慢）。
  - `poll`/`epoll_wait`/`select` = 正常等事件（事件驱动程序空闲时如此）。
  - `nanosleep` = 在 sleep（cpu_demo 场景3 就是）。
- **报错找不到原因**：看 syscall 的返回值和 `errno`：
  - `ENOENT` 文件/路径不存在、`EACCES`/`EPERM` 权限、`EMFILE`/`ENFILE` fd 耗尽（fd 泄漏）、`EADDRINUSE` 端口占用、`ECONNREFUSED` 连接被拒、`EAGAIN` 资源暂不可用。
- **配合 `-y`**：报错时能直接看到是哪个文件/哪个连接出的问题，省去手动查 fd。

## 八、交叉引用

- **内核态 CPU 入口**：[pidstat](/tools/cpu/pidstat.md) `-u`（先确认 %system 高）
- **函数级热点**：[perf](/tools/code/perf.md)（perf top/record 更精确但不如 strace 直观看到 syscall）
- **低开销替代**：`perf trace`（生产环境 stime 高时的首选）
- **现代追踪**：`bpftrace` / eBPF（可编程、低开销、可聚合）
- **库函数追踪**：`ltrace`（追踪 libc 调用而非 syscall）
- **锁分析**：perf lock（比 strace futex 更结构化）

## 九、相关工具

| 指标 | 缩写/英文 | 含义 | 判读 |
|------|----------|------|------|
| 时间占比 | `% time` | 该 syscall 占所有 syscall 总耗时比例 | 从高往下看，第一行就是内核态开销大头 |
| 累计耗时 | `seconds` | 总耗时（秒） | 结合调用次数看 |
| 单次平均耗时 | `usecs/call` | 平均每次微秒 | 单次异常慢的 syscall 在此突出 |
| 调用次数 | `calls` | 总调用次数 | **次数高得离谱 → 该合批/复用却没做** |
| 出错次数 | `errors` | 出错次数 | 大量 error 白白消耗 CPU |

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

### 7.1 stime 高 → 找耗时最多的 syscall

```bash
pidstat -u -p <PID> 1 → %system 高
  → strace -c -p <PID> → 等 5-10 秒 Ctrl-C
    → 看 % time 最高的 syscall
      → write/openat/close 次数多 → 小 IO 未合批
      → futex 耗时长 → 锁竞争，用 perf lock
      → mmap/munmap 频繁 → 内存分配模式有问题
```

### 7.2 程序卡住 → 看阻塞在哪个 syscall

```bash
strace -p <PID>          # 看最后一行停在哪
  → futex(..., FUTEX_WAIT) → 在等锁（死锁或锁竞争）
  → read(3, ...) → 在等数据（对端没发/磁盘慢）
  → poll/epoll_wait → 正常等事件
  → nanosleep → 在 sleep
```

### 7.3 报错找不到原因 → 看 errno

```bash
strace -e trace=openat,stat -p <PID> 2>&1 | grep "= -1"
  → ENOENT → 文件不存在
  → EACCES/EPERM → 权限问题
  → EMFILE → fd 耗尽（fd 泄漏）
  → ECONNREFUSED → 连接被拒
  → 配合 -y 选项直接显示文件路径
```

### 7.4 高频 syscall 优化评估

```bash
# 场景：cpu_demo 场景2 每轮 open+write+close
strace -c ./cpu_demo
  → openat 10万次、write 10万次、close 10万次
  → 改成打开一次、复用 fd、批量写
    → 优化后再次 strace -c 对比 → 调用次数应大幅下降
```

## 八、交叉引用

- `ltrace`：追踪**库函数**调用（如 `malloc`、`strcpy`），而非系统调用。
- `perf trace`：perf 自带的 strace 替代，开销低得多，适合生产。
- `bpftrace`/eBPF：可编程、低开销、可聚合，是现代高频追踪的首选。

