Linux服务器如何查看系统调用?strace命令使用实例

程序跑在服务器上,代码是别人写的,日志又没打全,你现在只知道它"卡住了""连不上数据库""打开文件报错",却不知道它到底在哪一步停住。这种时候,strace 是能让你直接看到进程和内核之间对话的工具:它把程序每一次系统调用、每一次返回值都打印出来,程序在等什么、被什么拒绝了,一目了然。

这篇文章讲清楚 strace 的原理和常用参数,重点放在怎么用它排查真实故障:程序卡在什么地方、文件为什么打不开、网络连接为什么建不起来。同时会明确告诉你一件重要的事——strace 是有性能开销的,生产环境使用要谨慎,用错了可能把线上问题放大。看完你应该能熟练地用它定位大多数"程序行为异常但日志无痕"的问题。

strace 的原理:看进程与内核的对话

应用程序想要读文件、发网络包、申请内存、创建子进程,都必须通过系统调用(system call)请求内核代为完成。普通程序没法直接操作硬件,系统调用就是那道门。strace 做的事情,是利用 ptrace 机制挂到目标进程上,在每次系统调用进入和返回时把参数和结果记录下来。

打个比方:内核是一个服务窗口,进程是来办事的人。正常情况下你只看到人进出窗口、办完事离开,但看不到他递了什么材料、窗口为什么退回。strace 相当于站在窗口旁边全程记录:谁递了什么、窗口返回了什么、等了多久。

它的输出长这样:

openat(AT_FDCWD, "/etc/nginx/nginx.conf", O_RDONLY) = 3
read(3, "#user  nginx;\nworker_processes"..., 4096) = 4096
close(3)                                = 0
connect(4, {sa_family=AF_INET, sin_port=htons(3306), sin_addr=inet_addr("10.0.0.5")}, 16) = -1 ECONNREFUSED (Connection refused)

左边是系统调用名,括号里是参数,等号后面是返回值。返回负数(如 -1)就是失败,后面跟着的错误码(如 ECONNREFUSED)说明失败原因。你要练的本事就是快速扫这些返回值和错误码,尤其是那些 -1 和耗时特别长的行。

安装很简单,多数发行版不带,需要手动装:

# RHEL / CentOS / Rocky
sudo dnf install -y strace

# Debian / Ubuntu
sudo apt update
sudo apt install -y strace

strace -V

常用参数一览

strace 参数很多,先记住下面这些就够处理九成场景:

参数 作用 典型场景
-p PID 附加到正在运行的进程 追踪线上服务,不用重启
-f 跟随子进程和线程 追踪会 fork 的程序、Java、多进程服务
-e trace= 只跟踪指定类别或具体调用 只看文件、网络、进程相关
-o 文件 把输出写到文件 输出量大时避免刷屏
-T 显示每次调用耗时 找哪一步慢
-t / -tt 显示时间戳 看时间线
-c 统计汇总,不逐条打印 看调用次数分布
-s 长度 字符串最大显示长度,默认 32 看完整的路径或报文内容
-y 显示文件描述符对应的路径 把 3 翻译成 /etc/nginx/nginx.conf

-e trace= 支持按类别过滤,最常用的几类:

-e trace=file       # 所有文件相关(open、stat、unlink 等)
-e trace=network    # 网络相关(socket、connect、sendto、recvfrom 等)
-e trace=process    # 进程相关(fork、execve、clone、exit 等)
-e trace=signal     # 信号相关
-e trace=desc       # 文件描述符相关(read、write、close 等)
-e trace=%memory    # 内存相关(mmap、brk 等)

类别前面加 ! 表示排除,比如 -e trace=!futex 可以过滤掉大量无意义的 futex 调用——这在追踪多线程程序时非常有用,因为 futex 经常刷屏。

实战一:程序卡在哪里

最常见的用法是"命令执行了但没反应",你要知道它卡在哪一步。

# 直接跟踪整个命令
strace -T -tt -s 200 curl -s http://10.0.0.5/health

最后几行通常停在某个系统调用上不动,比如卡在 connect 或 recvfrom,说明它在等网络;卡在 read,可能在等磁盘或某个管道;卡在 futex,可能是有线程锁竞争。加 -T 能看到每个调用的耗时,一眼就能看出是哪一次调用拖了很久。

