/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
一、引言
运维里有一类最让人抓狂的问题——看不见:
- Nginx 起不来,说端口被占用,但 netstat 一扫又没看到谁;
- 系统磁盘报警「满了」,可 du 把所有目录加起来根本没那么多;
- 一个进程 fd 数悄悄涨到上万,最后 too many open files 崩掉(呼应 Nginx、redis 连接爆);
- 某程序卡死,你不知道它到底卡在等网络、等锁、还是等一个永远不来的文件;
- 进程被莫名杀掉,想搞清楚是收到 SIGKILL 还是 OOM(呼应 systemd 信号篇)。
这类问题的共同点是:真相藏在「进程和内核之间」,普通命令看不到。lsof 和 strace 就是对付它们的两把刀——一个看静态持有(进程手里攥着哪些文件/端口/句柄),一个看动态行为(进程正在发什么系统调用、收什么信号)。本文把它们讲透,并和前面几篇的排错点串起来。
二、原理解析
2.1 lsof:一切皆文件,看进程攥着什么
Linux 的哲学是 "everything is a file"——普通文件、目录、socket、管道、设备、甚至内存映射,在内核里都挂在一个文件描述符(fd)上。每个进程打开的东西都是一个 fd,fd 表就是它和外界的所有"连接点"。
lsof 做的事,本质是遍历内核的打开文件表 + /proc/<pid>/fd,把每个 fd 还原成人类可读的「哪个进程、什么类型、指向什么」。它的输出关键列:
- FD:如 5u(5 号 fd,u=读写);cwd=工作目录、txt=程序本身、mem=内存映射库。
- TYPE:REG(普通文件)、DIR(目录)、IPv4/IPv6(socket)、PIPE、unix(域套接字)。
- NAME:指向的路径或 *:80 (LISTEN) 这类网络信息。
2.2 strace:系统调用跟踪,看进程在做什么
strace 基于内核的 ptrace 机制:它让目标进程每次进入/退出系统调用时都被"拦截",记录下调用名、参数、返回值、耗时。你看到的每一行,就是进程和内核的一次对话:
三个核心能力直接对应排错:
- 卡在哪:某行 syscall 长时间不返回(如 futex 死锁、read 阻塞、connect 超时)→ 这就是卡点。
- 错了什么:返回值 -1 后面跟的 errno(ECONNREFUSED/ETIMEDOUT/EAGAIN)比应用日志更底层、更真实。
- 收了什么信号:-e trace=signal 能看见进程收到 SIGTERM/SIGKILL/SIGHUP(呼应 systemd 篇的"先 TERM 后 KILL"、OOM 是 SIGKILL 形式)。
开销警告:ptrace 会显著拖慢目标进程(每个 syscall 都要上下文切换+序列化)。生产核心进程只可短时挂、要过滤、别长期跑;高并发多线程用 -f 输出会爆炸,务必配 -e 缩小范围。
三、实战演示
3.1 lsof 高频用法(端口/文件/句柄/已删)
3.2 句柄泄漏诊断(呼应 Nginx too many open files / Redis 连接爆)
经验:fd 表里 IPv4/IPv6 持续涨 = socket 泄漏(连接没关,典型如应用没用连接池,呼应 Redis 连接爆);REG 持续涨 = 文件/socket 打开后没 close。
3.3 strace 高频用法
3.4 四个真实排错场景
- 场景 1 · 程序卡死:strace -p <pid>,发现最后一行停在 futex(…, FUTEX_WAIT) 不返回 → 锁竞争/死锁;或停在 read(3, 不动 → 在等对端数据(网络/管道断连)。
- 场景 2 · 端口被占启动失败:lsof -i :80 找到元凶 PID,kill 掉再起服务。 | | | | — | — |
- 场景 3 · 磁盘满但 du 对不上:lsof | grep deleted 找到持有已删大文件的进程(日志轮转后没重载的服务最典型),重启/重载该进程即释放空间——呼应 OverlayFS 删容器空间不释放、systemd 进程文件句柄。
- 场景 4 · 配置/日志路径不对:strace -e trace=file ./app 2>&1 | grep openat 抓到它实际 openat 的是哪个路径,瞬间定位"为什么读不到配置"。
四、排错实战(重点)
4.1 lsof 权限不足 / 命令不存在
- lsof: permission denied 或看不到别人进程 → 加 sudo,普通用户只能看自己。
- 精简容器/最小化系统可能没装 → yum/apt install lsof。
- 极端受限环境用 /proc/<pid>/fd 直接 ls -l 兜底(lsof 的本质数据源)。
4.2 "端口被占"但 lsof -i 看不到
- 可能是 TIME_WAIT(连接刚关,内核还在等 2MSL),lsof -i 不一定列,但 ss/netstat 看得到:
- 或进程正在绑定的竞态中、还没完全 listen。先用 ss 确认真实状态,别只信 lsof。
4.3 已删除文件磁盘不释放(经典高频)
这是生产最常见的"空间消失"根因:文件被 rm 了,但持有它的进程没关 fd,磁盘块不会回收。日志轮转(logrotate 后没 reload 服务)、大文件误删、容器删了但进程还在写,全是同一机理。
4.4 strace 性能开销 / 生产谨慎
- ptrace 会拖慢目标数倍,绝不对核心生产进程长期挂。
- 短时抓 + 过滤:-e trace=network 只看网络、-c 只统计不逐行。
- 多线程高并发用 -f 输出会瞬间爆量,配合 -e 缩小;输出落 -o file 而非直接刷屏。
4.5 strace 看不到"用户态"卡顿
- strace 只追系统调用。纯用户态死循环、CPU 100% 算个不停、应用层自旋——这些不产生 syscall,strace 一片空白。
- 这种该用 top/perf 看 CPU 占用,或 gdb 附加(更重、会暂停进程)。
- 卡在 futex 是锁竞争信号:看哪些线程在等同一个 futex,再回到代码/配置层。
4.6 容器 / namespace 里 lsof 受限
- 容器内 lsof 默认只看到该容器的 mount/pid namespace,看不到宿主机或其他 netns 的资源。
- 宿主机侧用 nsenter 进对应 namespace 再看,或按容器 PID 直接查宿主 /proc/<pid>/fd。
- 这也是为什么 K8s 节点上排查要用 crictl/nsenter 而非裸 lsof。
4.7 信号排查(呼应 systemd 篇)
进程收 SIGTERM 没退 → 呼应 systemd 篇"停不掉";收 SIGKILL 不可捕获 → 多半 OOM 或被人强杀。
五、总结
记住一句分工,这两把刀就不会用错:
- lsof 看静态持有:端口被谁占、fd 泄漏、谁在写某文件、已删未释放的磁盘空间。它是"快照式"——回答"现在攥着什么"。
- strace 看动态行为:syscall 卡哪、errno 是什么、收了什么信号、实际 openat 的路径。它是"录像式"——回答"正在干什么 / 为什么卡"。
两者组合:lsof 先定位资源(哪个进程、哪个 fd),strace 再定位行为(它拿这 fd 在干嘛),几乎能破掉一半"看不见"的运维怪现象。
和前面几篇的闭环:Nginx too many open files / Redis 连接爆 → 用 lsof 看 fd 类型和增长;磁盘删了不释放 → lsof deleted;systemd 信号/OOM → strace signal + dmesg;容器排查受限 → nsenter 进 namespace。
速查表
|
目的 |
命令 |
|
查端口占用 |
lsof -i :80 / ss -tunlp |
|
进程开了啥 |
lsof -p <pid> |
|
句柄泄漏 |
watch -n1 "ls /proc/<pid>/fd | wc -l" |
|
已删未释放 |
lsof -n -P | grep deleted |
|
跟踪进程 |
strace -p <pid> |
|
跟子线程 |
strace -f -p <pid> |
|
只看网络 |
strace -e trace=network |
|
只看信号 |
strace -e trace=signal |
|
耗时/统计 |
strace -T / strace -c |
|
抓文件路径 |
strace -e trace=file 2>&1 | grep openat |


