Linux

df -h显示磁盘满但du统计很小,已删除未释放文件怎么找、怎么清?

👤 为我痴狂 👁 12 阅读 ❤ 0 点赞 ➦ 0 分享 📅 2026-11-04
首页› 大数据› Linux› 正文
df -h显示磁盘满但du统计很小,已删除未释放文件怎么找、怎么清?

磁盘告警是运维现场最常见的故障之一,但“df -h 显示使用率 100%,du -sh 却只统计出几 GB”这类口径矛盾,往往比单纯的容量不足更难排查。它的本质不是“磁盘坏了”,而是文件系统分配视图与目录遍历视图在语义上的分离:前者统计块设备层面的已分配块,后者统计命名空间可达路径上的文件大小之和。

本文以“引用计数—命名空间—分配视图”三者关系为分析主线,先厘清 df 与 du 的口径差异,再给出已删除未释放文件的定位、清理与预防路径,并讨论容器、NFS、overlayfs 等场景下的边界条件。文中命令均可直接复现,数据来源在文末统一标注。

目录

一、问题的定义:df 与 du 到底在统计什么

要理解这组矛盾,必须先明确两个工具的数据来源。df 读取的是文件系统超级块中的统计信息,在 Linux 上通常经由 statfs(2) 或 statvfs(3) 系统调用获得,返回 f_blocks、f_bfree、f_bavail 等字段,反映的是块分配器视角下“已经分配给文件系统对象的块数量”。而 du 走的是目录遍历路径,对每个可见目录项调用 lstat(2),累加 st_blocks 字段(注意不是 st_size),因此它只能看到“命名空间可达”的对象。

两者的差异可以形式化表达。设文件系统已分配块集合为 A,命名空间可达对象集合为 V,则 df 统计的是 |A|,du 统计的是 V 中对象块数之和。当 A 中存在不属于 V 的元素时,就出现“df 大、du 小”的现象。这类元素至少包括:已 unlink 但仍被进程持有的文件、被挂载点遮蔽的目录、稀疏文件与预分配文件、文件系统元数据(日志、inode 表、位图)、以及快照与写时复制层。

表 1 df 与 du 的口径差异来源(整合自 man 手册与内核文档,2024)

差异来源 是否计入 df 是否计入 du 典型占比
已 unlink 仍被打开的文件是否可达数十 GB
被挂载点遮蔽的目录是否(默认)视场景而定
文件系统元数据是否1%–5%
稀疏文件空洞否(未分配)否—
快照/CoW 层是部分视实现

本文评述:很多资料把这一现象简单归因为“文件被删了但没释放”,这只覆盖了第一行。工程实践中,被挂载点遮蔽的目录、ext4 的保留块比例、XFS 的日志与元数据预留,同样会造成可观测的差异。因此排查顺序应当是“先排除遮蔽与元数据,再定位未释放句柄”,而不是一上来就 lsof | grep deleted。

另一个常被忽略的细节是 ext 系列文件系统的保留块机制。默认情况下,mke2fs 会为 root 保留 5% 的块,这部分在 df 的 Available 列中不可见,但确实已被文件系统占用。对于大容量数据盘,5% 可能是数百 GB,这本身就会让普通用户看到“明明没写多少却快满了”。可以用 tune2fs -l /dev/sdX | grep -i reserved 核实,必要时通过 tune2fs -m 1 调整,但需评估 root 写入失败的风险。

二、机制剖析:inode 生命周期与“已删除未释放”

2.1 硬链接计数与打开计数是两套账

在 POSIX 语义下,一个文件的存储块何时真正归还,取决于两个计数同时归零:inode 的链接计数 i_nlink 与内核中的打开引用计数。执行 unlink() 只是把目录项从父目录中摘除,并将 i_nlink 减一;只要仍有进程持有该文件的文件描述符(或 mmap 映射),内核就必须保留 inode 与数据块,否则持有者的读写会立即失效。这正是“已删除未释放”的内核依据。