对一个已经在跑的服务,用 -p 附加,不要重启它:

# 先找到 PID
ps -ef | grep nginx
pgrep -a nginx

# 附加追踪,只看网络和文件,跟随子进程
sudo strace -f -p $(pgrep -n nginx) -e trace=network,file -T -tt

纯追踪是只读的,不会改变进程行为,但它会拖慢目标进程,所以附加前要评估。默认按 Ctrl+C 就会脱离,进程继续正常运行。

下面这张对照表可以帮助快速定位卡点:

卡在哪个调用 可能原因 下一步查什么
connect 目标端口不通、被防火墙拦、服务没起 ss -tnp、telnet IP 端口
recvfrom / recv 等对端数据、对端处理慢 看对端服务日志、抓包
read 等磁盘 I/O、等管道/套接字输入 iostat -x、检查上游
write 磁盘满、管道缓冲区满、对端不读 df -h、检查下游进程
futex 线程锁竞争 属于业务逻辑问题,转移到应用层分析
nanosleep / poll 程序主动等待,可能正常 看代码里是否有重试间隔

实战二:文件打不开、权限不足

"明明文件在那里,程序却说没有"这类问题,用 strace 一追就现原形,因为你能看到它实际去访问的路径。

strace -e trace=file -f -s 200 ./your_app 2>&1 | grep -E 'ENOENT|EACCES|EPERM'

常见的错误码:

错误码 含义 常见原因
ENOENT 文件或目录不存在 路径拼错、工作目录不对、动态库缺失
EACCES 权限不足 属主/权限位不对、目录没有 x 权限
EPERM 操作不被允许 需要更高权限、被安全模块拦截
ENOTDIR 路径中某段不是目录 中间路径写成了文件
EISDIR 期望文件却是目录 路径指向目录

一个经典场景:程序报"找不到配置文件",但你觉得路径没错。用 strace 一追,可能发现它实际去找的是 /etc/myapp/config.yml 而不是 /opt/myapp/config.yml,或者它先去找相对路径、再去找其他几个位置,最后才失败。

注意 SELinux:如果权限位看起来都对,但操作仍被拒绝并返回 EACCES,很可能是 SELinux 拦的。这时要去审计日志里找 AVC 记录:

sudo ausearch -m avc -ts recent
sudo grep AVC /var/log/audit/audit.log | tail

strace 只告诉你有系统调用被拒绝,但拒绝它的是权限位还是 SELinux,要进一步确认。

实战三:网络连接问题

服务连不上数据库、请求第三方接口超时,用 strace 看网络调用最直接。

# 只看网络相关,显示时间戳和耗时
sudo strace -f -e trace=network -T -tt -s 300 -p $(pgrep -n myapp)

重点看 connect 和 sendto / recvfrom:

  • connect(...) = -1 ECONNREFUSED:目标端口没人监听,或服务挂了、防火墙返回了拒绝。
  • connect(...) = -1 ETIMEDOUT:包发出去了但没人回,通常是防火墙丢包或路由不通。
  • connect(...) = -1 EINPROGRESS:非阻塞套接字正常现象,随后要结合 poll/epoll 判断。
  • sendto(...) = -1 EPIPE / ECONNRESET:连接被对端关闭。

想看程序连的是哪个地址,connect 的参数里就写明了 sin_addr 和 sin_port。配合 -y 参数,还能把文件描述符翻译成具体的 socket 或文件路径:

strace -f -y -e trace=network,file -p 1234

统计汇总:-c 与 -C

逐条输出在排查"哪里慢、哪个调用多"时信息过载,-c 汇总成一张表:

strace -c -f -p 1234
# 运行几秒后 Ctrl+C,得到统计

输出大致如下:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.10    0.842000        4210       200           futex
 20.30    0.275000          27       1024        12 openat
  8.15    0.110000          11      9990           read
  5.00    0.068000          68      1000           write

% time 是各调用占用总时间的比例,calls 是次数,errors 是失败次数。如果一个进程大量时间花在 futex 上,说明线程锁竞争严重;read/write 次数极多,可能是没做缓冲、小包读写;openat 错误多,说明在反复尝试打开不存在的文件。

