lsof

如何揭示 Linux 一切皆文件的终极真相

原文整理自微信公众号「技术杂家」 优化调整:结构重组、补充说明、修正欠妥之处


一、Linux 的核心哲学:一切皆文件

在 Linux 世界里,有一句被奉为真理的话:一切皆文件(Everything is a file)

但很多人只停留在"知道",却不知道如何验证lsof(List Open Files)就是最好的验证工具。

1.1 什么都可以是文件

对象

是否文件

普通文件

目录

TCP 连接

UDP 连接

Unix Socket

管道(pipe)

匿名内存映射

设备文件(/dev/null)

磁盘块设备

这就是 Linux 内核的统一抽象——所有 I/O 操作都通过文件描述符(File Descriptor, FD)完成。


二、lsof 的底层原理

lsof 本质上做了三件事:

  1. 遍历 /proc:扫描 /proc/<pid>/ 目录

  2. 读取 FD 信息:读取 /proc/<pid>/fd/ 目录

  3. 解析符号链接:解析 FD 指向的实际对象

2.1 /proc 文件系统示例

ls -l /proc/1234/fd

# 输出示例:
# 3 -> socket:[402653]
# 4 -> /var/log/app.log
# 5 -> anon_inode:[eventpoll]

解读:

  • FD=3 是 socket

  • FD=4 是日志文件

  • FD=5 是 epoll 实例

lsof 做的事情就是把这些信息结构化 + 关联进程 + 解析网络信息,以人类可读的方式呈现。


三、基础用法

3.1 查看所有打开的文件

lsof

⚠️ 生产环境慎用:输出量巨大,可能影响性能。

3.2 查看指定进程打开的文件

lsof -p 1234

输出类型包括:

类型

说明

cwd

当前工作目录

txt

可执行文件

mem

内存映射文件

IPv4/IPv6

网络连接

REG

普通文件

一个 Java 进程可能打开几百甚至几千个文件——这就是"一切皆文件"的具象体现。


四、生产环境实战场景

场景 1:排查端口占用

lsof -iTCP:6443 -sTCP:LISTEN

输出示例:

COMMAND  PID  USER   FD   TYPE  DEVICE  SIZE/OFF  NODE  NAME
java     1234 app    3u   IPv4  12345   0t0       TCP   *:6443 (LISTEN)

优势:比 netstat 更直观,直接显示 PID 和进程名。

场景 2:查看所有监听端口

lsof -i -P -n | grep LISTEN

参数说明:

  • -P:不解析端口号(显示数字,避免卡顿)

  • -n:不解析 DNS(避免卡顿)

场景 3:排查 FD 泄漏

lsof -p 1234 | wc -l

