如何恢复 Linux 上删除的文件–reiserfs 文件系统原理

reiserfs 对于小文件的存取速度非常高,这取决于它所采用的精美的设计:reiserfs 文件系统就是一棵动态的 B+ 树,小文件和大文件的尾部数据都可以通过保存到叶子节点中而加快存取速度。本文将探讨 reiserfs 的设计和实现内幕,并从中探讨恢复删除文件的可能性。

reiserfs 是由 namesys 公司的 Hans Reiser 设计并开发的一种通用日志文件系统,它是第一个进入 Linux 标准内核日志文件系统。从诞生之日起,reiserfs 就由于其诸多非常有吸引力的特性而受到很多用户的青睐,迅速成为 Slackware 等发行版的默认文件系统。它也一度也是 SUSE Linux Enterprise 发行版上的默认文件系统,直到 2006 年 10 月 12 日 Novell 公司决定将默认文件系统转换到 ext3 为止。尽管其主要设计人员 Hans Reiser 由于涉嫌杀害妻子遭到指控而入狱,从而导致他不得不试图出售 namesys 公司来支付庞大的诉讼费用,但是 reiserfs 已经受到广大社区开发人员和用户的极大关注,有很多志愿者已经投入到新的 reiserfs 4 的开发工作中来。本文中的介绍都是基于最新的稳定版本 3.6 版本的,所引用的代码都基于 2.6.23 版本的内核。

reiserfs 最初的设计目标是为了改进 ext2 文件系统的性能,提高文件系统的利用率,并增强对包含大量文件的目录的处理能力(ext2/ext3 文件系统中一个目录下可以包含的子目录最多只能有 31998 个)。传统的 ext2 和 ufs 文件系统都采用了将文件数据和文件元数据分离开保存的形式,将元数据保存到索引节点中,将文件数据保存到单独的磁盘块中,并通过索引节点中的 i_block 数组利用直接索引和间接索引的形式在磁盘上定位文件数据。这种设计非常适合存储较大的文件(比如20KB以上),但是对于具有大量小文件的系统来说就存在一些问题。首先在于文件系统的利用率,由于 ext2 会将文件数据以数据块为单位(默认为 4KB)进行存储,因此对于存储只有几十个字节的文件来说,会造成空间的极大浪费。另外由于在读取文件时需要分别读取文件元数据和文件数据,加上多读取数据块的开销,ext2 文件系统在处理大量小文件时,性能会比较差。为了获取最好的性能和最大程度地利用磁盘空间,很多用户会在文件系统之上采用数据库之类的解决方案来存储这些小文件,因此会导致上层应用程序的接口极不统一。

为了解决上面提到的问题,reiserfs 为每个文件系统采用一棵经过专门优化的 B+ 树来组织所有的文件数据,并实现了很多新特性,例如元数据日志。为了提高文件系统的利用率,reiserfs 中采用了所谓的尾部封装(tail packing)设计,可以充分利用已分配磁盘块中的剩余空间来存储小文件。实际上,reiserfs 文件系统中存储的文件会比 ext2/ext3 大 5% – 6% 以上。下面让我们来探索一下 reiserfs 文件系统中数据在磁盘上究竟是如何存储的。

磁盘布局

与 ext2/ext3 类似,reiserfs 文件系统在创建时,也会将磁盘空间划分成固定大小的数据块。数据块从 0 开始编号,最多可以有 232 个数据块。因此如果采用默认的 4KB 大小的数据块,单个 reiserfs 文件系统的上限是 16TB。reiserfs 分区的前 64KB 保留给引导扇区、磁盘标签等使用。超级块(super block)从 64KB 开始,会占用一个数据块;之后是一个数据块位图,用来标识对应的数据块是否处于空闲状态。如果一个数据块位图可以标识 n 个数据块,那么 reiserfs 分区中的第 n 个数据块也都是这样一个数据块,用来标识此后(包括自己)n 的数据块的状态。reiserfs 文件系统的磁盘结构如图 1 所示。
图 1. reiserfs 分区磁盘布局
reiserfs 分区磁盘布局

