印度机房用 lsof 查占用,进程号对了文件却不对
印度孟买机房的备份机,Ubuntu 22.04,48GB 内存,/data 分区约 12TB,上面跑着三个 rsync 拉取任务和一个本地日志归档脚本。某天 df 显示 /data 用了 91%,但 du -sh 把可见目录加完只有 7.2TB,差出将近 4TB。ncdu 扫过一遍,结论和 du 一致,说明有已删除但仍被打开的文件占着 inode。
常规做法是 lsof +L1 找 deleted 文件。第一次跑出来两百多行,我按 SIZE 排序取最大的一条,拿到 PID,kill 掉,df 只降了 30GB。问题在于 lsof 的 SIZE 列对稀疏文件和被 truncate 过的文件会误导,而且同一进程可能持有多个 fd。
换了个做法,直接按 inode 上的实际块数算,而不是看 lsof 报的 size:
for p in /proc/[0-9]*; do
pid=${p#/proc/}
for fd in $p/fd/*; do
t=$(readlink -f "$fd" 2>/dev/null) || continue
case "$t" in *deleted*) ;; *) continue;; esac
blocks=$(stat -Lc %b "$fd" 2>/dev/null) || continue
echo "$((blocks*512)) $pid $t"
done
done | sort -rn | head -20
这段在 12TB、fd 数量几千的规模下跑十几秒。按块数排出来的第一名才是真正吃盘的那个,是一个日志归档脚本 fork 出来的子进程,父进程早就退出,子进程继承 fd 后一直挂着,lsof 按 size 排根本排不到前面。kill 之后 df 立刻回到 68%。
为什么不用 lsof:它要遍历 /proc 再格式化输出,字段语义在跨文件系统时不一致,stat 的 %b 直接读 inode 的块计数,和 df 的统计口径对得上。代价是每个 fd 一次 stat 系统调用,fd 上万时会慢,而且需要 root 才能读别的进程的 fd。
失效的地方:容器里 /proc 是命名空间隔离的,宿主机上跑这段看不到容器内的 fd,得进对应 namespace;NFS 上的 deleted 文件块数不一定实时反映;另外如果进程在遍历过程中退出,readlink 会报错,脚本里已经用 2>/dev/null 吞掉,但会漏掉那一瞬间的 fd。我第一次跑没加 -L,stat 跟着符号链接走到了目标路径,结果全是 0。
还有个坑:kill 之前确认进程不是正在写的任务,我那次差点杀掉 rsync 的守护子进程。现在会先看 /proc/PID/cmdline 和 cwd 再动手。
0 条评论
还没有评论,写下第一条。