想要每次调用耗时统计而不是总耗时,可以配 -c -T,不过最常见的还是上面的默认统计。-C 是在正常输出之外同时给出统计,适合既想看细节又想看全局。

性能开销:生产环境慎用

这是必须强调的一点。strace 依托 ptrace,会让目标进程每进一次系统调用就停下来、被记录一次,在高频系统调用(比如高并发网络服务每秒几万次读写)的场景下,可能让目标进程慢几倍甚至一个数量级,还可能因为序列化而被拖成响应超时、触发上游熔断。

使用原则:

  • 能不停机就不追大流量服务,优先在测试环境复现。
  • 确实要在生产追,尽量缩短时间:用 timeout 5 strace ... 包一层,或者附加后几秒就 Ctrl+C。
  • 用 -e trace= 收窄范围,别全量打印。
  • 用 -c 只拿统计,输出量小,开销也相对低。
  • 追多线程程序时 -f 会让输出爆炸,务必配 -o 落盘并配合过滤。
# 限定 5 秒、只看网络、输出到文件
sudo timeout 5 strace -f -e trace=network -T -o /tmp/trace.log -p $(pgrep -n myapp)
wc -l /tmp/trace.log

如果只是想知道某进程打开了哪些文件和端口,用 lsof 比 strace 轻量得多,不必小题大做:

sudo lsof -p 1234
sudo ss -tnp

替代与补充工具

strace 不是万能的,遇到这些场景可以换个工具:

工具 用途 相对 strace 的优势
ltrace 跟踪库函数调用 站在更高层,看 malloc、strcmp 等
lsof 查看进程打开的文件与端口 轻量,不阻塞进程
perf 性能采样分析 开销小,适合生产做火焰图
bpftrace eBPF 动态追踪 内核态可编程,开销极低
tcpdump / wireshark 网络抓包 看完整报文,网络问题更细

用 eBPF 做系统调用统计,几乎不影响性能,是生产环境的更优解:

# 需安装 bcc-tools
sudo execsnoop-bpfcc
sudo syscount-bpfcc -p 1234 -L

常见问题

追踪程序时输出太多刷屏,怎么办?

用 -o 文件 落盘,再用 grep 过滤关键字,比如只看失败调用:grep '= -1' /tmp/trace.log。再加 -e trace= 收窄类别,以及用 -e trace=!futex,clock_gettime 排除噪声调用。

为什么附加到进程后看不到任何输出?

两种情况:一是该进程系统调用确实很少,可能在做纯计算;二是它内部开子进程/线程干活,而你没加 -f,只看到了主进程。加 -f 再试。还有一种是被 ptrace_scope 限制,需要 root 权限或调整 /proc/sys/kernel/yama/ptrace_scope。

strace 会不会把进程弄挂?

正常追随时 Ctrl+C 只是脱离,进程继续跑。但如果追踪期间进程被强杀,或者目标本身依赖 ptrace(某些调试器、容器运行时),可能互相干扰。所以生产附加前要有回滚预案,最好先测试。

内容被截断,看不到完整路径或报文?

默认字符串只显示 32 字节。加 -s 300 或更大。想看文件描述符对应的实际路径,加 -y。

权限不足报 ptrace: Operation not permitted?

非 root 用户默认只能追自己的子进程。用 sudo 提权,或检查 cat /proc/sys/kernel/yama/ptrace_scope:值为 0 允许附加任意同用户进程,值为 1 只允许追子进程。修改是临时的,需要持久化要写系统配置。

总结

  • strace 通过 ptrace 记录进程的每次系统调用,是把"程序行为异常"变成"看得见的调用序列"的核心工具。
  • 记牢几个保命参数:-f 跟子进程、-p 附加运行中进程、-e trace=file/network/process 过滤、-T 看耗时、-c 看统计、-s 放宽字符串长度、-o 落盘。
  • 卡点看最后停住的调用,失败看返回 -1 的错误码(ENOENT、EACCES、ECONNREFUSED 各代表不同问题),慢点看 -T 和 -c 的耗时。
  • 生产环境慎用,ptrace 会给高频系统调用的服务带来显著额外开销;能用 lsof、ss、bpftrace/perf 解决的,就别上 strace。
  • SELinux 拦截会表现为 EACCES,要配合 ausearch -m avc 确认,别只在权限位里找原因。