运维千问——为什么磁盘文件删除了但空间没释放?

2023-09-21 16:46:02 浏览数 (1)

萧十一入职公司2年有余,便成为了一位很热心的实习生导师,新来的实习生与其说他是幸运的,不如说他是“幸运牌方便面“的,每次总被一个问题搞到深夜,饭也来不及吃,只能以面充鸡。

偶有遇到问题求教十一导师,十一导师语重心长且耐心地说:

“重八呀,这个问题嘛,你可以在浏览器输入三达不溜点摆渡点卡姆,一回车,再输入你的问题,搞腚!“

朱重八小实习生只能无语凝噎。。。。。。

天渐渐暗了下来,萧十一看着眼前的这个实习生,不由得心生感慨。用他那呆滞的眼神看着窗外,此时不由得想起他自己实习的那天。。。。。。

那天,是他来公司第100天,坐着585路公交车,半路上公交还抛锚着火了,着实有点不顺。换车之后,他就在想一个绕不开的终身的大事,运维到底是干什么的?能不能干一辈子?网上充斥着五花八门的,让人意乱情迷啊的答案。搜了一段时间实在顶不住早起的困意,依偎在玻璃窗边睡着了。梦里他梦到领导让他周末不用来加班了,回去休息休息。。。。

叮铃铃。。。。。。还没到公司,他就收到领导的紧急电话。

“喂,十一,你咋又迟到,快点,线上服务器挂了,抓紧时间!“

“喂,领导,公交车抛锚了,我马上就到。“,另外一个运维老兵今天刚好结婚请假,对十一来说是莫大的双重打击啊。

不由分说,萧十一急匆匆挂了电话,三步并作两步,赶到工位,打开电脑,查看监控,果然。有一批机器磁盘飙红。

“一定是磁盘空间满了!!!“

萧十一三下五除二,写了一个批量脚本检测了下,正如所料,有个服务的日志没做轮询,磁盘给撑爆了。

“咋整?“,十一心里暗想。

“rm -rf 吧,先救急!“

他们的项目其实并不大,十万的用户量,那时候的运维审批流程也没那么严格,再加上领导让救急,急火攻心,说干就干。

快速写了一个批量删除脚本,一把跑完,命令没报错,十几台机器运行完没报错,真是漂亮,自我窃喜。

可是,一查监控,磁盘指标还是飙红。于是又跑了一遍批量脚本检测了下,我乖,空间并没减少,咋回事?

按照之前他的导师给他的心路大法,就去摆渡了下,果然有这问题,太棒了!

页面赫然写着:

文件被其他程序或进程占用。即使文件被删除,如果其仍被其他进程或程序占用,则磁盘空间将无法释放。可以通过终端命令“lsof | grep deleted”查找所有被删除但仍被占用的文件,并杀掉该进程或关闭该程序来释放该文件占用的磁盘空间。

果然一试上述方法真的解决了。

这件事过后,萧十一心想,作为一名读过研的人,那就得有刨根问底精神,rm -rf删除的是什么,为什么删除了还能被进程占用?lsof是个什么高级玩意?带着问题,他就像写论文做实验一样,开始兴奋滴深究起来。

1. rm -rf删除的是什么?

通过对源码分析https://github.com/coreutils/coreutils/blob/master/src/rm.c,我们也能略知一二。

在rm.c源文件中包含了main函数,这个文件主要干了这么几件事:

a. 主函数使用getopt来处理rm命令行传入的参数。主要的选项包括:

  • -r 递归删除
  • -f 强制删除,不询问
  • -i 删除前询问确认
  • --preserve-root 避免删除根目录
  • -v 详细输出

如果没有提供要删除的文件名,会检查并报missing operand错误。

如果使用了--preserve-root选项,会设置一些变量比如x.root_dev_ino。

统计传入的要删除的文件名数量。

如果使用了-i选项,在删除多文件前会提示用户确认。

主要逻辑在rm()函数中,调用它来删除文件。

检查rm()的返回状态,并相应地以成功或失败退出程序。

b. rm()函数它有两个参数:

  • file: 要删除的文件路径数组
  • x: 删除的选项

  • 初始化状态
  • 可选地通过FTS遍历文件
  • 对每个条目调用rm_fts()
  • 返回最终状态

c. rm_fts函数

  • 处理FTS遍历中的每个条目
  • 根据类型删除目录
  • 跳过错误
  • 返回状态

d. excise函数

  • 根据is_dir确定unlinkat的flag
  • 调用unlinkat删除条目
  • 处理任何错误
  • 返回状态

e.unlinkat函数

  • 调用来删除文件/目录
  • 传递FTS信息,如文件描述符和路径
  • 尝试删除,返回0或-1
  • 返回值表示成功/失败

根据unlinkat文档介绍:

unlink() deletes a name from the filesystem. If that name was the last link to a file and no processes have the file open, the file is deleted and the space it was using is made available for reuse.If the name was the last link to a file but any processes still have the file open, the file will remain in existence until the last file descriptor referring to it is closed.If the name referred to a symbolic link, the link is removed.If the name referred to a socket, FIFO, or device, the name for it is removed but processes which have the object open may continue to use it.

翻译如下:Unlink()会从文件系统中删除一个文件名称。如果该文件名称是指向某个文件的最后一个链接,并且没有进程打开该文件,那么该文件将被删除,它所使用的空间将可供重用。如果该文件名称是指向某个文件的最后一个链接,但仍有进程打开该文件,那么该文件将一直存在,直到关闭最后一个引用该文件的文件描述符。如果该名称引用了一个符号链接,则该链接将被删除。如果该名称引用了一个套接字、 FIFO 或设备,则该名称将被删除,但已打开该对象。

所以到这里就真相大白了,如果还想继续深入探究,还可以具体看下unlink函数的实现过程。

2. lsof是什么?

  • 作用 lsof 是一个在Unix和Unix-like操作系统上的命令行工具,用于列出当前系统上打开的文件和网络连接的信息。它的名字代表"list open files",但实际上它可以列出文件、目录、设备、网络套接字等的信息。
  • 原理 lsof 的原理是通过读取系统内核的相关数据结构来获取当前系统中打开的文件和网络连接的信息。它利用了Unix和类Unix操作系统内核提供的 /proc 文件系统(或类似的机制)以及其他系统调用来实现这个功能。
  • 如何替代 有些时候,由于系统限制,无法安装这个命令,可以自己使用其他命令写脚本实现,一个例子:
代码语言:shell复制
#!/bin/bash

# 检查参数数量
if [ $# -ne 1 ]; then
    echo "用法: $0 <目标文件夹路径>"
    exit 1
fi

# 获取目标文件夹路径
target_folder=$1

# 使用find命令查找目标文件夹下的所有进程占用的文件
find_result=$(find "$target_folder" -type f)

# 循环遍历每个找到的文件
while read -r file; do
    # 使用fuser命令查找占用该文件的进程
    pid=$(fuser "$file" 2>/dev/null)

    # 如果找到了占用进程,则输出相应信息
    if [ -n "$pid" ]; then
        echo "文件 $file 被进程 $pid 占用"
    fi
done <<< "$find_result"

执行结果参考如下:

代码语言:shell复制
[root@VM-1210-18-xxx xxxx]# sh fake_lsof.sh .
文件 ./fake_lsof.sh 被进程  3657653 占用

一番探究之后,萧十一欣喜若狂,如鱼得水。不禁感叹道,”做技术其实就是做学问啊!“

“老师你说的啥?”,实习生一听,不禁问道。

“路漫漫其修远兮,吾将上下而求索。”

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

0 人点赞