与 ext2/ext3 类似,reiserfs 文件系统的一些关键信息也保存超级块中。reiserfs 的超级块使用一个 reiserfs_super_block 结构来表示,其定义如清单1 所示:
清单1. reiserfs_super_block 结构定义

                
135 struct reiserfs_super_block_v1 {
 136         __le32 s_block_count;   /* blocks count         */
 137         __le32 s_free_blocks;   /* free blocks count    */
 138         __le32 s_root_block;    /* root block number    */
 139         struct journal_params s_journal;
 140         __le16 s_blocksize;     /* block size */
 141         __le16 s_oid_maxsize;   /* max size of object id array, see
 142                                  * get_objectid() commentary  */
 143         __le16 s_oid_cursize;   /* current size of object id array */
 144         __le16 s_umount_state;  /* this is set to 1 when filesystem was
 145                                  * umounted, to 2 - when not */
 146         char s_magic[10];       /* reiserfs magic string indicates that
 147                                  * file system is reiserfs:
 148                                  * "ReIsErFs" or "ReIsEr2Fs" or "ReIsEr3Fs" */
 149         __le16 s_fs_state;      /* it is set to used by fsck to mark which
 150                                  * phase of rebuilding is done */
 151         __le32 s_hash_function_code;    /* indicate, what hash function is being use
 152                                          * to sort names in a directory*/
 153         __le16 s_tree_height;   /* height of disk tree */
 154         __le16 s_bmap_nr;       /* amount of bitmap blocks needed to address
 155                                  * each block of file system */
 156         __le16 s_version;       /* this field is only reliable on filesystem
 157                                  * with non-standard journal */
 158         __le16 s_reserved_for_journal;  /* size in blocks of journal area on main
 159                                          * device, we need to keep after
 160                                          * making fs with non-standard journal */
 161 } __attribute__ ((__packed__));
 162 
 163 #define SB_SIZE_V1 (sizeof(struct reiserfs_super_block_v1))
 164 
 165 /* this is the on disk super block */
 166 struct reiserfs_super_block {
 167         struct reiserfs_super_block_v1 s_v1;
 168         __le32 s_inode_generation;
 169         __le32 s_flags;     /* Right now used only by inode-attributes, if enabled */
 170         unsigned char s_uuid[16];       /* filesystem unique identifier */
 171         unsigned char s_label[16];      /* filesystem volume label */
 172         char s_unused[88];      /* zero filled by mkreiserfs and
 173                                  * reiserfs_convert_objectid_map_v1()
 174                                  * so any additions must be updated
 175                                  * there as well. */
 176 } __attribute__ ((__packed__));

 

 

该结构定义中还包含了其他结构的定义,例如 journal_params,这是有关日志的一个结构,并非本文关注的重点,读者可以自行参考内核源代码中的 include/linux/ reiserfs_fs.h 文件。

实际上,超级块并不需要一个完整的数据块来存储,这个数据块中剩余的空间用来解决文件对象 id 的重用问题,详细内容请参看本系列文章下一部分的介绍。

 


B+ 树

 

与 ext2/ext3 的超级块比较一下会发现,reiserfs 的超级块中并没有索引节点表的信息,这是由于 reiserfs 并没有使用索引节点表,而是采用了 B+ 树来组织数据(在 reiserfs 的文档中也称为是 S+ 树)。图 2 中给出了一棵典型的 2 阶 B+ 树,其深度为 4。
图 2. 2 阶 B+ 树
2 阶 B+ 树

一棵 B+ 树有唯一一个根节点,其位置保存在超级块的 root_block 字段中。包含子树的节点都是中间节点(internal node),不包含子树的节点称为叶子节点(leaf node)。按照是否包含 B+ 树本身需要的信息,节点又可以分为两类,一类节点包含 B+ 树所需要的信息(例如指向数据块位置的指针,同时也包括文件数据),称为格式化节点(formatted node);另外一类只包含文件数据,而不包含格式化信息,称为未格式化节点(unformatted node 或 unfleaf)。因此,所有的中间节点都必须是格式化节点。一个格式化的叶子节点中可以包含多个条目(item,也称为项),所谓条目是一个数据容器,其内容可以保存到一个数据块中,也就是说,一个条目只能隶属于一个数据块,它是节点管理空间的基本单位。

