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 本质上做了三件事:
遍历 /proc:扫描
/proc/<pid>/目录读取 FD 信息:读取
/proc/<pid>/fd/目录解析符号链接:解析 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
输出类型包括:
类型 |
说明 |
|---|---|
|
当前工作目录 |
|
可执行文件 |
|
内存映射文件 |
|
网络连接 |
|
普通文件 |
一个 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>
输出示例:
类型 |
说明 |
|---|---|
|
当前目录 |
|
Python 可执行文件 |
|
libc 等共享库 |
|
监听端口 8000 |
|
stdout 管道 |
|
epoll 实例 |
5.2 结论
Linux 用 file abstraction(文件抽象) 统一了:
存储(文件、目录)
通信(socket、pipe)
设备(/dev/*)
进程交互(信号、epoll)
这是一种极其优雅的内核设计,让所有 I/O 操作都使用相同的系统调用(open、read、write、close)。
六、lsof vs 其他工具对比
工具 |
能力范围 |
|---|---|
|
仅进程信息 |
|
仅网络连接 |
|
仅 socket 状态 |
|
文件 + 网络 + 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 这个整数来引用和操作。
这就是"一切皆文件"在内核层面的真相。
九、常用命令速查表
场景 |
命令 |
解释 |
|---|---|---|
查看所有打开文件 |
|
显示系统中所有打开的文件 |
查看指定进程 |
|
显示指定 PID 打开的文件,-p 选项用于指定进程 ID |
查看端口占用 |
|
显示指定 TCP 端口的监听状态,-i 选项用于指定网络连接,-s 选项用于指定 socket 状态 |
查看所有监听端口 |
|
显示所有监听的网络连接,-P 选项用于显示端口号而非服务名称,-n 选项用于不解析主机名 |
查看 TCP 连接 |
|
显示所有 TCP 连接,-i 选项用于指定网络连接 |
查看指定端口连接 |
|
显示指定 TCP 端口的连接 |
查看连接到某 IP 的进程 |
|
显示连接到指定 IP 的进程,@ 选项用于指定 IP 地址 |
查看 Unix Socket |
|
显示所有 Unix Socket,-U 选项用于指定 Unix Socket |
查看文件被谁使用 |
|
显示使用指定文件的进程 |
查看进程当前目录 |
|
显示指定进程的当前目录,-p 选项用于指定进程 ID |
查看管道 |
|
显示所有管道 |
查看 FD 数量 |
|
显示指定进程打开的文件描述符数量 |
十、原文欠妥之处及修正
原文问题 |
修正说明 |
|---|---|
|
补充了各类型(cwd/txt/mem/IPv4)的具体含义 |
FD 泄漏排查步骤不够完整 |
补充了 |
"Too many open files" 排查不完整 |
增加了系统级和进程级限制的区分 |
缺少内核数据结构说明 |
补充了 |
场景编排较散乱 |
重新组织为 13 个场景,分类更清晰 |
缺少命令速查表 |
新增第九章常用命令速查表 |
整理自原文,优化结构、补充细节、修正欠妥之处。