Chinaunix首页 | 论坛 | 博客

pwp

  • 博客访问: 46239
  • 博文数量: 9
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 120
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-06 13:52
文章分类

全部博文(9)

文章存档

2011年(1)

2010年(6)

2008年(2)

我的朋友

分类: LINUX

2010-10-08 20:43:13

一、概述

orphan在英文中是孤儿的意思,在这里取被遗弃、被删除之意。

orphan inode是什么样的inode呢?这种inode是怎样产生的呢?

先介绍一个概念,文件的引用计数,准确地说应该是inode的引用计数,因为一般来说一个文件会对应一个inode。文件的引用计数,简单地说是表示有多少个文件指向该文件,准确地说是文件的硬链接的个数。

情况1:设想一个进程,open一个文件,然后unlink该文件,然后进行文件读写。这是允许的,并且在进程退出时,内核会自动将引用计数为0的文件删除。

但是如果该进程尚未退出之前,系统崩溃了,那么,内核就没有机会将已被unlink、并且引用计数为0inode从磁盘上删除了。

情况2:设想我们正在截断一个大文件(系统调用truncate),但是操作尚未完成,系统就崩溃了。同样,内核也没有办法将该文件的所有数据块全部删除了。

ext3ext4orphan inode机制就是处理上述两种情况的。基本思想是这样的:如果要删除或截断一个inode,要先把这个inode记录到磁盘上的一个特殊的orphan inode链表上。如果删除或截断操作能够正常完成,那么,就从磁盘上的orphan inode链表上删除该inode;否则,如果删除或截断操作未完成之前,系统就发生崩溃了,那么,系统重启后,文件系统会遍历磁盘上的orphan inode链表,对链表上的每一个inode都重新进行一遍删除或截断操作,以此来保证这些inode真正在磁盘上被删除,维护文件系统的一致性。

内核版本:2.6.35

二、相关数据结构及之间的关系

先总体说一下orphan inode的组织。

orphan inode需要在两个地方组织,分别是在内存中和在磁盘上。不论在哪里,从抽象角度来看,orphan inode都被组织成一个单向链表。

1ext4_inode

struct ext4_inode {

    __le32 i_dtime; /* Deletion Time */

    .........

}

这个是磁盘上的inode的结构,i_dtime本来表示该inode被删除的时间,在orphan inode机制中,因为此时该域的值并不重要,故借用一下,用于记录下一个被unlink/truncateinode号。

2ext4_super_block

struct ext4_super_block {

    __le32 s_last_orphan; /* start of list of inodes to delete */

    .........

}

这个是磁盘上的superblock结构。其中,s_last_orphan记录的是最近一个被unlink/truncateinode号,从抽象角度来看,它就代表磁盘上orphan inode单链表的头。

新的inode插入orphan inode链表时采用“头插法”,也就是说,最近被unlink/truncateinode号会放在s_last_orphan中。

这样,磁盘上的orphan inode单链表如下图1所示。

3ext4_inode_info

struct ext4_inode_info {

    __u32 i_dtime;

    struct list_head i_orphan; /* unlinked but open inodes */

    .......

}

这个是内存中的inode结构,即 磁盘上的ext4_inode在内存中的表现。

其中,i_dtimeext4_inode中的i_dtime相对应,i_orphan是个链表节点,用于在内存中组成orphan

inode链表。

4ext4_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很类似,在此从略。

三、辅助函数

1NEXT_ORPHAN(inode)

fs/ext4/ext4.h

1209 #define NEXT_ORPHAN(inode) EXT4_I(inode)->i_dtime

这个宏的作用是在磁盘orphan inode链表上取得下一个orphan inode号。

2ext4_orphan_get()

1074 struct inode *ext4_orphan_get(struct super_block *sb, unsigned long ino)

{

       .......

1104 inode = ext4_iget(sb, ino);

       .......

}

这个函数的主要作用是根据一个inodeino,从磁盘上将该inode的信息读入内存,保存在ext4_inode_info结构中。

3ext4_orphan_add()

1984 int ext4_orphan_add(handle_t *handle, struct inode *inode)

{

       .......

       // 将“下一个”最近的inode号保存在本inodeext4_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节点。

4ext4_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不在链表头

             // 上一个inodei_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节点。

四、删除一个inode的正常过程

我们结合unlink系统调用的逻辑,看看删除一个inode的正常的过程。

注意:我们只看未unlink前,该inode的引用计数为1unlink后,该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_addext4_orphan_del是成对使用的,

这样才能保证一致性。

五、恢复时处理orphan inode的逻辑

一般而言,上述unlink过程在ext4_delete_inode会执行较长的时间,因为里面有ext4_truncate函数,它负责把inode的全部索引信息清除,如果一个文件比较大,索引信息就会很多,删除过程就会很长。

现在有两个假设:

假设1、假设orphan inode的信息已经写回磁盘,这样磁盘上的inodesuperblock就包含了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设置成下一个被orphaninode号。

                  // 于是,本循环的作用是从最近被orphaninode开始,依次处理次近的、第三近的......最后一个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设置成下一个被orphaninode号,这样循环才能继续。

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
下载:下载


阅读(5825) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-10-10 17:36:39

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com