这个命令是干啥的
lsof 全称是 List Open Files。在 Linux 里"一切皆文件",所以 lsof 能列出来:普通文件、目录、网络 socket、管道、设备文件,只要是进程打开的东西都能查。
我第一次被 lsof 救命的场景是这样的:有个程序要监听 8080 端口,但启动时报 Address already in use。我 df、netstat 一顿操作猛如虎,发现端口确实被别人占了,但不知道是谁。老同事过来敲了个 lsof -i :8080,直接显示了 PID 和进程名,干净利落。从那天起 lsof 就是我的必装工具。
lsof 最大的好处是:它能告诉你"谁在用这个东西"。不管是文件删不掉、端口被占、磁盘卸载不了,lsof 一出马,总能找到元凶。
基本用法(3分钟上手)
查看哪个进程占用了端口
# 查看 8080 端口被哪个进程占用
lsof -i :8080
输出大概是这样的:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 12345 ubuntu 56u IPv4 123456 0t0 TCP *:8080 (LISTEN)
COMMAND 是进程名,PID 是进程 ID,USER 是运行用户,NAME 显示是监听状态。一目了然。
# 查看所有监听中的端口
lsof -i -s TCP:LISTEN
查看文件被哪个进程占用
# 查看某个文件正在被谁使用
lsof /var/log/nginx/access.log
删文件的时候提示 Device or resource busy,用这个查一下就知道是哪个进程还在用。
查看某个用户打开的文件
# 查看 ubuntu 用户打开了哪些文件
lsof -u ubuntu
这个命令输出一般很多,通常会配合 grep 过滤:
# 查看指定用户打开的 TCP 连接
lsof -u ubuntu -i TCP
查看某个进程打开了哪些文件
# 查看 PID 为 12345 的进程打开了哪些文件
lsof -p 12345
这个特别实用。比如你的 Java 程序内存泄漏,怀疑是文件句柄没释放,用这个一看就知道。
进阶骚操作
递归查找目录下被打开的文件
# 递归查找 /var/log 目录下所有被打开的文件
lsof +D /var/log
+D 会递归遍历整个目录,比直接用 lsof /var/log/* 方便多了。注意如果目录下文件很多,输出会很大。
查看已删除但未释放的文件
这是我最常用的技巧。有时候你用 df -h 看到磁盘满了,但是 du -sh /* 加起来又没那么多,说明有一个被删掉的文件还在被占用。
# 查找已删除但进程还在用的文件
lsof | grep '(deleted)'
输出里会看到类似这样的行:
nginx 12345 root txt REG 8,1 0 123456 /usr/sbin/nginx (deleted)
这种文件已经被删除,但进程打开的文件描述符还没关,所以磁盘空间没真正释放。解决方案是重启进程或者 > /proc/PID/fd/FD 清空。
查看网络连接详情
# 查看所有 IPv4 的网络连接
lsof -i4
# 查看所有 IPv6 的网络连接
lsof -i6
# 查看所有 TCP 连接
lsof -i TCP
# 查看所有 UDP 连接
lsof -i UDP
# 排除 22 端口(SSH)
lsof -i TCP -s TCP:LISTEN -u ^root
-s TCP:LISTEN 只显示监听的端口,^root 表示排除 root 用户的进程。
查看进程的当前工作目录
# 查看进程的当前目录
lsof -p 12345 | grep cwd
cwd 就是 current working directory。有时候你想知道一个后台进程是在哪个目录启动的,用这个查。
查看进程打开了哪些库文件
# 查看进程加载的动态库
lsof -p 12345 | grep '\.so'
查动态库版本、库文件路径,定位依赖问题的时候很有用。
避坑指南
1. lsof 输出太大
没有过滤条件直接跑 lsof,输出可能上万行,而且很慢。系统上有几百个进程,每个可能打开几百个文件描述符。所以一定要加过滤条件,要么 -i、要么 -p、要么 grep。
2. 权限问题
普通用户只能看到自己进程的文件。想查所有进程,需要 root 权限:
# 非 root 用户查其他进程会返回空或者报错
sudo lsof -i :80
3. 内核线程和进程的区别
# lsof 会显示内核线程(kernel thread),它们没有用户态文件描述符
# 可以通过 -K 排除
lsof -K
Linux 内核线程也有 PID,但它们的文件信息没有意义,-K 可以排除。
4. 文件描述符类型区分
lsof 输出里的 FD 列:
- cwd 当前工作目录
- rtd 根目录
- txt 程序代码文件
- mem 内存映射文件
- 0u、1u、2u 分别对应 stdin、stdout、stderr
TYPE 列:
- REG 普通文件
- DIR 目录
- IPv4 或 IPv6 网络 socket
- unix Unix domain socket
5. 不要在繁忙系统上跑裸 lsof
# 不要这样,会卡死终端
lsof
# 要这样,加限定
lsof -i :3306
我之前在一个 64G 内存、上千个连接的生产数据库上跑了 lsof,结果等了 30 秒才出结果,而且 CPU 飙到 100%。从此以后必加过滤。
实战场景
场景一:端口被占,快速定位凶手
上线新服务,报错 bind: address already in use:
# 快速找到端口占用者
sudo lsof -i :8080
输出显示 PID 是 12345,是个 Java 进程。然后决定是杀了重来还是换个端口:
# 杀进程
kill -9 12345
# 或者确认一下这个 PID 是谁的
ps -ef | grep 12345
场景二:磁盘空间满了但 df 和 du 对不上
# 先看 df
df -h
# 发现 /var 用了 95%,但 du -sh /var/* 加起来只有 60%
# 查被删除但未释放的文件
sudo lsof | grep '(deleted)'
找到后,确认是哪个文件:
# 确认文件对应的进程和文件描述符号
sudo lsof +L1
+L1 显示 link count 小于 1 的文件,也就是被删掉的文件。然后要么重启进程,要么清空文件描述符:
# 清空被删文件占用的空间(不会删除进程正在写的文件)
: > /proc/PID/fd/FD_NUMBER
场景三:Nginx 日志轮转后还在写旧文件
做日志轮转后,发现旧日志文件的大小还在增长,说明 Nginx 还在往里写:
# 查看 nginx 进程打开了哪些日志文件
lsof -c nginx | grep log
输出会显示 nginx 打开的文件路径。如果日志轮转后没有 reload,它还在写旧文件的 inode。解决方法是 nginx -s reload。
场景四:外部存储卸载失败
# USB 盘或者 NFS 挂载点卸载不了
umount /mnt/usb
# 报错: target is busy
# 查谁在用
lsof /mnt/usb
找到后,要么关掉对应的进程,要么加 -l 强制卸载。
今日作业
你的服务器 /tmp 目录空间告警,但用 du -sh /tmp/* 找不到太大的文件。请用 lsof 查找:
1. 在 /tmp 目录下被打开的文件
2. 特别关注标记为 "(deleted)" 的文件
3. 找到之后,不重启进程,用 : > /proc/PID/fd/FD 的方式释放空间
4. 确认空间是否真的释放了
写一条完整的 lsof 命令,并说明你怎么判断进程是否可以安全清理。