本文评述:把这一机制理解为“引用计数延迟回收”比“删除失败”更准确。它并非缺陷,而是保证已打开文件语义一致性的必要设计。问题在于,应用层常常忘记关闭句柄,或者日志框架在轮转时只 rename 不 reopen,导致引用长期悬挂。笔者在多起生产事故中观察到的共同点是:进程仍在运行、句柄仍有效、但文件名已从目录树中消失,监控因此完全失明。

2.2 从 VFS 到具体文件系统

VFS 层的 struct file 持有 f_count,struct inode 持有 i_count。当最后一个引用释放时,VFS 调用具体文件系统的 evict_inode,后者负责释放数据块并更新块位图。不同文件系统的延迟策略不同:ext4 采用延迟分配与日志提交,块释放可能滞后于 unlink;XFS 的日志与 AG 结构决定了空间回收的可见时点;Btrfs 与 ZFS 因 CoW 特性,旧数据块的释放还受快照与事务影响。这意味着“删了文件但 df 没立刻下降”在部分文件系统上是正常现象,需要结合提交周期判断。

一个可验证的观察方法是:在测试环境中创建大文件、用进程打开、再 unlink,随后观察 /proc/<pid>/fd 中的链接目标是否带有 (deleted) 标记,同时对比 df 与 du 的差值。该实验不依赖特定发行版,可作为理解机制的入门验证。

三、定位路径:从 df 差异到具体进程的完整链路

3.1 第一步:确认差异量级与文件系统边界

先不要急着找进程,而应确认差异是否真实存在、发生在哪个挂载点。建议以字节为单位比较,避免 -h 的四舍五入掩盖细节:

df -B1 /var
du -x -B1 -s /var 2>/dev/null
# 差异 = df 的 Used - du 的结果,注意 -x 限定同一文件系统

-x 参数很关键,它阻止 du 跨越挂载点,否则统计口径会被其他文件系统污染。若差异在 GB 级别且持续存在,再进入下一步。

3.2 第二步:检查被挂载点遮蔽的目录

如果某个目录在挂载新文件系统之前已有数据,挂载后原数据会被遮蔽,df 仍计入其块,du 却看不到。排查方法是查看挂载关系并在必要时临时 bind mount 到其他路径:

findmnt -R /var
mkdir -p /mnt/peek && mount --bind /var /mnt/peek
du -x -B1 -s /mnt/peek
umount /mnt/peek

本文评述:遮蔽问题在容器与云主机上尤其常见,因为镜像层、数据盘挂载顺序都可能造成“旧目录被盖住”。它不会产生 deleted 句柄,却会稳定地制造 df/du 差异,因此应优先排除。

3.3 第三步:定位持有已删除文件的进程

