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/ENFILEfd 耗尽(fd 泄漏)、EADDRINUSE端口占用、ECONNREFUSED连接被拒、EAGAIN资源暂不可用。
- 配合
-y:报错时能直接看到是哪个文件/哪个连接出的问题,省去手动查 fd。
八、交叉引用
- 内核态 CPU 入口:pidstat
-u(先确认 %system 高) - 函数级热点:perf(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 → 在 sleep7.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:可编程、低开销、可聚合,是现代高频追踪的首选。
总结
strace 追踪进程与内核之间的系统调用边界:-p <PID> attach 运行中的进程、-e trace= 按类别过滤、-c 统计每个 syscall 的次数与耗时、-f 跟子线程;程序卡住看最后停在哪个调用(futex=等锁、read=等数据、poll/epoll_wait=正常等事件),报错看 = -1 后的 errno(ENOENT 文件不存在、EACCES 权限、EMFILE fd 耗尽、ECONNREFUSED 连接被拒),-c 发现海量 open/write/close 就改成复用 fd、批量写再复跑对比优化。它只看 syscall 边界,库函数级用 ltrace、低开销高频追踪用 perf trace/eBPF。