背景node
有时会出现这样的状况,磁盘空间显示已经被占满,可是在查看磁盘的具体文件占用状况时,发现磁盘仍然有很大的空余空间。mysql
1.执行df命令查看磁盘使用状况,发现磁盘已经满了。sql
-bash-4.2$ df -Th Filesystem Type Size Used Avail Use% Mounted on /dev/vda1 ext4 30G 30G 0 100% / devtmpfs devtmpfs 489M 0 489M 0% /dev tmpfs tmpfs 497M 0 497M 0% /dev/shm tmpfs tmpfs 497M 50M 447M 11% /run tmpfs tmpfs 497M 0 497M 0% /sys/fs/cgroup
2.执行 du 命令查看各个目录的磁盘占用状况,把各个目录文件的大小相加,发现并无占满磁盘,有10多G空间莫名失踪。缓存
-bash-4.2$ du -h --max-depth=1 /home16M /home/logs11G /home/serverdog11G /home
3.为什么会出现这样的状况呢?bash
由于虽然文件已被删除,可是一些进程仍然打开这些文件,所以其占用的磁盘空间并无被释放。执行lsof 命令显示打开已删除的文件。将有问题的进程重启(或,清空),磁盘空间就会获得释放。app
-bash-4.2# lsof | grep deletemysqld 2470 mysql 4u REG 253,1 0 523577 /var/tmp/ibfTeQFn (deleted) mysqld 2470 mysql 5u REG 253,1 0 523579 /var/tmp/ibaHcIdW (deleted) mysqld 2470 mysql 6u REG 253,1 0 523581 /var/tmp/ibLjiALu (deleted) mysqld 2470 mysql 7u REG 253,1 0 523585 /var/tmp/ibCFnzTB (deleted) mysqld 2470 mysql 11u REG 253,1
那么,Linux 的文件系统,到底为何这么设计呢?要了解这些,就要先弄清楚并不容易,下面将从一些基本概念入手,一步步将这些梳理清楚:less
下图显示了 Linux 操做系统中负责文件管理的基本组件。上半区域为用户模式,下半区域为内核模式。应用程序使用标准库libc来访问文件,库将请求映射到系统调用,以便进入内核模式。全部与文件相关的操做的入口都是虚拟文件系统(VFS),而非特定的额文件系统(如Ext三、ReiserFS和NFS)。VFS 提供了系统库和特定文件系统之间的接口。所以,VFS 不只充当抽象层,并且实际上它提供了一个文件系统的基本实现,能够由不一样的实现来使用和扩展。所以,要了解文件系统是如何工做的,就要先了解VFS 。post
VFS 的主要思想在于引入了一个通用文件模型(common file model)。通用文件模型由如下对象类型组成:ui
超级块对象(superblock object)spa
索引节点对象(inode object)
文件对象(file object) -内存:打开文件时建立,存放 打开文件 与进程之间进行交互的有关信息(file 结构) 打开文件信息,仅当进程访问文件期间存在于内核内存中。
目录项对象(dentry object)
综合来讲,Linux 的 根文件系统(system’s root filessystem) 是内核启动mount的第一个文件系统。内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载以后,从中把一些基本的初始化脚本和服务等加载到内存中去运行(文件系统和内核是彻底独立的两个部分)。其余文件系统,则后续经过脚本或命令做为子文件系统安装在已安装文件系统的目录上,最终造成整个目录树。
start_kernel vfs_caches_init mnt_init init_rootfs // 注册rootfs文件系统 init_mount_tree // 挂载rootfs文件系统 … rest_init kernel_thread(kernel_init, NULL, CLONE_FS);
就单个文件系统而言,在文件系统安装时,建立超级块对象;沿树查找文件时,老是首先从初识目录的中查找匹配的目录项,以便获取相应的索引节点,而后读取索引节点的目录文件,转化为dentry对象,再检查匹配的目录项,反复执行以上过程,直至找到对应的文件的索引节点,并建立索引节点对象。
软连接是一个普通的文件,其中存放的是另一个文件的路径名。硬连接则指向同一个索引节点,硬连接数记录在索引节点对象的 i_nlink 字段。当i_nlink字段为零时,说明没有硬连接指向该文件。
下图是一个简单示例,说明进程是怎样与文件进行交互。三个不一样进程打开同一个文件,每一个进程都有本身的文件对象,其中两个进程使用同一个硬连接(每一个硬连接对应一个目录对象),两个目录项对象都指向同一个 索引节点对象。
索引节点的数据又由两部分组成:内存数据和磁盘数据。Linux 使用 Write back 做为索引节点的数据一致性策略。对于索引节点的数据,当文件被打开时,才会加载索引节点到内存;当再也不被进程使用,则从内存踢出;若是中间有更新,则须要把数据写回磁盘。
* "in_use" - valid inode, i_count > 0, i_nlink > 0 * "dirty" - as "in_use" but also dirty * "unused" - valid inode, i_count = 0
索引节点是否仍在使用,是经过 open() 和 close() 操做创建和销毁文件对象,文件对象经过索引节点提供的 iget 和 iput 更新索引节点的i_count字段,以完成使用计数。open 操做使得 i_count 加一, close 操做使得 i_count 减一。在 close 操做时判断索引节点是否释放,若是 i_count = 0,则意味着再也不有进程引用,将会从内存释放。
文件与磁盘管理联系最紧密的操做,莫过于touch和rm操做,而尤之后者最为关键。经过strace(或 dtruss),查看 rm 的实际的系统调用
# dtruss rm tmp ... geteuid(0x0, 0x0, 0x0) = 0 0 ioctl(0x0, 0x4004667A, 0x7FFEE06F09C4) = 0 0 lstat64("tmp0", 0x7FFEE06F0968, 0x0) = 0 0 access("tmp0", 0x2, 0x0) = 0 0 unlink("tmp0", 0x0, 0x0) = 0 0
能够发现 rm 实际是经过 unlink 完成的。unlink表明删除目录项,以及减小其索引节点的计数。由通用文件模型可知,父目录自己一样是一个文件,也就意味着目录项是其文件数据的一部分。删除目录项等价于从父目录的文件中删除数据,也就意味着首先要打开父目录的文件。那么,删除操做便可理解为:
回头来看遇到的问题,其实能够从两个角度来理解:
索引与数据
文件系统与文件、磁盘管理与文件、进程管理与文件,最核心的都是文件的索引,而不是文件的数据。把数据和索引分开是理解文件系统的关键。
缓存策略
因为操做系统使用 Write back 的策略,意味着只有先释放内存,才有可能释放磁盘。
Why lsof ?
从上面的模型能够很清楚的理解,由于目录已经没有索引到文件了,可是打开文件还有索引到文件,因此不能马上释放磁盘空间。
为何 lsof 能够找到已删除未释放的文件呢?
lsof,顾名思义:list open files,该命令的原理就是查找打开文件的列表,所以能够找到已删除未释放的文件。
做者:cyningsun
连接:https://juejin.im/post/687511...