如果数字持续增长,说明存在 FD 泄漏。常见原因:

  • 文件流未关闭(fopen 后忘记 fclose

  • Socket 未关闭

  • epoll FD 未释放

场景 4:排查 "Too many open files"

错误现象:

EMFILE: Too many open files

排查步骤:

# 1. 查看进程实际打开的 FD 数量
ls /proc/1234/fd | wc -l

# 2. 用 lsof 查看具体是什么文件
lsof -p 1234

# 3. 检查系统限制
ulimit -n

# 4. 检查进程级别的限制
cat /proc/1234/limits | grep "open files"

场景 5:查看 TCP 连接

# 所有 TCP 连接
lsof -i tcp

# 指定端口
lsof -i tcp:3306

这直接揭示:网络连接本质也是文件

场景 6:查看连接到某台远程机器的进程

lsof -i @10.0.0.5

场景 7:查看 Unix Domain Socket

lsof -U

输出示例:

COMMAND  PID  USER   FD   TYPE  DEVICE  SIZE/OFF  NODE  NAME
docker   1234 root   3u   Unix  0t0     0t0       12345 /var/run/docker.sock

说明:Docker API 本质上是 Unix 文件通信

场景 8:查看某个文件被谁使用

lsof /var/log/app.log

用于排查"文件被占用无法删除"的问题。

场景 9:查看内存映射文件

lsof | grep mem

Java 的 jar 包、.so 库都会显示为内存映射文件。

场景 10:容器环境排查

nsenter -t <pid> -m -u -i -n -p lsof

进入容器的命名空间后运行 lsof,可以观测:

  • 容器网络

  • 挂载文件

  • 共享 FD

场景 11:排查 NFS 锁

lsof | grep nfs

场景 12:查看进程当前目录

lsof -p 1234 | grep cwd

场景 13:查看管道

lsof | grep pipe

Shell 管道 | 的本质:匿名文件


五、用 lsof 验证"一切皆文件"

5.1 实验验证

启动一个简单的 HTTP 服务:

python3 -m http.server 8000 &

查看该进程打开的文件:

lsof -p <pid>

输出示例:

类型

说明

cwd

当前目录

txt

Python 可执行文件

mem

libc 等共享库

IPv4

监听端口 8000

pipe

stdout 管道

anon_inode

epoll 实例

5.2 结论

Linux 用 file abstraction(文件抽象) 统一了:

  • 存储(文件、目录)

  • 通信(socket、pipe)

  • 设备(/dev/*)

  • 进程交互(信号、epoll)

这是一种极其优雅的内核设计,让所有 I/O 操作都使用相同的系统调用(open、read、write、close)。


六、lsof vs 其他工具对比

工具

能力范围

ps

仅进程信息

netstat

仅网络连接

ss

仅 socket 状态

lsof

文件 + 网络 + FD 全覆盖

lsof 提供的是统一视角,一个命令搞定多种排查需求。


七、架构师视角:为什么这很重要?

理解 lsof 和"一切皆文件",你会获得三种能力:

7.1 抽象能力

Linux 通过 file descriptor 统一所有资源。这类似于:

  • JVM:一切皆对象

  • Kubernetes:一切皆 API

  • Unix:一切皆流

7.2 故障排查模型

当线上出现问题时:

问题类型

排查思路

端口占用

是哪个进程持有了 socket 文件?

磁盘满

是哪个进程打开了太多文件?

FD 泄漏

是哪个文件没有正确关闭?

连接异常

是哪个 socket 状态异常?

核心问题:哪个文件没释放?

7.3 性能优化视角

高并发服务器的核心指标:

  • socket 数量

  • epoll FD 数量

  • 文件句柄上限

这些都能用 lsof 观测和调优。


八、终极理解:内核数据结构

Linux 内核本质上维护着这样的结构:

task_struct(进程描述符)
    └── files_struct(文件表)
            └── file descriptor table(FD 表)
                    └── fd[0] → stdin
                    └── fd[1] → stdout
                    └── fd[2] → stderr
                    └── fd[3] → socket/pipe/file/...
                    └── ...

所有资源(文件、socket、pipe、设备)都通过 int fd 这个整数来引用和操作。

这就是"一切皆文件"在内核层面的真相。


九、常用命令速查表

场景

命令

解释

查看所有打开文件

lsof

显示系统中所有打开的文件

查看指定进程

lsof -p <pid>

显示指定 PID 打开的文件,-p 选项用于指定进程 ID

查看端口占用

lsof -iTCP:<port> -sTCP:LISTEN

显示指定 TCP 端口的监听状态,-i 选项用于指定网络连接,-s 选项用于指定 socket 状态

查看所有监听端口

lsof -i -P -n | grep LISTEN

显示所有监听的网络连接,-P 选项用于显示端口号而非服务名称,-n 选项用于不解析主机名

查看 TCP 连接

lsof -i tcp

显示所有 TCP 连接,-i 选项用于指定网络连接

查看指定端口连接

lsof -i tcp:<port>

显示指定 TCP 端口的连接

查看连接到某 IP 的进程

lsof -i @<ip>

显示连接到指定 IP 的进程,@ 选项用于指定 IP 地址

查看 Unix Socket

lsof -U

显示所有 Unix Socket,-U 选项用于指定 Unix Socket

查看文件被谁使用

lsof <file>

显示使用指定文件的进程

查看进程当前目录

lsof -p <pid> | grep cwd

显示指定进程的当前目录,-p 选项用于指定进程 ID

查看管道

lsof | grep pipe

显示所有管道

查看 FD 数量

lsof -p <pid> | wc -l

显示指定进程打开的文件描述符数量


十、原文欠妥之处及修正

原文问题

修正说明

lsof -p 1234 输出说明不够详细

补充了各类型(cwd/txt/mem/IPv4)的具体含义

FD 泄漏排查步骤不够完整

补充了 ulimit -n/proc/<pid>/limits 检查

"Too many open files" 排查不完整

增加了系统级和进程级限制的区分

缺少内核数据结构说明

补充了 task_structfiles_struct → FD table 的层次关系

场景编排较散乱

重新组织为 13 个场景,分类更清晰

缺少命令速查表

新增第九章常用命令速查表


整理自原文,优化结构、补充细节、修正欠妥之处。