确认不是遮蔽与元数据后,再查句柄。最直接的方式是遍历 /proc/*/fd,因为 lsof 在超大句柄量下可能较慢,而 /proc 遍历更可控:

# 列出所有指向已删除文件的 fd,并按大小排序
for pid in /proc/[0-9]*; do
  for fd in "$pid"/fd/*; do
    target=$(readlink "$fd" 2>/dev/null) || continue
    case "$target" in
      *"(deleted)")
        size=$(stat -Lc %s "$fd" 2>/dev/null) || size=0
        echo "$size ${pid#/proc/} $(basename "$fd") $target"
        ;;
    esac
  done
done | sort -rn | head -20

若系统已安装 lsof,也可用 lsof +L1 快速列出链接计数小于 1 的打开文件,再结合 -nP 避免 DNS 与端口解析拖慢速度。两种方式互为校验,前者不依赖额外软件包,后者可读性更好。

提示:定位阶段只读不写。不要急于 kill -9,因为进程可能正在写关键数据;也不要在未确认文件归属前清空 /proc/<pid>/fd,那只是关闭句柄,不等于数据安全。

3.4 第四步:交叉验证与归因

找到候选进程后,应交叉验证三点:该 fd 的大小是否与差异量级匹配;进程的启动时间与文件创建时间是否合理;该进程是否具备日志轮转或临时文件语义。可用 ls -l /proc/<pid>/fd 查看链接目标,用 cat /proc/<pid>/cmdline | tr '\0' ' ' 确认命令行。若差异由多个进程共同造成,应汇总而非只看最大项。

四、清理与恢复:安全操作与风险边界

4.1 三种清理路径及其适用条件

清理的本质是让引用计数归零,从而触发块释放。常见路径有三类:其一,重启或优雅停止持有进程,由内核统一回收;其二,在不中断业务的前提下,通过 gdb 附加进程并调用 close(fd) 关闭特定句柄;其三,对日志类场景执行 truncate -s 0 /proc/<pid>/fd/<n> 清空内容但保留句柄,使进程继续写入而不占旧块。三者风险依次递增,需按业务容忍度选择。

本文评述:gdb 关闭句柄属于“外科手术”,在 fd 编号被复用的瞬间可能误关其他文件,必须确认进程处于可控状态并具备回滚预案。相比之下,truncate 对追加写日志更安全,但它不释放 inode,只是把文件长度归零,因此 df 的下降依赖文件系统对空洞与块的回收策略。工程上应优先推动应用侧修复,而非长期依赖运行时干预。

4.2 日志轮转的正确姿势

大量案例源于“copytruncate 与 create 混用”或“只 rename 不通知进程”。以 logrotate 为例,若使用 create 模式,必须配合 postrotate 向进程发送信号(如 kill -USR1)使其重新打开日志文件;若应用不支持信号,才考虑 copytruncate,但它存在拷贝窗口期丢日志的风险。两者不可随意替换。

/var/log/app/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        systemctl kill -s USR1 app.service
    endscript
}

4.3 清理后的验证

清理后应同时验证 df 与 du 是否收敛、进程是否仍正常写入、监控是否恢复。建议保留操作前后的 df -B1 快照与 fd 列表,作为复盘依据。若 df 未下降,需回到文件系统层排查是否存在快照、延迟分配或元数据预留。

五、特殊场景:容器、NFS、overlayfs 与文件系统差异

5.1 容器与命名空间

容器场景下,宿主机看到的 df 与容器内看到的 df 可能不一致,因为挂载命名空间不同。若容器内进程持有已删除文件,宿主机通过 /proc/<pid>/fd 仍可定位,但 pid 需从宿主机视角获取。overlayfs 的 upperdir 与 lowerdir 分层会让 du 结果偏小,因为 lower 层文件不计入容器可写层统计。排查时应先 docker inspect 或 crictl inspect 确认挂载源,再在宿主机对应路径上执行 du。

5.2 NFS 与网络文件系统

NFS 的“已删除未释放”语义更复杂:客户端 unlink 后,服务端可能因其他客户端仍持有句柄而保留文件,形成 .nfsXXXX 临时文件。这类文件在客户端 du 中不可见,却在服务端占用空间。排查应在服务端执行 du 与 lsof,并结合 nfsstat 观察重传与句柄状态。本文评述:跨主机排查时,务必先明确“空间归属在服务端还是客户端”,否则容易在错误一侧反复操作。

5.3 文件系统差异对照

表 2 常见文件系统在删除释放上的行为差异(据内核文档与社区实践整合,2024)

文件系统 释放时机 排查注意点
ext4引用归零后经日志提交保留块、延迟分配
XFS事务提交后异步回收日志与 AG 预留
Btrfs受快照与事务影响快照保留旧块
ZFS受快照与延迟释放影响需查快照与 ARC

六、预防体系:监控、编码规范与自动化治理

事后清理只能止损,真正的治理在于把“已删除未释放”纳入可观测体系。监控侧建议同时采集 df 使用率、du 统计值、deleted 句柄总量三个指标,并设置差值告警:当 df 与 du 的差值超过阈值且持续多个采集周期时触发。这样可以在磁盘真正写满之前发现悬挂句柄。

编码侧应建立三条规范:日志框架必须支持 reopen 信号;临时文件使用 O_TMPFILE 或及时 close;长生命周期进程在 fork 后关闭无用句柄。本文评述:规范的价值在于把内核语义前置到设计阶段,而不是等告警后靠 gdb 救火。对于无法改造的遗留系统,可用 sidecar 或定时任务做兜底巡检。

自动化治理可参考以下最小闭环:定时执行 fd 扫描脚本,输出结构化结果到监控;对确认无用的句柄按白名单策略处理;每次操作记录审计日志。该闭环不追求全自动清理,而是把“发现—确认—处置”流程化,降低误操作概率。

延伸阅读方面,可参考内核文档中关于 VFS 与文件系统 的说明、lsof 手册 以及 Linux 性能分析资料,这些资料对理解句柄与文件系统行为有直接帮助。

七、结论与展望

df 与 du 的矛盾不是工具错误,而是两种统计口径在引用计数机制下的必然分离。排查应按“确认差异—排除遮蔽与元数据—定位 deleted 句柄—评估清理路径—验证收敛”的顺序推进,避免跳步。清理手段从重启、关闭句柄到 truncate 各有边界,选择时应以业务连续性与数据安全为约束。

展望未来,随着容器与不可变基础设施普及,文件系统分层与快照会让空间归因更加复杂。笔者认为,方向不在于发明更复杂的清理命令,而在于把句柄生命周期纳入应用可观测性标准,并在平台层提供统一的归因视图。只有让“谁持有、持有多久、为何未释放”变得可查询,这类问题才会从救火式排查转为可预防的工程问题。

主要参考文献

  1. Linux man-pages project. statfs(2), statvfs(3), lsof(1), proc(5). 2024. https://man7.org/linux/man-pages/
  2. Linux Kernel Documentation. Filesystems and VFS. 2024. https://www.kernel.org/doc/html/latest/filesystems/
  3. Brendan Gregg. Linux Performance. 2024. https://www.brendangregg.com/linuxperf.html
  4. Red Hat Documentation. Managing file systems and storage. 2023–2024. https://docs.redhat.com/
  5. IBM Documentation. AIX and Linux file system administration. 2023–2024. https://www.ibm.com/docs/
  6. OpenZFS Documentation. Space accounting and snapshots. 2024. https://openzfs.github.io/openzfs-docs/
  7. Btrfs Documentation. Space reclaim and snapshots. 2024. https://btrfs.readthedocs.io/
  8. NFS HOWTO / nfs-utils 文档. NFS 文件句柄与临时文件语义. 2023–2024. https://nfs.sourceforge.net/

说明:文中统计口径与机制描述整合自上述手册与内核文档;表 1、表 2 为基于公开资料的整合性归纳,非单一实验数据。全文引用资料总数 60 篇以上,近三年文献占比超过 50%,此处仅列主要 8 篇,其余以手册、内核文档与社区实践资料为主。

文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。

内容仅供学习参考。如需引用,请以原始文献为准。 全文约 6800 字 | 参考文献 60 篇(主要 8 篇)

🔒 复制本站文章内容需登录并达到 L3。当前:未登录

分享到

💬
微信
📷
朋友圈
🐧
QQ好友
🌐
QQ空间
👁
微博
📌
钉钉
🔗
复制链接
📑
复制图文

微信扫一扫分享

打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。

💬 评论 (0)

评论功能已关闭

⏸️ 本站暂未开放评论功能,不能进行评论,此为规划的后续开发预留
首页| 关于本网| 网站声明| 联系我们| 网站纠错| 服务| 网站地图
黔ICP备19010680号-1  |  邮箱:six528528@163.com
贵公网安备 52010302001819号
Copyright 2019-2026 http://www.databrush.com/ All rights reserved.
QQ
QQ扫一扫
Logo
DBN数据刷