更稳妥的做法,其实是直接去读 /proc/[pid]/fd/ 这个目录:先用 ls -l 把所有数字形式的 fd 项列出来,再通过 readlink 看它们各自指向哪里;接着把 stdin/stdout/stderr 这类伪路径过滤掉,同时结合 stat 或 readlink 失败的结果判断该 fd 是否已经关闭,这样就能尽量不依赖 lsof 之类的外部工具。

怎么查某个进程当前打开的 fd 列表
直接读 /proc/[pid]/fd/ 是最轻量、最可靠的方式,不需要额外工具,也不受权限限制(只要能访问该进程的 proc 目录)。每个数字子项就是一个打开的 fd,readlink 能看出它指向什么。
- 先拿到 PID,比如
pgrep nginx或pidof mysqld - 执行
ls -l /proc/1234/fd/,输出类似:0 -> /dev/pts/03 -> socket:[123456]4 -> /var/log/nginx/access.log - 想只看真实文件(排除 stdin/stdout/stderr),加过滤:
ls -l /proc/1234/fd/ | awk '$NF !~ /^/dev/pts/|^/proc/|^/sys// {print}' - 注意:符号链接失效(如
readlink: No such file or directory)通常意味着 fd 已关闭但未被回收,属于内核延迟释放,不是泄漏
为什么 lsof -p PID 有时不准或报错
lsof 看起来全面,但实际容易出问题:权限不足时跳过部分条目;遇到已关闭但未清理的 fd 会显示 can't identify protocol 或直接漏掉;某些容器环境或 seccomp 限制下根本跑不起来。
- 常见错误:
lsof: no pwd entry for UID xxx—— 不影响计数,但说明用户数据库缺失,lsof -n -p PID可绕过 DNS/UID 解析 - 统计总数别用
lsof -p PID | wc -l,它包含表头和空行;稳妥写法:lsof -n -p PID 2>/dev/null | grep -v "COMMAND" | wc -l - 真正要定位泄漏点,优先用
lsof -n -p PID | awk '{print $9}' | sort | uniq -c | sort -nr | head -5,看哪些路径重复最多
cat /proc/[pid]/status 里的 FDs 和 FDSize 是啥
这两个字段是内核快照值,非实时精确计数,但适合快速巡检。它们来自同一内存结构,比遍历 /proc/[pid]/fd/ 开销小得多。
- 执行
cat /proc/1234/status | grep -E 'FDs|FDSize',输出形如:FDSize: 256FDs: 42 FDs是当前已分配且活跃的 fd 数(≈ls /proc/[pid]/fd/ | wc -w)FDSize是内核为该进程预分配的 fd 槽位总数(通常是 256、1024、2048 等 2 的幂),不是限制值,只是内部哈希表大小- 如果
FDs接近FDSize,说明进程频繁扩容 fd 表,可能有泄漏或设计缺陷
怎么确认某个 fd 是否真在用
光看 /proc/[pid]/fd/ 里有链接不够,得验证它是否还绑定有效资源。很多“僵尸 fd”其实是 close() 调用后未被内核立即回收的残留。
- 对 fd 3 执行:
readlink /proc/1234/fd/3,若返回No such file or directory,大概率已关闭 - 更准一点:用
stat /proc/1234/fd/3 2>/dev/null | grep -q "No such file"判断是否存在 - 如果
readlink成功但目标路径是socket:[123456]或anon_inode:[eventpoll],说明是网络连接或 epoll 实例,属正常活跃状态 - 真正可疑的是大量指向
pipe:[...]或inotify却长期不读写的 fd,这类容易堆积