为了方便理解起见,我们可以认为对于 B+ 树来说,一共包含 3 类节点:中间节点(其中保存了对叶子节点的索引信息)、叶子节点(包含一个或多个条目项)和数据节点(仅仅用来存放文件数据)。

熟悉数据结构的读者都会清楚,B+ 树是一棵平衡树,从根节点到达每个叶子节点的深度都是相同的。与 B- 树相比,B+ 树的好处是可以将数据全部保存到叶子节点中,而中间节点中并不存放真正的数据,仅仅用作对叶子节点的索引。为了方便起见,树的深度从叶子节点开始计算,叶子节点的深度为 1,之上的中间节点逐层加 1。在 B+ 树中查找匹配项首先要对关键字进行比较并沿对应指针进行遍历,直至搜索到叶子节点为止;而叶子节点也是按照关键字从小到大的顺序进行排列的。也正是由于这种结构,使得 B+ 树非常适合用来存储文件系统结构。

 


关键字

 

reiferfs 中采用的关键字包含 4 个部分,形式如下:

( directory-id, object-id, offset, type )

这 4 个部分分别表示父目录的 id、本对象的 id、本对象在整个对象(文件)中的偏移量以及类型。关键字的比较就是按照这 4 个部分逐一进行的。读者可能会好奇为什么不简单地采用对象 id 作为关键字。实际上,这种设计是有很多考虑的,采用 directory-id作为关键字的一部分,可以将相同目录中的文件和子目录组织在一起,加快对目录项的存取。offset 的出现是为了支持大文件,一个间接条目(后文中会介绍)最多能指向 (数据块大小-48)/4 个数据块来存放文件数据,在默认的 4KB 数据块中最大只能支持 4048KB 的文件。因此使用 offset 就可以表明该对象在文件中所处的偏移量。type 可以用来区分对象的类型。目目前reiserfs 支持四种类型,TYPE_STAT_DATA、TYPE_INDIRECT 1、TYPE_DIRECT 2、 TYPE_DIRENTRY,解释见后文的条目头部分。

 

在 3.5 之前的版本中,这 4 部分都是 32 位的整数,这样造成的问题是最大只能支持大约 232=4GB 的文件。从 3.6 版本开始,设计人员将 offset 扩充至 60 位,将 type 压缩至 4 位。这样理论上能够支持的最大文件就达到了 260 字节,但是由于其他一些限制,reiserfs 中可以支持的文件上限是 8TB。正是由于这个原因,reiserfs 中有两个版本的关键字,相关定义如清单 2 所示。
清单2. reiserfs 中与关键字有关的定义

                
363 struct offset_v1 {
 364         __le32 k_offset;
 365         __le32 k_uniqueness;
 366 } __attribute__ ((__packed__));
 367 
 368 struct offset_v2 {
 369         __le64 v;
 370 } __attribute__ ((__packed__));
 371        
 372 static inline __u16 offset_v2_k_type(const struct offset_v2 *v2)
 373 {
 374         __u8 type = le64_to_cpu(v2->v) >> 60;
 375         return (type <= TYPE_MAXTYPE) ? type : TYPE_ANY;
 376 }
 377 
 378 static inline void set_offset_v2_k_type(struct offset_v2 *v2, int type)
 379 {
 380         v2->v =
 381             (v2->v & cpu_to_le64(~0ULL >> 4)) | cpu_to_le64((__u64) type << 60);
 382 }
 383 
 384 static inline loff_t offset_v2_k_offset(const struct offset_v2 *v2)
 385 {
 386         return le64_to_cpu(v2->v) & (~0ULL >> 4);
 387 }
 388 
 389 static inline void set_offset_v2_k_offset(struct offset_v2 *v2, loff_t offset)
 390 {
 391         offset &= (~0ULL >> 4);
 392         v2->v = (v2->v & cpu_to_le64(15ULL << 60)) | cpu_to_le64(offset);
 393 }
 394 
 395 /* Key of an item determines its location in the S+tree, and
 396    is composed of 4 components */
 397 struct reiserfs_key {
 398         __le32 k_dir_id;        /* packing locality: by default parent
 399                                    directory object id */
 400         __le32 k_objectid;      /* object identifier */
 401         union {
 402                 struct offset_v1 k_offset_v1;
 403                 struct offset_v2 k_offset_v2;
 404         } __attribute__ ((__packed__)) u;
 405 } __attribute__ ((__packed__));
 406

