最准确的方法是直接查看/proc/[PID]/fd目录,它由内核维护,包含每个文件描述符指向的实际资源;配合/proc/[PID]/fdinfo/[FD]可获取偏移、标志等详细状态;lsof则提供语义化解释但受权限限制。

直接看 /proc/[PID]/fd 目录最准
Linux 内核为每个进程在 /proc/[PID]/fd 下维护一组符号链接,每个链接名就是文件描述符编号(如 0、3、12),指向它实际打开的资源。这是最底层、最权威的来源,不依赖外部工具,也不受权限过滤干扰。
操作步骤:
- 先用
ps aux | grep your_process或pidof nginx拿到目标 PID - 执行
ls -l /proc/1234/fd(把1234换成真实 PID) - 每行形如
lr-x------ 1 user user 64 Jul 9 04:20 3 -> /etc/passwd,其中3是 fd 编号,-> /etc/passwd是它指向的目标
注意:如果看到 socket:[12345] 或 pipe:[67890],说明该 fd 指向网络套接字或管道,不是普通文件;anon_inode:[eventpoll] 则是 epoll 实例——这些都合法,也是 fd 的常见目标。
用 lsof -p [PID] 看带语义的详情
lsof 不是“数 fd”,而是解释 fd 的含义:它告诉你这个 fd 是普通文件、目录、socket 还是内存映射,以及访问模式(r、w、u)、inode、设备号等。适合排查“为什么某个 fd 占着不放”或“哪个 socket 对应哪个连接”。
常见陷阱:
- 非 root 用户运行
lsof -p [PID]可能看不到其他用户进程的 fd,报can't identify protocol或缺失部分条目 lsof -p [PID] | wc -l的结果比ls /proc/[PID]/fd | wc -w多 1 行(表头),别直接当数量用- FD 列显示
cwd(当前工作目录)、txt(可执行文件)、mem(内存映射)这些不是数字 fd,但确实在进程资源里——/proc/[PID]/fd不包含它们
查某个 fd 的详细信息用 /proc/[PID]/fdinfo/[FD]
光知道 3 -> /dev/tty 不够?想确认它是否被设为非阻塞、是否启用 close-on-exec、当前读写偏移量?那就得看 /proc/[PID]/fdinfo/[FD]。
例如:
cat /proc/1234/fdinfo/3
输出类似:
pos:0 flags:02004002 mnt_id: 12 ino:12345
其中 flags 是八进制掩码,02004002 对应 O_RDWR|O_LARGEFILE|O_CLOEXEC;pos 是当前文件偏移(对 socket 或 pipe 恒为 0)。
关键点:
- 只有内核 2.6.22+ 支持
fdinfo,老系统可能无此目录 - 该文件内容是瞬时快照,多次读可能不同(尤其对 pipe、socket)
- 普通用户只能读自己进程的
/proc/[PID]/fdinfo/*,即使有 PID 也无法越权读其他用户进程
别误用 stat 或 ls 查 fd 本身
stat 和 ls -l 盯着看的,其实是“文件路径”,并不是“文件描述符”。也正因为如此,直接对 /proc/1234/fd/3 执行 stat,拿到的并不是原始文件的元数据;它返回的是这个符号链接本身的属性。比如,它的创建时间通常固定在进程启动那个时刻,而不是目标文件(例如 /etc/passwd)真实的 mtime 或 inode。
真正要查目标文件详情,得先用 readlink /proc/1234/fd/3 拿到路径,再 stat 那个路径。但注意:如果目标是 socket、pipe 或已删除仍被占用的文件(deleted 标记),readlink 返回的路径无法直接 stat。
所以:查 fd 本身状态,盯死 /proc/[PID]/fd 和 /proc/[PID]/fdinfo/[FD];查目标文件属性,按需结合 readlink + stat,并接受其局限性。