生产环境中,进程突然卡死、CPU 飙高或毫无响应,是最让人头疼的问题之一。日志里没有异常,监控指标也看不出端倪,重启虽然能暂时恢复,但根因不明意味着下次还会再犯。这时候,strace 和 ltrace 这两个 Linux 下的动态追踪工具,往往能成为破局的关键。本文将从实战角度出发,带你一步步用它们定位进程卡死的真实原因。
为什么 strace 和 ltrace 能定位卡死问题
一个进程卡死,本质上要么是系统调用阻塞(比如读一个永远不来的网络包、等一把永远不释放的锁),要么是库函数调用陷入死循环或阻塞(比如 malloc 卡住、pthread_mutex_lock 等待)。
strace跟踪的是系统调用(syscall),也就是进程和内核之间的交互:文件读写、网络收发、内存映射、信号处理等。ltrace跟踪的是用户态库函数调用,比如 libc 里的malloc、free、pthread_*、str*等。
两者结合,就能从“内核边界”和“用户态边界”两个层面还原进程到底卡在哪一行。
准备工作:先别急着 attach
在 attach 之前,先做两件事,能省下大量时间:
- 确认进程状态:用
ps -o pid,stat,wchan,cmd -p <PID>查看。STAT为D表示不可中断睡眠(通常在等 I/O),S表示可中断睡眠,R表示运行中。WCHAN会告诉你内核里正在等待的函数名,比如futex_wait、pipe_read。 - 确认权限:attach 需要
ptrace权限。生产环境常见限制是kernel.yama.ptrace_scope=1,非 root 只能 attach 自己的子进程。必要时用 root 或调整该参数。
用 strace 定位系统调用层面的卡点
基础用法
最直接的命令是:
strace -p <PID> -T -tt -f
参数含义:
-p <PID>:attach 到目标进程。-T:显示每个系统调用的耗时。-tt:显示精确时间戳。-f:跟踪子线程/子进程。多线程程序一定要加,否则只能看到主线程。
如果进程已经完全卡死、没有任何输出,可以加 -e trace=all 并观察最后停留的那个系统调用。卡死时最后一条未返回的调用,往往就是元凶。
实战案例:卡在 futex 等待
假设一个 Java 或 C++ 服务进程 CPU 为 0,但接口无响应。attach 后输出类似:
[pid 12345] 10:22:31.123456 futex(0x7f8a1c002a00, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
这说明线程在等一把用户态锁(futex)。此时 strace 只能告诉你“在等锁”,但谁持有锁、为什么没释放,需要 ltrace 或 gdb 进一步看。
实战案例:卡在 read/write
如果看到:
[pid 12346] 10:22:33.234567 read(5, <unfinished ...>
说明线程卡在 fd 5 的读操作上。用 ls -l /proc/<PID>/fd/5 看看这个 fd 指向什么——可能是 socket、管道或文件。如果是 socket,再用 ss -tnp 看对端状态,判断是网络对端没发数据,还是本端读逻辑有问题。
常用技巧
-c:统计各系统调用耗时,快速找出“哪个调用最慢”。-e trace=network:只看网络相关调用。-e trace=file:只看文件相关调用。-s 256:打印字符串参数时最多显示 256 字节,避免被截断。-o file:输出到文件,避免终端刷屏影响性能。
注意:strace 会显著拖慢进程(尤其是高频 syscall 场景),生产环境建议短时间 attach,或先用
-c做统计。
用 ltrace 定位库函数层面的卡点
如果 strace 显示进程在用户态空转(没有 syscall),或者卡在 futex 但看不出原因,就该 ltrace 上场了。
基础用法
ltrace -p <PID> -T -tt -f
参数与 strace 类似。ltrace 默认只跟踪动态库调用,如果程序是静态链接或用了 -O2 内联,可能看不到有用信息。
实战案例:卡在 malloc 或锁
假设 strace 显示线程在 futex 等待,ltrace 输出:
[pid 12345] 10:22:31.123456 pthread_mutex_lock(0x7f8a1c002a00 <unfinished ...>
这就明确了:线程在等 pthread_mutex_lock。接下来可以:
- 用
gdb -p <PID>attach,执行thread apply all bt看所有线程栈,找到持有该 mutex 的线程。 - 检查代码中是否有锁顺序不一致导致的死锁,或某个线程持锁后阻塞在 I/O 上。
实战案例:卡在 malloc
如果 ltrace 显示:
[pid 12347] 10:22:35.345678 malloc(1048576 <unfinished ...>
而 strace 同时显示该线程在 mmap 或 brk 上等待,说明内存分配触发了向内核要内存,可能因为内存碎片或 cgroup 限制导致卡顿。这时要检查 free -m、/sys/fs/cgroup/memory/.../memory.limit_in_bytes 以及是否有大量小对象分配。
ltrace 的局限
- 对 C++ 的
std::mutex、std::condition_variable等,ltrace 可能只能看到底层pthread_*调用,需要结合符号表理解。 - 对 Go、Rust 等自带运行时的语言,ltrace 效果有限,更适合用
perf、gdb或语言自带的 pprof。 - ltrace 性能开销比 strace 更大,生产环境慎用,建议只做短时采样。
组合拳:一套完整的排查流程
遇到进程卡死,可以按以下顺序推进:
- 看状态:
ps -o pid,stat,wchan,cmd -p <PID>,判断是 D 还是 S,WCHAN 是什么。 - strace 采样:
strace -p <PID> -T -tt -f -o /tmp/strace.log,跑 5~10 秒后 Ctrl+C,看最后停留的 syscall。 - 定位 fd/资源:根据 syscall 的参数,用
/proc/<PID>/fd、ss、lsof确认资源状态。 - ltrace 补充:若卡在 futex 或用户态,用
ltrace -p <PID> -T -tt -f看库函数。 - gdb 收尾:
gdb -p <PID>后thread apply all bt,拿到完整调用栈,结合代码定位根因。 - 验证:修复后,用相同场景复现,确认不再卡死。
生产环境注意事项
- 性能影响:strace/ltrace 会让目标进程变慢数倍甚至数十倍,高并发服务上可能引发雪崩。尽量在低峰期操作,或先用
-c做轻量统计。 - 权限与安全:attach 需要 ptrace 权限,容器环境可能被 seccomp 或 apparmor 限制。必要时在容器内安装工具或使用
--cap-add=SYS_PTRACE。 - 日志留存:把输出重定向到文件,方便后续分析,也避免终端卡顿。
- 别只依赖工具:strace/ltrace 是“现象观察器”,不是“根因分析器”。最终还是要结合代码、配置和业务逻辑判断。
总结
strace 和 ltrace 是 Linux 下定位进程卡死的两把利器:一个看内核边界,一个看用户态边界。掌握“先 ps 看状态,再 strace 找 syscall,再 ltrace 找库函数,最后 gdb 拿栈”的流程,大多数卡死问题都能在几分钟内缩小到具体代码行。工具本身不难,难的是在生产环境的压力下保持冷静、按步骤推进。希望这篇实战指南能帮你在下次遇到卡死时,不再只能重启了事。
未经允许不得转载:任鹏个人博客 » 手把手教你用 strace 和 ltrace 定位生产环境进程卡死问题


朋友圈点赞图在线生成源码