尽管结构定义中并没有显式地声明 offset 和 type 分别是 60 位和 4 位长,但是从几个相关函数中可以清楚地看到这一点。

 

 


数据块头

 

在图 2 中我们曾经介绍过,b+ 树中的节点可以分为格式化节点和未格式化节点两种。未格式化节点中保存的全部是文件数据,而格式化节点中包含了 b+ 树本身需要的一些信息。为了与未格式化节点区分开来,每个格式化节点所占用的数据块最开头都使用一个数据块头来表示。数据块头大小为 24 个字节,定义如清单 3 所示。
清单3. block_head 结构定义

                
698 /* Header of a disk block.  More precisely, header of a formatted leaf
 699    or internal node, and not the header of an unformatted node. */
 700 struct block_head {
 701         __le16 blk_level;       /* Level of a block in the tree. */
 702         __le16 blk_nr_item;     /* Number of keys/items in a block. */
 703         __le16 blk_free_space;  /* Block free space in bytes. */
 704         __le16 blk_reserved;
 705         /* dump this in v4/planA */
 706         struct reiserfs_key blk_right_delim_key;   /* kept only for compatibility */
 707 };

block_head 结构中的 blk_level 表示该节点在 B+ 树中的层次,对于叶子节点来说,该值为 1;blk_nr_item 表示这个数据块中条目的个数;blk_free_space 表示这个数据块中的空闲磁盘空间。

 

格式化节点可以分为中间节点和叶子节点两类,它们所采用的存储结构是不同的。

 


中间节点

 

中间节点由数据块头、关键字和指针数组构成。中间节点中的关键字和指针数组都是按照从小到大的顺序依次存放的,它们在磁盘上的布局如图 3 所示。
图 3. 中间节点的布局
中间节点的布局

每个关键字就是一个 16 字节的 reiserfs_key 结构,而指针则是一个 disk_child 结构,其大小为 8 个字节,定义如清单 4 所示。
清单4. disk_child 结构定义

                
1086 /* Disk child pointer: The pointer from an internal node of the tree
1087    to a node that is on disk. */
1088 struct disk_child {
1089         __le32 dc_block_number; /* Disk child's block number. */
1090         __le16 dc_size;         /* Disk child's used space.   */
1091         __le16 dc_reserved;
1092 };

其中 dc_block_number 字段是所指向子节点所在的数据块块号,dc_size 表示这个数据块中已用空间的大小。对于一共有 n 个关键字的中间节点来说,第 i 个关键字位于 24+i*16 字节处,对应的指针位于 24+16*n+8*i 字节处。

 

需要注意的是,对于中间节点来说,数据块头的 blk_nr_item 字段表示的是关键字的个数,而指针数总是比关键字个数多 1,这是由 B+ 树的结构所决定的,小于 Key 0 的关键字可以在 Pointer 0 指针指向的数据块(下一层中间节点或叶子节点)中找到,而介于 Key 0 和 Key 1 之间的关键字则保存在 Pointer 1 指向的数据块中,依此类推。大于Key n的关键字可以在Pointer n+1中找到。

 


叶子节点

 

