在linux系统中如果误删除了某个文件,但是文件句柄还被某些进程占用着,这种情况下文件还能恢复吗?如果可以,那该怎么恢复被删除的文件呢?
首先我们先来理解下,【文件被删除了,但是文件句柄还被某些进程占用着】是什么意思?
先上个图
当我们创建一个文件后,linux文件系统会将文件的数据存储到磁盘的数据区(data area),文件根据其大小可能会占用多个磁盘扇区;
每个文件都会对应一个inode,inode存在于磁盘的索引表部分(inode table),inode中存储着文件的元数据信息,文件系统通过inode里存储的元数据读写磁盘;
每个inode都会分配一个唯一的inode编号,即索引节点编号;
文件系统会将文件名与inode编号建立映射关系,存储在内存中,便于快速访问。
上面说了这么一些,那实际上文件系统访问文件的流程是怎样的呢?
1)我们平常查看或修改文件都是通过文件名来操作的,上边说到了文件名与inode之间存在映射关系,而这个映射关系存储在内存中,通过文件名可以找到与之关联的inode编号;
2)拿到了inode编号后,就可以查找到该编号对应的inode,进而从磁盘里读取到inode中存储的文件元数据;
3)通过文件元数据,再访问。
大概流程就是这样。
那这个与我们所要理解的【文件被删除了,但是文件句柄还被某些进程占用着】有什么关系呢?其实是有关系的。
我们常说文件名、文件名,都知道它是一个文件的名称,但实际上文件名只是一个指向文件inode的硬链接(可以理解为文件的一个别名),可以真正恒久不变地标识一个文件的是inode编号;
从一个文件被创建后,它就会对应着一个inode,在它的生命周期内(即一个文件被删除之前),文件的inode都是不会发生改变的;而文件名是可以随意改变的,文件名发生了改变,文件内容是不会受影响的;
就像一个人的微信,它的微信号是唯一的,但是微信昵称却是可以变来变去的。
当一个文件正被某个进程读写时,我们执行rm操作将其删除,此时我们执行cat filename,查看文件文件内容的话,肯定会报【No such file or directory】的错误。
衔接上面说到的文件名只是一个指向文件inode的硬链接(它与inode之间建立的联系),文件系统访问文件是先通过文件名在内存中存储的映射关系中找到inode编号,然后再找到inode,接着读取出inode中文件的元数据,然后文件系统再通过元数据访问磁盘进行IO操作。
我们把文件名删除后,相当于在内存中删除了文件名与inode之间建立的映射关系,这样文件系统就找不到inode了;
但是如果此时还有进程在占用着文件,那么文件不会彻底删除,因为此时该文件的句柄还没释放,文件属于标记删除状态;
而如果文件没有被进程占用且文件也没有其他的硬链接指向它的inode了(即文件只有一个别名),那么此时该文件会被彻底删除(包括文件名、inode以及磁盘中存储的文件数据);
如果此时还有其他的硬链接指向文件的inode(文件有多个别名,类似于一个人可以拥有多个花名),那么仅是删除这个这个硬链接(即此次rm操作只是删除这个文件名,对文件数据没影响)。
所以,【文件被删除了,但是文件句柄还被某些进程占用着】的意思是:虽然你在linux系统上访问文件时报【No such file or directory】错误,但是别慌,文件还没真正删除的,还可以找回来。那怎么找回来呢?这就需要从进程以及其占用的文件句柄(文件句柄其实是一个文件描述符,即符号链接)入手了。
linux系统上跑着的每个进程都在/proc目录下存在着一个记录,记录格式为:/proc/pid,即每个进程在/proc目录下都会对应一个目录,目录名为进程的id号。
例如,进程tail -f ldapbak.ldif的进程id为15603
我们到/proc目录下查看下是否真的有这样的一个目录,切换到/proc,执行ls 15603
可以看到这个目录下还是有挺多文件的,其中有一个目录为fd。
fd这个目录的作用是存储着该进程占用着的文件句柄,例如:
其中,0、1、2、3、4这些就是文件描述符,它们其实本质就是符号链接(即软链接),指向被该进程打开的文件。
所以进程打开的文件描述符的存储路径是:/proc/pid/fd/文件描述符
如果一个进程占用的文件描述符没有被释放,那么执行rm操作删除文件时,文件尚未真正被删除。
那么当我们执行rm操作误删了文件,我们怎么判断这个文件是否正被某些进程占用着以判断文件能否恢复呢?
如果你知道文件名,那么可以直接执行lsof | grep filename,如果能够查到记录,那么说明还有进程在占用着这个文件;
否则没有进程占用了,文件可能很难恢复了。
如果不知道确切的文件名,那么可以执行lsof | grep deleted,来查找记录。
此时ldapbak.ldif文件正被tail进程占用着,我们对其执行rm -f操作,然后再通过lsof | grep filename或者lsof | grep deleted来查找,由于这里我明确知道文件名,所以我就直接使用lsof | grep ldapbak.ldif来查找了,如下图:
可以看到,如果一个被进程占用着,这种情况下对其执行rm操作,lsof得到的记录中会有(deleted)标签,所以我们才可以通过lsof | grep deleted来查找。
要想恢复文件,首先得知道占用着这个文件的进程id以及其创建的文件描述符,为什么?因为我们需要根据/proc/pid/fd/文件描述符,找到被进程占用的文件句柄;
如上图所示,第二列是tail进程的pid:34095(这里因为我kill了进程,重新打开了一个,所以pid不是之前的15603了),第四列是文件句柄(文件描述符)3。
好了,现在找到了pid,也找到了文件描述符,我们可以切换到/proc/34095/fd目录,找到文件描述符3,然后通过cp命令即可恢复被删除的文件;
例如我删除的文件是ldapbak.ldif,那么就可以执行cp 3 /home/test/ldapbak.ldif就可以恢复文件ldapbak.ldif到指定目录了。