最近有一位刚入行 DevOps 的同学跑来问我一个诡异的现象:生产服务器磁盘告警,他反手一个 rm 删除了 5GB 的大日志,结果 df -h 一看,空间占用纹丝不动。
这其实是 Linux 系统中 "文件删除机制" 与 "进程引用计数" 之间的一个经典博弈。
1. 解耦的设计:文件名并不等于文件
在 Linux 的虚拟文件系统(VFS)中,一个文件被拆分为两个核心维度:
-
dentry (Directory Entry): 我们在 Shell 里看到的"文件名",本质上只是一个指向 Inode 的指针。
-
Inode: 真正的文件元数据(元信息),包含权限、属主以及指向磁盘 Data Blocks 的地址。
当你执行 rm 命令时,内核调用的是 unlink()。这个操作仅仅是删除了 dentry,并将 Inode 的 i_link(硬链接数) 减 1。
2. 关键变量:i_count 引用计数
除了 i_link,内核还维护着一个关键指标:i_count(进程引用计数)。
在上述案例中,Web Server 进程依然打开着该日志文件,持有其文件描述符(File Descriptor)。此时:
-
i_link= 0(文件名已消失) -
i_count> 0(进程句柄未关闭)
Linux 内核的准则: 只有当 i_link 和 i_count 同时为 0 时,指向磁盘 Data Blocks 的指针才会被释放,空间才会被标记为"空闲"。
3. df 与 du 的"情报差"
这就是为什么你会看到 df 和 du 的数据冲突:
-
du(Disk Usage): 基于文件树扫描。因为 dentry 没了,它找不到这个文件,所以报告空间已释放。 -
df(Disk Free): 基于超级块(Superblock)的磁盘块分配表。因为i_count > 0,内核依然认为这些块是 Active 状态,所以报告空间依然占满。
4. 专家级处理方案
在生产环境中,严禁对正在被进程写入的日志执行 rm。
- 优雅方案(在线清空):
使用 `true > /var/log/access.log`{index-in-node="15" path-to-node="19,0,0"} 或 `truncate -s 0`{index-in-node="44" path-to-node="19,0,0"}。\
这会触发内核将 Data Blocks 直接置空,而无需改变 Inode 状态,进程可以无感地继续写入,且空间立即释放。
- 事后补救(寻找隐形文件): 如果已经删了文件,可以使用以下命令定位这些"幽灵文件":
[Bash]{ngcontent-ng-c3649887988=""}
lsof +L1 # 查找 link count 小于 1 且仍被打开的文件
找到对应的 PID 后,通过 `> /proc/[PID]/fd/[FD]`{index-in-node="15" path-to-node="19,1,2"} 强制截断,或者优雅地 `reload`{index-in-node="48" path-to-node="19,1,2"} 该服务。
{#section path-to-node="21"}
{#section-1 path-to-node="21"}
LeisureLinux 禅悟
在 Linux 的世界里,看到的(文件名)不一定是真实的,看不到的(Inode/FD)才是系统运行的基石。理解了 引用计数,你才算真正跨过了运维开发的初阶门槛。