格式化叶子节点的结构比中间节点的结构稍微复杂一点。为了能够在一个格式化叶子节点中保存多个条目,reiserfs 采用了如图 4 所示的布局结构。
图 4. 格式化叶子节点的布局
格式化叶子节点的布局

从图中可以看出,每个格式化叶子节点都以一个数据块头开始,然后是从两端向中间伸展的条目头和条目数据的数组,空闲空间保留在中间,这种设计是为了扩充方便。

所谓条目(item,或称为项)就是可以存储在单个节点中的一个数据容器,我们可以认为条目是由条目头和条目数据体组成的。
清单5. item_head 结构定义

                
460 /*  Everything in the filesystem is stored as a set of items.  The
 461     item head contains the key of the item, its free space (for
 462     indirect items) and specifies the location of the item itself
 463     within the block.  */
 464 
 465 struct item_head {
 466         /* Everything in the tree is found by searching for it based on
 467          * its key.*/
 468         struct reiserfs_key ih_key;
 469         union {
 470                 /* The free space in the last unformatted node of an
 471                    indirect item if this is an indirect item.  This
 472                    equals 0xFFFF iff this is a direct item or stat data
 473                    item. Note that the key, not this field, is used to
 474                    determine the item type, and thus which field this
 475                    union contains. */
 476                 __le16 ih_free_space_reserved;
 477                 /* Iff this is a directory item, this field equals the
 478                    number of directory entries in the directory item. */
 479                 __le16 ih_entry_count;
 480         } __attribute__ ((__packed__)) u;
 481         __le16 ih_item_len;     /* total size of the item body */
 482         __le16 ih_item_location;        /* an offset to the item body
 483                                          * within the block */
 484         __le16 ih_version;      /* 0 for all old items, 2 for new
 485                                    ones. Highest bit is set by fsck
 486                                    temporary, cleaned after all
 487                                    done */
 488 } __attribute__ ((__packed__));

从 item_head 结构定义中可以看出,关键字已经包含在其中了。ih_item_len 和 ih_item_location 分别表示对应条目的数据体的长度和在本块中的偏移量。请注意该结构的第 17、18 个字节是一个联合结构,对于不同类型的条目来说,该值的意义不同:对于 stat 数据条目(TYPE_STAT_DATA)或直接数据条目(TYPE_DIRECT),该值为 15;对于间接数据条目(TYPE_INDIRECT),该值表示最后一个未格式化数据块中的空闲空间;对于目录条目(TYPE_DIRENTRY),该值表示目录条目中目录项的个数。

 

目前 reiserfs 支持的条目类型有 4 种,它们是依靠关键字中的 type 字段来区分的;而在旧版本的关键字中,则是通过 uniqueness 字段来标识条目类型的,其定义如清单 6 所示。
清单6. reiserfs 支持的条目类型

                
346 //      
 347 // there are 5 item types currently
 348 //
 349 #define TYPE_STAT_DATA 0
 350 #define TYPE_INDIRECT 1
 351 #define TYPE_DIRECT 2
 352 #define TYPE_DIRENTRY 3
 353 #define TYPE_MAXTYPE 3
 354 #define TYPE_ANY 15             // FIXME: comment is required
 355  
…
 509 //
 510 // in old version uniqueness field shows key type
 511 //
 512 #define V1_SD_UNIQUENESS 0
 513 #define V1_INDIRECT_UNIQUENESS 0xfffffffe
 514 #define V1_DIRECT_UNIQUENESS 0xffffffff
 515 #define V1_DIRENTRY_UNIQUENESS 500
 516 #define V1_ANY_UNIQUENESS 555   // FIXME: comment is required
 517

下面让我们逐一来了解一下各种条目的存储结构。

 

STAT 条目

