你可能遇到过这样的场景:用 rm 删除了一个几十 GB 的日志文件,df -h 一看,磁盘占用率纹丝未动。是 rm 没删掉吗?再执行一次 ls,文件确实已经不在了。这背后隐藏着一个 Linux 文件系统的工作机制——删除文件与释放磁盘空间,并不是一回事。
rm 的真正工作:解除链接
在 Linux 中,文件由两部分组成:目录项(文件名到 inode 的映射)和 inode(存储文件元数据及数据块指针)。rm 命令所做的,仅仅是删除目录项,并将 inode 中的链接计数减 1。只有当链接计数变为 0,且没有进程打开该文件时,系统才会真正回收数据块。
如果文件正在被某个进程打开(例如正在写入的日志、数据库文件),即便你执行了 rm,进程的文件描述符依然指向该 inode,数据块不会被释放。从文件系统的角度看,这个文件已经“不可见”了,但其占用的磁盘空间却依然被标记为“使用中”。这就是“幽灵文件”的来源。
如何找到这些幽灵文件?
使用 lsof(list open files)命令可以轻松定位它们:
lsof | grep deleted
输出中会列出所有已被删除但仍被进程打开的文件,典型格式如下:
java 12345 root 14w REG 8,1 1048576 123456 /var/log/app.log (deleted)
重点关注 PID(进程号)和文件路径。(deleted) 标记清楚地表明:这个文件已经被 rm,但进程还在占用它,磁盘空间尚未释放。
释放空间:两种稳妥的方法
方法一:重启进程
最直接的方式是重启占用该文件的进程。进程终止时,内核会自动关闭所有打开的文件描述符,对应的 inode 链接计数归零,文件数据块被回收,磁盘空间即刻释放。但在生产环境中,重启服务可能影响业务,需要谨慎操作。
方法二:通过 /proc/PID/fd/ 清空文件内容
不需要重启进程,也可以安全地释放磁盘空间。每个进程的描述符信息都存放在 /proc/PID/fd/ 目录下。找到幽灵文件的描述符编号(例如 14),然后清空它:
# 先找到进程PID和文件描述符FDlsof | grep deleted# 假设PID=12345,FD=14> /proc/12345/fd/14
这个操作会将文件内容截断为 0,磁盘空间立即释放,而且进程仍然可以继续向同一个文件描述符写入(写入会从文件开头重新开始)。这对于长期运行的服务特别有用,比如 Web 服务器、数据库或守护进程的日志文件。
注意:使用 > 重定向清空文件时,要确保描述符的打开模式允许写入(通常是 w 或 u)。只读描述符(r)不能这样操作,这时只能选择重启进程。
一个常见的误区:清理正在写入的日志文件
很多管理员习惯用 rm 删除旧的日志文件,再 touch 新建一个同名文件,或者直接 rm 后等待服务重新创建。这会导致两个问题:
- 如果服务一直打开着旧文件句柄,
rm 后空间不释放。 - 服务重新创建新文件时,inode 已经改变,可能还需要发送信号让服务重新打开日志。
正确的做法是:直接清空文件内容,而不是删除文件本身。
这样做的好处是:文件 inode 保持不变,服务进程的文件描述符依然有效,写入位置从 0 开始,磁盘空间被立即释放,服务无需任何配置更改或重启。
预防与总结
- 定期使用
lsof | grep deleted 检查是否有长期残留的幽灵文件,尤其是运行数月未重启的服务。 - 对于轮转的日志文件,优先使用
logrotate 等工具,它会采用 copytruncate 或 postrotate 脚本正确清理。 - 理解
rm 的本质——它只是一个链接删除操作,而不是“销毁数据”的最终动作。释放磁盘空间,需要确保没有任何进程再持有该文件的引用。
下次 df 发现空间异常,先别急着删更多文件。运行 lsof | grep deleted,很可能只需要一条重定向命令,就能轻松找回那几十 GB 的空间。