用lsof查看打开的文件和端口,从进程查看到误删恢复的操作记录
以下命令在 Debian 11/12、Ubuntu 20.04/22.04、CentOS 7/8 上验证过,操作环境包括AWS洛杉矶CN2 GIA节点(1核1G内存、20GB SSD、1Gbps带宽)、LuckVM德国法兰克福节点(2核2G/80GB NVMe/50Mbps BGP欧洲线路,月付$10)、LuckVM泰国曼谷节点(1核1G/60GB NVMe/20Mbps,月付$7)、LuckVM澳大利亚悉尼节点(1核512M/60GB SSD/10Mbps,月付$6.5)、DMIT香港CN2 GIA节点(1核1G内存)、Vultr东京节点(1核1G内存、25GB SSD)、AWS Lightsail新加坡节点(1核512M内存、20GB SSD),全程root或sudo权限执行。lsof需要查看所有进程信息,普通用户执行只能看到自己的进程,查看系统进程建议加sudo或使用root。
一、lsof基础:Linux一切皆文件
在Linux里,几乎所有东西都是文件:普通文件、目录、网络连接(socket)、管道、设备、进程间通信…… 一个进程启动后会打开很多文件,每个文件通过**文件描述符(File Descriptor,简称fd)**来操作。常见文件描述符编号:0是标准输入stdin,1是标准输出stdout,2是标准错误stderr,从3开始是进程自己打开的文件。
lsof(list open files)就是"列出打开的文件",能回答这些问题:
- 某个进程打开了哪些文件?
- 某个端口被哪个进程占用?
- 某个文件正在被哪些进程使用?
- 某个用户打开了哪些文件?
- 文件被删了但空间没释放,是什么进程占着?
- 无法卸载磁盘,是什么进程在用这个目录?
二、lsof基础输出解读
直接输入 lsof 会列出当前系统所有打开的文件(输出可能非常多,几万行很正常),每列含义:
COMMAND PID TASKCMD USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root cwd DIR 8,1 4096 2 /
systemd 1 root txt REG 8,1 1620248 525128 /usr/lib/systemd/systemd
systemd 1 root 0u CHR 1,3 0t0 1028 /dev/null
nginx 1247 root 6u IPv4 25832 0t0 TCP *:http (LISTEN)
| 列名 | 含义 |
|---|---|
| COMMAND | 进程名(默认前9个字符,加+c w可显示更长) |
| PID | 进程ID |
| USER | 进程所属用户 |
| FD | 文件描述符编号和访问模式(见下表) |
| TYPE | 文件类型(REG普通文件/DIR目录/IPv4 IPv6网络/CHR字符设备/FIFO管道等) |
| DEVICE | 设备号(主设备号:次设备号,对应磁盘分区) |
| SIZE/OFF | 文件大小或偏移量 |
| NODE | inode编号 |
| NAME | 文件路径、挂载点或网络地址 |
FD列常见值:
| 标识 | 含义 |
|---|---|
| cwd | 当前工作目录(进程启动的目录) |
| txt | 程序可执行文件本身(代码text段) |
| mem | 内存映射文件(.so动态库等) |
| 0u / 1u / 2u | 标准输入/输出/错误(u=读写,r=只读,w=只写) |
| 3w / 4r | 第3个文件描述符写模式 / 第4个读模式 |
| 6u | IPv6 socket |
三、按条件过滤查看
(1)查看某个进程打开的文件:lsof -c 进程名
# 查看nginx进程打开的所有文件
lsof -c nginx
# 同时查看多个进程
lsof -c nginx -c mysql
# -c支持前缀匹配,lsof -c ng会匹配nginx、nginx-worker等以ng开头的进程
(2)查看某个PID打开的文件:lsof -p PID
# 查看PID 1247打开的文件
lsof -p 1247
# 排除某个PID(^开头表示取反,排除PID 1的进程)
lsof -p ^1
(3)查看某个用户打开的文件:lsof -u 用户名
lsof -u root
lsof -u www-data
# 排除某个用户打开的文件
lsof -u ^root
(4)查看某个目录下被打开的文件:lsof +D 目录
# 递归查看/var/log下被打开的所有文件(会扫描子目录)
lsof +D /var/log/
# 不递归,只看目录本身被谁打开
lsof /var/log/
(5)组合条件:默认是OR(或),加-a变AND(且)
# 查看root用户启动的nginx进程打开的文件(两个条件同时满足)
lsof -a -u root -c nginx
四、查看网络连接和端口占用
这是lsof最常用的功能之一,格式:lsof -i [条件]
(1)查看所有网络连接
lsof -i
(2)查看TCP或UDP连接
lsof -i TCP
lsof -i UDP
(3)查看特定端口被谁占用(最常用)
# 查看80端口
lsof -i :80
# 查看多个端口
lsof -i :80,443,3306
# 查看端口范围
lsof -i :1-1024
输出示例:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 1247 root 6u IPv4 25832 0t0 TCP *:http (LISTEN)
nginx 1248 www-data 6u IPv4 25832 0t0 TCP *:http (LISTEN)
可以看到80端口(http服务)被nginx进程(PID 1247)占用,状态是LISTEN(监听中)。
之前在LuckVM德国法兰克福节点上排查Nginx启动失败问题,报错"Address already in use",直接 lsof -i :80 发现旧的nginx进程(PID 1247)还在监听80端口,kill 1247 后重新启动成功。
(4)查看连接到某个IP的连接
lsof -i @192.168.1.100
lsof -i @192.168.1.100:3306
(5)查看特定状态的连接
# 查看正在监听的端口(哪些服务在对外提供服务)
lsof -i -sTCP:LISTEN
# 查看已建立连接(当前正在通信的连接)
lsof -i -sTCP:ESTABLISHED
五、实战场景:恢复已删除但还被进程占用的文件
这是lsof非常实用的一个场景:文件被误删了,但进程还打开着这个文件(比如正在写日志的nginx、java应用),文件内容还在内存和磁盘里,可以从 /proc/PID/fd/ 恢复。
排查步骤:
# 1. 用lsof找被删除但仍被打开的文件
lsof | grep deleted
输出示例:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 1247 www-data 5w REG 8,1 2100000000 395128 /var/log/nginx/access.log (deleted)
可以看到 /var/log/nginx/access.log 被标记为 (deleted),但PID 1247的nginx进程仍然通过文件描述符5(5w表示写模式)打开着,文件大小约2.1G。
# 2. 通过 /proc/PID/fd/ 路径恢复文件
cp /proc/1247/fd/5 /var/log/nginx/access.log
之前在LuckVM泰国曼谷节点上就是这种情况:删除了2.1G的access.log但没通知nginx重载日志,df看磁盘使用率还是86%,通过lsof | grep deleted发现nginx还占着fd 5,从/proc复制恢复后,执行 nginx -s reopen 让nginx重新打开新日志文件,df显示使用率降到了53%。
注意:恢复后要记得让进程重新打开文件(比如重启服务或发reopen信号),否则进程还是会继续写旧的文件描述符,恢复的文件不会再有新内容写入。
六、其他实用场景
(1)目录无法卸载(device is busy)
umount /mnt/backup
# umount: /mnt/backup: target is busy.
# 查看哪个进程在使用这个挂载点
lsof +D /mnt/backup
之前在LuckVM澳大利亚悉尼入门款节点上umount数据盘时报device busy,lsof +D发现是一个vim进程还在编辑目录下的配置文件,退出vim后顺利卸载。
(2)查看某个进程打开了多少个文件描述符
# 统计PID 1247打开的fd数量
lsof -p 1247 | wc -l
# 查看当前系统每个进程的文件描述符数量限制
ulimit -n
# 默认通常是1024,高并发服务器需要调大这个值
(3)查看哪些进程连接到了某个IP的某个端口
lsof -i @10.0.0.5:3306
(4)只输出PID(方便kill进程)
# -t参数只输出PID列,不输出表头和其他信息
lsof -t -i :80
# 组合使用:杀掉占用80端口的所有进程
kill $(lsof -t -i :80)
kill -9 $(lsof -t -i :80)
(5)查看某个具体文件被谁打开
lsof /var/log/nginx/error.log
七、易错点和注意事项
- 普通用户执行lsof看不到其他用户的进程:查看系统进程需要root或sudo权限,否则只能看到自己启动的进程,信息不全不是lsof坏了。
- lsof +D 递归扫描大目录会很慢:因为要逐个扫描目录下所有文件,在大目录下(比如/有几十万文件)会等很久,优先用lsof -c/-p/-i过滤后再定位。
- deleted文件恢复有时效性:进程一旦关闭文件描述符(比如重启服务),文件数据就真的找不回来了,发现误删后第一时间不要急着重启服务,先用lsof确认。
- 不是所有deleted文件都需要恢复:有些临时文件是程序正常清理(比如自己删tmp目录下的文件),不需要处理。
- NAME列路径截断:路径太长时lsof默认截断显示,加
-w不显示警告,加+c 0不截断COMMAND列。 - lsof默认显示每个线程一行:多线程进程会输出多行(每个线程一行),看PID列相同就是同一个进程的不同线程。
- lsof可能没预装:最小化安装的系统(比如Debian最小版、CentOS最小版)可能没有lsof,需要先
apt install lsof或yum install lsof。
八、实用经验
- 端口占用排查是lsof最常用的场景,记住
lsof -i :端口号,比netstat -tlnp | grep 端口输出更直观,而且会显示具体进程名和用户。 - 磁盘空间满但du算出来的总大小比df显示的少很多,第一反应就是
lsof | grep deleted找已删除但被进程占用的大文件,这是最常见的原因。 - 想杀掉占用某端口的进程:
kill -9 $(lsof -t -i :端口)一行搞定,-t参数只输出PID,方便传给kill。 - lsof输出太多时用管道加grep过滤,或者组合多个条件加
-a取AND,减少无关信息。 - 恢复已删除文件后,一定要通知进程重新打开文件(比如nginx -s reopen、重启服务),否则进程继续写旧fd,磁盘空间还是不会真正释放。
- 高并发服务器调大 ulimit -n 之前,先用lsof统计实际需要多少个文件描述符,不要盲目调到几十万。
- 排查"谁在访问这个文件/目录",直接
lsof 文件路径或lsof +D 目录就能得到答案,比猜快得多。

浙公网安备 33010602011771号