分类: LINUX
2010-10-08 20:43:13
orphan在英文中是孤儿的意思,在这里取被遗弃、被删除之意。
orphan inode是什么样的inode呢?这种inode是怎样产生的呢?
先介绍一个概念,文件的引用计数,准确地说应该是inode的引用计数,因为一般来说一个文件会对应一个inode。文件的引用计数,简单地说是表示有多少个文件指向该文件,准确地说是文件的硬链接的个数。
情况1:设想一个进程,open一个文件,然后unlink该文件,然后进行文件读写。这是允许的,并且在进程退出时,内核会自动将引用计数为0的文件删除。
但是如果该进程尚未退出之前,系统崩溃了,那么,内核就没有机会将已被unlink、并且引用计数为0的inode从磁盘上删除了。
情况2:设想我们正在截断一个大文件(系统调用truncate),但是操作尚未完成,系统就崩溃了。同样,内核也没有办法将该文件的所有数据块全部删除了。
ext3、ext4的orphan inode机制就是处理上述两种情况的。基本思想是这样的:如果要删除或截断一个inode,要先把这个inode记录到磁盘上的一个特殊的orphan inode链表上。如果删除或截断操作能够正常完成,那么,就从磁盘上的orphan inode链表上删除该inode;否则,如果删除或截断操作未完成之前,系统就发生崩溃了,那么,系统重启后,文件系统会遍历磁盘上的orphan inode链表,对链表上的每一个inode都重新进行一遍删除或截断操作,以此来保证这些inode真正在磁盘上被删除,维护文件系统的一致性。
内核版本:2.6.35
先总体说一下orphan inode的组织。
orphan inode需要在两个地方组织,分别是在内存中和在磁盘上。不论在哪里,从抽象角度来看,orphan inode都被组织成一个单向链表。
1、ext4_inode
struct ext4_inode {
__le32 i_dtime; /* Deletion Time */
.........
}
这个是磁盘上的inode的结构,i_dtime本来表示该inode被删除的时间,在orphan inode机制中,因为此时该域的值并不重要,故借用一下,用于记录下一个被unlink/truncate的inode号。
2、ext4_super_block
struct ext4_super_block {
__le32 s_last_orphan; /* start of list of inodes to delete */
.........
}
这个是磁盘上的superblock结构。其中,s_last_orphan记录的是最近一个被unlink/truncate的inode号,从抽象角度来看,它就代表磁盘上orphan inode单链表的头。
新的inode插入orphan inode链表时采用“头插法”,也就是说,最近被unlink/truncate的inode号会放在s_last_orphan中。
这样,磁盘上的orphan inode单链表如下图1所示。
3、ext4_inode_info
struct ext4_inode_info {
__u32 i_dtime;
struct list_head i_orphan; /* unlinked but open inodes */
.......
}
这个是内存中的inode结构,即 磁盘上的ext4_inode在内存中的表现。
其中,i_dtime与ext4_inode中的i_dtime相对应,i_orphan是个链表节点,用于在内存中组成orphan
inode链表。
4、ext4_sb_info
struct ext4_sb_info {
struct list_head s_orphan;
struct mutex s_orphan_lock;
.........
}
这个是内存中的superblock结构,即磁盘上ext4_super_block在内存中的表现。其中,s_orphan代表链表头,s_orphan_lock是用于保护链表的互斥锁。在内存中的orphan inode链表结构与图1很类似,在此从略。
1、NEXT_ORPHAN(inode)
fs/ext4/ext4.h
1209 #define NEXT_ORPHAN(inode) EXT4_I(inode)->i_dtime
这个宏的作用是在磁盘orphan inode链表上取得下一个orphan inode号。
2、ext4_orphan_get()
1074 struct inode *ext4_orphan_get(struct super_block *sb, unsigned long ino)
{
.......
1104 inode = ext4_iget(sb, ino);
.......
}
这个函数的主要作用是根据一个inode号ino,从磁盘上将该inode的信息读入内存,保存在ext4_inode_info结构中。
3、ext4_orphan_add()
1984 int ext4_orphan_add(handle_t *handle, struct inode *inode)
{
.......
// 将“下一个”最近的inode号保存在本inode的ext4_inode_info->i_dtime中
// 这样,当本inode写回磁盘时,会将“下一个”orphan inode号写回磁盘。
2030 NEXT_ORPHAN(inode) = le32_to_cpu(EXT4_SB(sb)->s_es->s_last_orphan);
// 将最近的inode号保存在ext4_super_block->s_last_orphan中,
// 这样,当本超级块写回磁盘时,会将最近的orphan inode号写回磁盘。
2031 EXT4_SB(sb)->s_es->s_last_orphan = cpu_to_le32(inode->i_ino);
.......
// 将内存中的ext4_inode_info结构链到ext4_sb_info->s_orphan链表的第一个位置。
2046 if (!err)
2047 list_add(&EXT4_I(inode)->i_orphan, &EXT4_SB(sb)->s_orphan);
.......
}
总之,ext4_orphan_add()的作用是在内存中和磁盘上的orphan inode链表中分别添加一个inode节点。
4、ext4_orphan_del()
2062 int ext4_orphan_del(handle_t *handle, struct inode *inode)
{
.......
// 取得磁盘上“下一个”orphan inode号
2079 ino_next = NEXT_ORPHAN(inode);
// 取得内存中“上一个”orphan inode的节点
2080 prev = ei->i_orphan.prev;
.......
// 在内存中的orphan inode链表上将该inode删除
2085 list_del_init(&ei->i_orphan);
.......
// 下面的代码为什么会有分支?
// 主要是因为要区分要删除的inode是不是链表头
2098 if (prev == &sbi->s_orphan) {
// 如果要删除的inode在链表头
// ext4_super_block->s_last_orphan中记录该inode的下一个inode号即可
2104 sbi->s_es->s_last_orphan = cpu_to_le32(ino_next);
2106 } else {
// 否则,要删除的inode不在链表头
// 用上一个inode的i_dtime记录本inode下一个 inode号
2116 NEXT_ORPHAN(i_prev) = ino_next;
2118 }
.......
// 本inode已从orphan链表中删除了,故这里i_dtime设为0
2121 NEXT_ORPHAN(inode) = 0;
.......
}
总之,ext4_orphan_del()的作用是在内存中和磁盘上的orphan inode链表中分别删除一个inode节点。
我们结合unlink系统调用的逻辑,看看删除一个inode的正常的过程。
注意:我们只看未unlink前,该inode的引用计数为1,unlink后,该inode的引用计数为0的情况。因为只有这种情况才会要求真正从磁盘上删除该inode。
SYSCALL_DEFINE1(unlink, const char __user *, pathname)
-->do_unlinkat
-->vfs_unlink
-->ext4_unlink
-->ext4_delete_entry从该文件所在目录中删除该文件
-->ext4_orphan_add
-->iput
-->iput_final
-->generic_drop_inode
-->generic_delete_inode(inode);
-->ext4_delete_inode
-->ext4_truncate清除磁盘上的索引信息
-->ext4_orphan_del
-->ext4_free_inode从内存中和磁盘上分别删除该inode
由此过程可知,在删除一个inode时,ext4_orphan_add和ext4_orphan_del是成对使用的,
这样才能保证一致性。
一般而言,上述unlink过程在ext4_delete_inode会执行较长的时间,因为里面有ext4_truncate函数,它负责把inode的全部索引信息清除,如果一个文件比较大,索引信息就会很多,删除过程就会很长。
现在有两个假设:
假设1、假设orphan inode的信息已经写回磁盘,这样磁盘上的inode和superblock就包含了orphan inode的信息。
假设2、假设在ext4_delete_inode函数的执行过程中,系统崩溃了。
此时orphan inode机制就可以发挥作用了。
挂载文件系统会调用下列函数:
ext4_get_sb
-->get_sb_bdev
-->ext4_fill_super
-->ext4_orphan_cleanup
1970 static void ext4_orphan_cleanup(struct super_block *sb,
1971 struct ext4_super_block *es)
1972 {
.......
2017 while (es->s_last_orphan) {
// 有点小技巧的地方在这里!
// es->s_last_orphan明显是个整数,这里怎么弄成个循环呢?
// 原因是每次循环,会调用ext4_orphan_del()在orphan链表上删除一个inode,
// 注意看ext4_orphan_del()函数的2104行,会将es->s_last_orphan设置成下一个被orphan的inode号。
// 于是,本循环的作用是从最近被orphan的inode开始,依次处理次近的、第三近的......最后一个inode。
2018 struct inode *inode;
2019
2020 inode = ext4_orphan_get(sb, le32_to_cpu(es->s_last_orphan));
.......
// 将该inode加入内存中的orphan inode链表
2026 list_add(&EXT4_I(inode)->i_orphan, &EXT4_SB(sb)->s_orphan);
2028 if (inode->i_nlink) {
// 如果该inode的引用计数为0,则重新执行一遍 ext4_truncate,
// 清除该文件在磁盘上的索引信息
2034 ext4_truncate(inode);
2035 nr_truncates++;
2036 } else {
2042 nr_orphans++;
2043 }
// 正如注释中说的那样,真正神奇的地方在这里
// 参考第四章“删除一个inode的正常过程”,你就会直到iput最终会调用ext4_orphan_del()在orphan链表上删除一个inode,
// 注意看ext4_orphan_del()函数的2104行,
// 会将es->s_last_orphan设置成下一个被orphan的inode号,这样循环才能继续。
2044 iput(inode); /* The delete magic happens here! */
}
.......
2045 }
至此,orphan inode机制就介绍完了。
总得来说,orphan inode机制能够在一定程度上避免unlink/truncate文件时的不一致性。
这里特别要强调“一定程度”,在第五章中我们做了三个假设,其中,如果假设1不成立的话,
orphan inode机制也是不能发挥作用的,因为必要的信息尚未写入磁盘,重启以后就无据可依了。
文件: ext3、ext4 orphan inode机制分析.pdf 大小: 113KB 下载: 下载
文件: ext3、ext4 orphan inode机制分析.odt.gz 大小: 28KB 下载: 下载
文件: ext3、ext4 orphan inode机制分析.doc.gz 大小: 8KB 下载: 下载
chinaunix网友2010-10-10 17:36:39
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com