stat 数据(TYPE_STAT_DATA)非常类似于 ext2 中的索引节点,其中保存了诸如文件权限、MAC(modified、accessed、changed)时间信息等数据。在3.6 版本的 reiserfs 中,stat 数据使用一个stat_data 结构表示,该结构大小为 44 字节,其定义如清单 7 所示:
清单7. stat_data 结构定义

                
835 /* Stat Data on disk (reiserfs version of UFS disk inode minus the
 836    address blocks) */
 837 struct stat_data {
 838         __le16 sd_mode;         /* file type, permissions */
 839         __le16 sd_attrs;        /* persistent inode flags */
 840         __le32 sd_nlink;        /* number of hard links */
 841         __le64 sd_size;         /* file size */
 842         __le32 sd_uid;          /* owner */
 843         __le32 sd_gid;          /* group */
 844         __le32 sd_atime;        /* time of last access */
 845         __le32 sd_mtime;        /* time file was last modified  */
 846         __le32 sd_ctime;       /* time inode (stat data) was last changed */
                                   /* (except changes to sd_atime and sd_mtime) */
 847         __le32 sd_blocks;
 848         union {
 849                 __le32 sd_rdev;
 850                 __le32 sd_generation;
 851                 //__le32 sd_first_direct_byte;
 852                 /* first byte of file which is stored in a
 853                    direct item: except that if it equals 1
 854                    it is a symlink and if it equals
 855                    ~(__u32)0 there is no direct item.  The
 856                    existence of this field really grates
 857                    on me. Let's replace it with a macro
 858                    based on sd_size and our tail
 859                    suppression policy? */
 860         } __attribute__ ((__packed__)) u;
 861 } __attribute__ ((__packed__));
 862 //
 863 // this is 44 bytes long
 864 //

stat_data 条目使用的关键字中,offset 和 type 的值总是 0,这样就能确保 stat 数据是相同对象(object-id)中的第一个条目,从而能够加快访问速度。

 

与 ext2 的 ext2_indoe 结构对比一下就会发现,stat_data 中既没有记录数据块位置的地方,也没有记录删除时间,而这正是我们在 ext2/ext3 中恢复删除文件的基础,因此可以猜测得到,在reiserfs 文件系统中要想恢复已经删除的文件,难度会变得更大。

目录条目

目录条目中记录了目录项信息。目录条目由目录头和目录项数据(即文件或子目录名)组成。如果一个目录中包含的目录项太多,可以扩充到多个目录条目中存储。为了方便管理某个目录中子目录或文件的增减,目录条目也采用了与条目头类似的设计:从两端向中间扩充,其布局结构如图 5 所示。
图 5. 目录条目存储结构
目录条目存储结构

目录头是一个 reiserfs_de_head 结构,大小为 16 字节,其定义如清单 8 所示。
清单8. reiserfs_de_head 结构定义

                
920 /*
 921    Q: How to get key of object pointed to by entry from entry?  
 922 
 923    A: Each directory entry has its header. This header has 
             deh_dir_id and deh_objectid fields, those are key
 924       of object, entry points to */
 925 
 926 /* NOT IMPLEMENTED:   
 927    Directory will someday contain stat data of object */
 928 
 929 struct reiserfs_de_head {
 930         __le32 deh_offset;      /* third component of the directory entry key */
 931         __le32 deh_dir_id;      /* objectid of the parent directory of the object, 
 932                                    that is referenced by directory entry */
 933         __le32 deh_objectid;    /* objectid of the object, that is referenced */
                                     /* by directory entry */
 934         __le16 deh_location;    /* offset of name in the whole item */
 935         __le16 deh_state;       /* whether 1) entry contains stat data (for future), 
 936                                    and 2) whether entry is hidden (unlinked) */
 937 } __attribute__ ((__packed__));

reiserfs_de_head 结构中包含了 deh_dir_id 和 deh_objectid fields 这两个字段,它们就是其父目录关键字中对应的两个字段。deh_offset 的 7 到 30 位是文件名的 hash 值,0 到 6 位用来解决 hash 冲突的问题(reiserfs 中可以使用 3 种 hash 函数:tea、rupasov 和 r5,默认为 r5)。文件名的位置保存在 deh_location 字段中,而 deh_state 的第 2 位表示该目录条目是否是可见的(该位为 1 则表示该目录条目是可见的,为 0 表示不可见)。文件名是一个字符串,以空字符结束,按照 8 字节对齐。

 

