Linux lsof看似简单,真正落地时却很容易踩坑。你遇到过这种情况吗:明明用 rm -rf 删了一个大日志文件,再用 df -h 一看,磁盘占用率纹丝不动。第一反应可能是“文件没删干净”,于是又删了一遍,还检查了回收站——但 Linux 没有回收站。问题到底出在哪?
这其实是 Linux 文件系统删除逻辑带来的一个经典陷阱:文件被删除后,空间并不会立即释放,只要还有进程在打开(open)这个文件,它的数据块就仍然被占用。 这篇文章会用最直白的方式解释背后的原理,再带着你一步步排查和解决。
一、为什么删了文件,空间没释放?
容易忽略的细节
先做一个思想实验:你有一个笔记本,你在某一页写满了字。现在你撕掉了这一页(相当于 rm 删除),但你的朋友还用手按着那一页在读。实际上,那一页的内容依然存在,只是你这边看不到目录里它的名字了。只有当朋友松手(关闭句柄),那一页才能真正被丢掉。
在 Linux 里,每个打开的文件由一个文件描述符(file descriptor,简称 fd)来引用。一个文件在磁盘上由两部分组成:
- 目录条目(directory entry):文件名、inode 编号等元数据
- inode + 数据块:文件的真实内容
当你执行 rm file.log,系统只是删除了目录条目,让这个文件名不可见。但 inode 和数据块并不会立即回收,因为 inode 上还有一个引用计数(link count)。只要进程的 fd 表里还指着这个 inode,引用计数就不为零,数据块就保留着。只有进程关闭 fd(或者进程结束),引用计数降为 0,系统才会释放磁盘空间。
这就是 “已删除但仍在被占用” 的本质。
Linux lsof:二、使用 lsof 查找罪魁祸首
lsof(List Open Files)是 Linux 上用来列出当前系统所有打开文件的工具。它能告诉我们哪个进程、哪个文件描述符正在占用什么文件。即便文件的目录条目已经删除,只要进程还开着它,lsof 就能显示出来。
2.1 安装 lsof
大多数发行版预装了 lsof。如果没装:
# Debian/Ubuntu
sudo apt install lsof
# CentOS/RHEL
sudo yum install lsof
2.2 找到被删除但未被释放的文件
我们主要关注文件状态标记为 (deleted) 的行。执行:
lsof +L1
参数 +L1 的意思是只显示 link count 小于 1 的文件(即已被删除但仍有进程打开的文件)。输出类似:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
rsyslogd 1234 syslog 7w REG 8,1 12345678 0 123456 /var/log/syslog (deleted)
nginx 2345 www 13w REG 8,1 98765432 0 234567 /var/log/access.log (deleted)
关键信息:
- COMMAND & PID:哪个进程在搞事
- FD:文件描述符类型(w 表示写,r 表示读,u 表示读写)
- SIZE/OFF:当前文件大小(字节)
- NLINK:硬链接数,这里是 0,说明目录条目已删除
- NAME:显示文件名及 (deleted) 标记
如果 +L1 选项在你的系统上不支持(有些旧版本 lsof 没有),可以用更通用的方式:
lsof | grep '(deleted)'
2.3 确认占用的空间大小
光知道有 deleted 文件还不够,要确认它到底占了多少空间。可以用 /proc/PID/fd 来查看每个打开文件的具体大小。比如上例中 PID=1234,fd 是 7(写方向):
ls -la /proc/1234/fd/7
返回类似:
l-wx------ 1 syslog syslog 0 Jun 10 10:00 /proc/1234/fd/7 -> /var/log/syslog (deleted)
大小显示为 0 是因为符号链接本身大小为 0,但我们可以通过 lsof 的 SIZE/OFF 列或者直接 du:
sudo du -h /proc/1234/fd/7
不过更稳妥的方式是查看该文件描述符的真实大小——用 fuser 或者直接看 /proc/PID/fdinfo。简单一点:我们可以直接重启对应进程或者用下面“释放”的方法。
关联教程:此处可内链到“Linux lsof部署与验证”内容。
想继续深入:此处可内链到“Linux lsof优化清单”文章。
三、释放磁盘空间的三种方法
找到问题进程后,你有三个选择,按风险从低到高排列:
3.1 方法一:重启进程(推荐)
大多数服务型进程(如 nginx、rsyslogd)在重启时会重新打开日志文件,旧的文件描述符会被关闭,空间自然释放。注意不要用 kill -9 粗暴杀掉,应该用服务的正常重启命令:
sudo systemctl restart rsyslog
# 或者
sudo service rsyslog restart
重启后再次 lsof +L1 确认相关行消失,再用 df -h 验证空间是否恢复。
3.2 方法二:向进程发送 SIGHUP 信号
如果不想完全中断服务,可以给进程发送 SIGHUP 信号,很多守护进程(如 nginx、syslogd)收到这个信号后会重新打开日志文件,而不会中断当前连接:
sudo kill -HUP 1234
注意:不是所有进程都支持 SIGHUP 重新加载。大部分 Web 服务器和日志守护进程支持,但像 tail -f 这样的用户进程就不支持。
3.3 方法三:清空文件内容而非删除(预防方案)
这是从源头上避免问题的做法:不要用 rm 删除正在被写入的日志文件,而是用重定向清空文件内容:
# 清空 access.log 内容,不影响 nginx 进程
> /var/log/nginx/access.log
这样文件 inode 没有被删除,进程的 fd 依然指向同一个 inode,但文件内容变 0,空间立刻释放。这是生产环境中日志轮转(logrotate)的常规做法。
补充参考:此处可内链到“Linux lsof故障排查实例”。
四、验证与回滚——Linux lsof
4.1 验证空间是否释放
df -h | grep '/var' # 看挂载点使用率
lsof +L1 # 确认 deleted 条目消失
如果重启后空间依然没变,请检查:
- lsof 显示的新行是否来自另一个进程?同一个文件可能被多个进程打开。
- 是否有文件系统的 inode 被用完(
df -i查看)? - 是否有磁盘快照或备份任务在读取文件?
4.2 回滚方案
如果重启进程导致服务异常(比如某些依赖状态丢失),你可以:
- 立即查看服务状态:
systemctl status nginx - 如果服务启动失败,检查配置文件语法:
nginx -t - 回滚到上一个配置快照(如果提前备份了)
- 或者用
systemctl revert恢复(前提是 systemd 支持)
注意:回滚并不会把已释放的空间“还回去”,空间释放是不可逆的。所以如果你误删了文件,但进程还没关——那么文件内容其实还在磁盘上,可以通过 cp /proc/PID/fd/7 /tmp/recovered.log 恢复出来。这是另一个话题了。
进阶阅读:此处可内链到“Linux lsof性能优化”指南。
五、如何预防此类问题
验证与回滚
知道了原理,就可以在设计日志管理时避开陷阱:
- 使用 logrotate:配置
copytruncate选项,它会先拷贝再清空,而不是直接删除文件。 - 避免 direct rm:生产环境中永远不要手动 rm 正在写入的日志文件。
- 监控 FD 泄漏:定期检查
lsof +L1或使用监控工具如 Prometheus + node_exporter 的process_open_fds指标。 - 了解进程行为:在部署新服务前,测试一下它是否会在日志轮转后正确重开 fd。
相关阅读:此处可内链到“Linux lsof常见问题”专题。
六、总结
故障定位思路
“已删除但未释放”的本质是:rm 只删了文件名,进程的 fd 还在。用 lsof +L1 可以秒级定位那些幽灵文件,然后通过重启进程或发送 HUP 信号释放空间。整个过程不需要你理解内核代码,只需要记住“文件句柄”这个核心概念。
下次再遇到磁盘空间告警,别急着扩容,先跑一遍 lsof 看看是不是有人在偷吃空间。按这个顺序复查,Linux lsof遇到异常时也更容易定位。
延伸阅读
