之前做技术研究的时候搞文件系统元数据镜像时处理过orphan inode的问题,而现在恰好有同事在做lsof时发现了一些的特殊的文件,lsof可以看到进程在使用,同时ls具体文件时却又看不到:

/var/run/nscd/db4Dqbpq文件就成为orphan文件,42883是文件的inode,称之为orphan inode

一、神马是orphan inode

Orphan inode,顾名思义就是孤儿节点,是Linux ext系列文件系统结构中节点的一种,orphan意指的是被删除,无主的节点。通常inode数据结构字段中有i_links_count的参数,指的就是有多少文件硬链接到这个inode上。而orphan inode的i_links_count则为0,即为无主,这个时候ls上就看不到了。

二、为什么存在orphan inode

正常情况下一个inode被占用,则i_links_count>0,如果i_links_count=0则说明这个文件已经被删除了,因为删除一个文件并不是原子操作,我们把删除这个过程无限放大,如果一个文件先将i_links_count--到0,然后再删除具体的数据块,现实却是在“然后”之前系统断电了,没有电池呀。这个样子orphan inode形态就产生了,然后系统起来之后通过fsck比较有些块直接被bitmap标记了,却没有inode占用,这个样子就视为fs error,而orphan inode机制则是及时处理orphan inode,避免文件系统不一致的情况。

当然还有另外一种情况,也就是文件开头的示例所说,一个文件已经被一个进程open了,立即unlink掉这个文件,进程并不结束,一直占用open的文件句柄,此时文件已经被删除了,i_links_count=0,如果具体的数据块也被释放了,那进程读写被释放的数据块(更有可能被另外的文件占用),文件系统就彻底悲剧了,所以此时文件的具体数据块并没有释放,仍然保留,进程仍可以读写,直到句柄关闭时,orphan inode机制才保证删除具体的数据块

三、orphan inode 结构

首先说主要有两个结构体是存有orphan inode的,其一是ext3_inode,另外一个就是ext3_super_block,这个两个都是磁盘数据的存储形式,内存上的对应形式是ext3_inode_info和ext3_sb_info,但内存上的具体内容就不在赘述。

先看ext3_super_block:

s_last_orphan就是超级块中的链接到orphan inode的索引,或者说就是orphan inode链表的头结点。

通过tune2fs工具查看var挂着设备sda5的超级块数据

超级块中的orphan inode 是42283,和前面lsof看到的inode是对应的。

但头结点只是一个无符号的整型数,描述的是一个indoe号,但磁盘分区有N个orphan inode,它们之间的链接就一块inode中的i_dtime参数
ext3_inode:

i_links_count代表了inode的被链接数,i_dtime则代表这inode被删除的参数,但事实上dtime参数应该一直没什么用(正常情况下一直是空的,为0),除了被orphan inode利用,用来链接下一个orphan inode。

我们通过debugfs工具看一下orphan inode具体形式:

从上面可以看出文件具体数据BLOCKS仍然存在,只是Links为0,同时没有删除时间

如此它们的链接关系如是:

orphan_inode_link_list

四、orphan inode 机制

通过删除一个文件可以清晰看到orphan inode的机制,下面描述删除文件的过程。

系统调用进入内核是通过SYSCALL_DEFINE1实现的,具体调用流程如下

SYSCALL_DEFINE1-->do_unlinkat-->vfs_unlink-->ext3_unlink

系统最后调用的是ext3_unlink,在函数中,它首先通过ext3_delete_entry函数删除了inode父目录中的普通数据条目,然后用ext3_orphan_add把准备删除的inode加入到orphan inode链表中,具体函数如下:

需要注意的是orphan inode链表采用的是头插法,删除也是自头结点开始删除。

ext3_unlink之后数据事实上还没有删除,只是删除一个硬链接,如果硬链接此时为0,则将inode的相关节点挂着orphan inode上,真正删除普通数据的过程函数如下:

do_unlinkat-->iput-->generic_drop_inode-->generic_delete_inode-->ext3_delete_inode

在generic_drop_inode中判断硬链接如果为0,则准备删除数据,最终通过ext3_delete_inode来实现的,需要注意的是ext3_delete_inode是在ext3_sops初始化是赋值的,generic_delete_inode调用ext3_sops中对象函数指针实现的。

函数通过调用ext3_truncate层层删除具体的文件数据,完成后用ext3_orphan_del将orphan inode删掉,此时inode的被删除时间才被赋值。

我们讨论的是在ext3_orphan_del完成之前,系统挂掉了,代表这文件删除失败了,但文件系统被重新挂着时,系统会加载文件系统上的超级块数据,读取s_last_orphan,将所有的orphan inode删除掉,保证系统是一致的。

ext3_get_sb-->ext3_fill_super-->ext3_orphan_cleanup


文件系统orphan inode机制分析来自于OenHan

链接为:http://oenhan.com/fs-orphan-inode-analysis

发表评论