叶子节点(Leaf nodes)
叶子节点处于S+树的最底层,除了间接项(indirect items),所有的数据都存储在叶子节点里。叶子节点由头信息,对象头信息和对象本身组成:
注意空闲区间是在块的中间,而且对象本身按反序排列,这样做的好处是加入新对象时不需要移动任何已有的对象。新对象的头信息加到头信息队列的后面,新对象本身加到对象队列的前面。同样注意对象本身可以是变长的。
对象头信息(Item Headers)
对象头信息里包含对象的key,块内的位置和大小。对象的类型用key来判断。
Name | Size | Description |
Key | 16 | 对象的key |
Count | 2 | 如果是间接项,表示最后一个无格式块的空闲字节数 如果是状态项或直接项,为0xffff 如果是目录项,表示目录下的文件数目(包括目录) |
Length | 2 | 对象的总字节数 |
Location | 2 | 对象本身在块内的偏移量 |
Version | 2 | 0表示老版本key,1表示新版本 |
注意Length表示本块内对象的字节数,而不是整个文件的大小。另外,虽然Reiser文件系统的源码注释里说新对象的版本号是2,但是常量KEY_FORMAT_3_6却定义成1,而它才是赋给Version的值。
示例:
下面是一个状态项的头信息,key是{2,14,0,0},前面说明版本2(文件系统版本3.6)的key时也使用过这个例子。
00000000 02 00 00 00 0e 00 00 00 00 00 00 00 00 00 00 00 ................
00000010 ff ff 2c 00 d4 0f 01 00 ÿÿ,.Ô...
Key: {2, 14, 0, 0}
Count: 0xffff
Length: 44 bytes
Location: byte 4052
Version: 1 (3.6)
对象(Items)
对象包含实际的数据。共有4中类型的对象:状态项(stat items),目录项(directory items),直接项(direct items)和间接项(indirect items)。文件由一个或多个直接项或间接项组成,取决于文件的大小。每个文件或目录前面都有一个状态项。
状态项(stat items)
状态项包含文件或目录的元数据,它的key的offset域和type域总是0,这样同一个inode节点里,状态项总是在其他对象的前面。由于有2种key版本,状态项的key也有2种版本,即offset域从32 bits增加到60 bits。同时由于某些原因,硬链接(hard links),用户id(user id)和组id(group id)都从原来的16 bits增加到了32 bits,还引入了一些新的域,于是状态项从版本3.5下的32字节,增加到了3.6版本下的44字节。
版本1的状态项结构如下:
Name | Size | Description |
Mode | 2 | 文件类型和权限 |
Num links | 2 | 硬链接数 |
UID | 2 | 用户id |
GID | 2 | 组id |
Size | 4 | 文件字节大小 |
Atime | 4 | 最后访问时间 |
Mtime | 4 | 最后修改时间 |
Ctime | 4 | 状态数据最后修改的时间 |
Rdev/blocks | 4 | 设备号 / 文件占用的块数 |
First dir. byte | 4 | 存储在直接对象(direct item)里的文件的第一个字节 如果为1,表示是符号链接(symlink) 如果为0xffffffff,表示没有直接对象 |
Name | Size | Description |
Mode | 2 | 文件类型和权限 |
Reserved | 2 | 保留 |
Num links | 4 | 硬链接数 |
Size | 8 | 文件字节大小 |
UID | 4 | 用户id |
GID | 4 | 组id |
Atime | 4 | 最后访问时间 |
Mtime | 4 | 最后修改时间 |
Ctime | 4 | 状态数据最后修改的时间 |
Blocks | 4 | 文件占用的块数 |
Rdev/gen/first | 4 | 设备号 / 文件的衍生代数 / 存储在直接对象(direct item)里的文件的第一个字节 如果为1,表示是符号链接(symlink) 如果为0xffffffff,表示没有直接对象 |
Mode域指明了文件的类型和访问权限,低9 bits(3个8进制数)包含所有用户,组和拥有者的访问权限,接下来3 bits(从低到高)分别是粘住位(sticky bit),set GID bit和set UID bit。最高的4 bits包含文件类型。在Linux系统里,可能的文件类型定义如下(在stat.h文件里):
Constant Name | 16-bit Mask | 4-bit value | Description |
S_IFSOCK | 0xc000 | 12 | socket |
S_IFLNK | 0xa000 | 10 | symbolic link |
S_IFREG | 0x8000 | 8 | regular file |
S_IFBLK | 0x6000 | 6 | block device |
S_IFDIR | 0x4000 | 4 | directory |
S_IFCHR | 0x2000 | 2 | character device |
S_IFIFO | 0x1000 | 1 | fifo |
其他操作系统可能有更多的文件类型。只有普通文件(regular file)和目录(directory)包含其他对象(items),并且关联到对应的状态项。其他的文件类型只包含状态项一个对象。
“Rdev”域应用于特殊文件,即除去普通文件(S_IFREG)、目录(S_IFDIR)和链接(S_IFLNK)之外的文件类型,用于保存文件的设备号。“gen”域正好应用于非特殊的文件,即普通文件、目录和链接,用于记录inode的衍生代数(见以前超级块的generation域介绍)。“first”域在Reiser文件系统3.6里不再使用。
示例:
下面显示了前面示例里key {2,14,0,0}的状态项:
00000000 ff 43 05 00 03 00 00 00 50 00 00 00 00 00 00 00 ÿC......P.......
00000010 00 00 00 00 00 00 00 00 2d 1c 17 3f 34 94 ff 3e ........-..?4.ÿ>
00000020 34 94 ff 3e 01 00 00 00 00 00 00 00 4.ÿ>........
Mode: 0x43ff -- type: directory, sticky bit set, 777 permissions
Reserved: 5
Num. links: 3
Size: 80 bytes
UID: 0
GID: 0
Atime: Thu Jul 17 16:59:09 2003
Mtime: Sun Jun 29 20:36:52 2003
Ctime: Sun Jun 29 20:36:52 2003
Blocks: 1
First: 0
目录项(Directory Items)
目录项描述一个目录。如果一个目录下有太多的项(entries),可能需要分好几个目录项来存储,并使用key的offset域区分。目录项由目录头信息和文件名列表组成。和叶子节点一样,目录项的空闲区域也在中间。下面是一个目录项的结构:
目录头信息包含偏移offset,key的前2个域(目录id和对象id),文件名在块内的位置location,和状态state。
Name | Size | Description |
Offset | 4 | hash值和衍生代数 |
Dir ID | 4 | 父目录的对象id |
Object ID | 4 | 对象id |
Location | 2 | 文件名在块内的位置 |
State | 2 | bit 0表示包含状态数据(未使用) bit 2 表示该项是可见(1)还是隐藏(0) |
文件名是以‘/0’结尾的简单ASCII字符串,似乎是按照8字节对齐的,实际情况以源代码为准。(译注:此处可能是因为Reiser文件系统不同版本有不同的实现。按照另一篇文章《The ReiserFS filesystem》的介绍,目录项里的文件名不是以‘/0’结尾,而是直接串接起来,需要根据前一个文件名的起始位置来判断结束位置。此外文件名也没有8字节对齐。)“offset”域名不符实,因为它包含的是文件名的hash值。bits 7到30是实际的hash值,bits 0到6是当同一个目录下2个文件名hash到同一个值时产生的generation number。bit 31未使用。hash值用于在Reiser文件系统里进行实际的文件或目录查找,目录里的项都按照offset进行排序。可能有3个hash函数:keyed tea hash,rupasov hash,和r5 hash。hash函数的目标是根据字符串算出尽可能少碰撞的值。在Linux系统的Reiser实现里,r5 hash似乎是默认的函数。
示例:
00000000 01 00 00 00 02 00 00 00 0e 00 00 00 48 00 04 00 ............H...
00000010 02 00 00 00 01 00 00 00 02 00 00 00 40 00 04 00 ............@...
00000020 00 6d 6f 73 0e 00 00 00 60 00 00 00 30 00 04 00 .mos....`...0...
00000030 76 69 2e 72 65 63 6f 76 65 72 00 00 00 00 00 00 vi.recover......
00000040 2e 2e 00 00 00 00 00 00 2e 00 00 00 00 00 00 00 ................
Header 0: {hash 0, gen. 1, 2, 14, byte 0x48, 4 (bit 2 set: visible)}
Header 1: {hash 0, gen. 2, 1, 2, byte 0x40, 4 (bit 2 set: visible)}
Header 2: {hash 15130330, gen. 0, 14, 96, byte 0x30, 4 (bit 2 set: visible)}
Name 2: "vi.recover"
Name 1: ".."
Name 0: "."
可以看到,key {2,14,0,0}对应的目录下有3个项(entries),分别是(这些key对应的都是文件或目录的状态项):
. {2, 14, 0, 0}
.. {1, 2, 0, 0}
vi.recover {14, 96, 0, 0}
直接项(Direct Items)
直接项包含小文件的整个数据,或者大文件的尾部数据。对于小文件,所有其他的信息都可以在对象头信息和对应的状态项里找到。对于大文件的尾部数据,直接项的key是整个文件所拥有的最后一个key。
间接项(Indirect Items)
间接项包含指向文件的无格式块的指针。每个指针4字节,包含块的编号。一个间接项块可以包含(块大小 - 48)/ 4个指针(48字节用于块和对象头信息)。当块大小是4096字节时,单个间接项可以表示4145152字节(4048KB:1012个4K的块);更大的文件用多个间接项表示,使用key里的offset域进行区分,最后可能还有一些尾数据。