直接条目与间接条目

在 reiserfs 中,文件数据可以通过两种方式进行存取:直接条目(direct item)和间接条目(indirect item)。对于小文件来说,文件数据本身和 stat 数据可以一起存储到叶子节点中,这种条目就称为直接条目。直接条目就采用图 4 所示的存储结构,不过每个条目数据体就是文件数据本身。对于大文件来说,单个叶子节点无法存储下所有数据,因此会将部分数据存储到未格式化数据块中,并通过间接条目中存储的指针来访问这些数据块。未格式化数据块都是整块使用的,最后一个未格式化数据块中可能会遗留一部分剩余空间,大小是由对应条目头的 ih_free_space_reserved 字段指定的。图 6 给出了间接条目的存储结构。
图 6. 间接条目存储结构
间接条目存储结构

对于缺省的 4096 字节的数据块来说,一个间接条目所能存储的数据最大可达 4048 KB(4096*(4096-48)/4 字节),更大的文件需要使用多个间接条目进行存储,它们之间的顺序是通过关键字中的 offset 进行标识的。

另外,文件末尾不足一个数据块的部分也可以像小文件一样存储到直接条目中,这种技术就称为尾部封装(tail packing)。在这种情况下,存储一个文件至少需要使用一个间接条目和一个直接条目。

 


实例分析

 

下面让我们来看一个实际的例子,以便了解 reiserfs 中的实际情况是什么样子。首先让我们来创建一个 reiserfs 文件系统,这需要在系统中安装 reiserfs-utils 包,其中包含的内容如清单 9 所示。
清单9. reiserfs-utils 包中包含的文件

                
[root@vmfc8 reiserfs]# rpm -ql reiserfs-utils
/sbin/debugreiserfs
/sbin/fsck.reiserfs
/sbin/mkfs.reiserfs
/sbin/mkreiserfs
/sbin/reiserfsck
/sbin/reiserfstune
/sbin/resize_reiserfs
/usr/share/doc/reiserfs-utils-3.6.19
/usr/share/doc/reiserfs-utils-3.6.19/README
/usr/share/man/man8/debugreiserfs.8.gz
/usr/share/man/man8/mkreiserfs.8.gz
/usr/share/man/man8/reiserfsck.8.gz
/usr/share/man/man8/reiserfstune.8.gz
/usr/share/man/man8/resize_reiserfs.8.gz

mkreiserfs 命令用来创建 reiserfs 文件系统,debugreiserfs 用来查看 reiserfs 文件系统的详细信息,如清单 10 所示。

 
清单10. 创建 reiserfs 文件系统

                
[root@ vmfc8 reiserfs]# echo y | mkreiserfs /dev/sda2
[root@vmfc8 reiserfs]# debugreiserfs -m /dev/sda2
debugreiserfs 3.6.19 (2003 www.namesys.com)
Filesystem state: consistent
Reiserfs super block in block 16 on 0x802 of format 3.6 with standard journal
Count of blocks on the device: 977952
Number of bitmaps: 30
Blocksize: 4096
Free blocks (count of blocks - used [journal, bitmaps, data, reserved] blocks): 969711
Root block: 8211
Filesystem is clean
Tree height: 2
Hash function used to sort names: "r5"
Objectid map size 2, max 972
Journal parameters:
        Device [0x0]
        Magic [0x28ec4899]
        Size 8193 blocks (including 1 for journal header) (first block 18)
        Max transaction length 1024 blocks
        Max batch size 900 blocks
        Max commit age 30
Blocks reserved by journal: 0
Fs state field: 0x0:
sb_version: 2
inode generation number: 0
UUID: 02e4b98a-bdf3-4654-9cae-89e38970f43c
LABEL: 
Set flags in SB:
        ATTRIBUTES CLEAN
Bitmap blocks are:
#0: block 17: Busy (0-8211) Free(8212-32767)
used 8212, free 24556
#1: block 32768: Busy (32768-32768) Free(32769-65535)
used 1, free 32767
…
#29: block 950272: Busy (950272-950272) Free(950273-977951) Busy(977952-983039)
used 5089, free 27679

从输出结果中可以看出,这是大约是一个 4GB 的分区,总共划分成 977952 个 4096B 大小的数据块;而超级块是第 16 个数据块(从 0 开始计算)。格式化过程中占用了其中的 8241 个数据块,其中从第 18 个数据块开始的 8193 个数据块用于日志(第 8210 数据块供日志头使用)。这个 B+ 树的根节点保存在该分区的第 8211 个数据块中。另外,数据块位图总共占用了 30 个数据块,-m 参数给出了这些数据块位图所占用的数据块的具体位置,这与前文中的介绍是完全吻合的。

 

我们真正关心的是存储实际数据的部分,从中可以了解在 reiserfs 中是如何对文件进行访问的。下面让我们来看一个实际文件系统的例子。
清单11. 分析 8211 数据块的内容

                
[root@vmfc8 reiserfs]# mount /dev/sda2 /tmp/test
[root@vmfc8 reiserfs]# dd if=/dev/sda2 of=block.8211 bs=4096 count=1 skip=8211
[root@vmfc8 reiserfs]# hexdump –C block.8211.hex

block.8211.hex 文件中就是根节点中的实际数据,图 7 给出了一个更为清晰的分析结果。

 
图 7. 新文件系统根节点的数据分析
新文件系统根节点的数据分析

从图 7 中可以清楚地看出,这是一个格式化叶子节点(深度为 1),其中包含 4 个条目:两个是 STAT 条目,另外两个是目录条目。实际上,它们分别是当前目录和其父目录。继续分析就会发现,当前目录中只包含两个目录项:当前目录及其父目录,因此这是一个空目录。

 


小结

 

总体来说,reiserfs 所采用的设计很多都是专门针对如何充分提高空间利用率和改进小文件的访问速度。与 ext2/ext3 不同,reiserfs 并不以固定大小的块为单位给文件分配存储空间。相反,它会恰好分配文件大小的磁盘空间。另外,reiserfs 还包括了围绕文件末尾而专门设计的尾部封装方案,将文件名和文件数据(或部分数据)共同保存在 B+ 树的叶子节点中,而不会像 ext2 那样将数据单独保存在一个磁盘上的数据块中,然后使用一个指针指向这个数据块的位置。

这种设计会带来两个优点。首先,它可以极大地改进小文件的性能。由于文件数据和 stat_data(对应于 ext2 中的 inode)信息都是紧邻保存的,只需要一次磁盘 I/O 就可以将这些数据全部读出。其次,可以有效提高对磁盘空间的利用率。统计表明,reiserfs 采用这种设计之后,可以比相应的 ext2 文件系统多存储超过 6% 的数据。

不过,尾部封装的设计可能会对性能稍有影响,因为它每次文件发生修改时,都需要重新对尾部数据进行封装。由于这个原因,reiserfs 的尾部封装特性被设计为可以关闭的,这样就为管理员在性能和存储效率之间可以提供一个选择。对于性能非常关键的应用程序来说,管理员可以使用 notail 选项禁用这个特性,从而牺牲一部分磁盘空间来获得更好的性能。

与 ext2/ext3 相比,在处理小于 4KB 的文件时,reiserfs 的速度通常会快 10 到 15 倍。这对于新闻组、HTTP 缓存、邮件发送系统以及其他一些小文件性能非常重要的应用程序来说是非常有益的。
参考资料

作者简介

冯锐,软件工程师,目前在 IBM 开发中心从事 AIX 性能测试方面的工作。您可以通过 fengrui@cn.ibm.com 与他联系。

 

丁成,软件工程师,目前在 IBM 开发中心从事 AIX 性能测试方面的工作,您可以通过 dingc@cn.ibm.com 与他联系。

出处:http://www.ibm.com/developerworks/cn/linux/l-cn-filesrc6/index.html

 

发表评论