Reiser文件系统结构(4)

日志(Journal

Reiser文件系统的日志是一些连续的磁盘块,记录了文件系统的所有事务。文件系统每次有修改时,都会把一系列操作(即为了维持文件系统的一致性,必须原子地完成的那些操作)组合成为事务,并首先记录到日志里。在迟一些的时候,这些事务被刷新,并标记为成功。

日志的大小是固定的。在2.4.x版本Linux实现里,日志大小是8192个块,加一个日志头信息块。日志本身包括一个变长的事务列表和一个日志头信息,头信息在日志的最后。一个事务可以有3个块那么大,但是日志的头信息总是1个块大小。整个日志是一个环形缓冲区,即如果写到了最后的块,那么下次就从第一个块开始写。

经常有人认为Reiser文件系统的日志只记录系统元数据,其实不是的。日志的作用的确是保证元数据的完整性。但是Reiser文件系统记录下整个磁盘块的内容,并提交成日志。和目录一样,状态数据和小文件都是直接存储在叶子节点里,同时部分数据还可能存在于日志里,这样可以在适当的时候恢复它们以前的版本。

 

日志头信息(Journal Header

日志头信息占用日志的最后一个块,描述第一个未刷新的事务的位置。在下面的例子里,第一个未刷新的块是18号,并且总共有8192个日志块。所以,日志头信息块编号是8210,日志头信息只占用12字节,块里其他空间没有使用。

 

Name

Size

Description

Last flush ID

4

上次刷新的事务id

Unflushed offset

4

下一个需要刷新的事务所在的块编号

Mount ID

4

已刷新事务的mount ID

offset域指向的事务必须比已刷新的事务有更高的事务idmount id,这样才被认为是未刷新的事务,否则所有的事务都被认为已经刷新,而offset域指向的块只是说明从哪里开始记录新事务的日志。

示例:

00000000 e2 74 02 00 24 1c 00 00 1d 01 00 00 12 00 00 00  ât..$...........

 

Last flush ID: 160994

Unflushed offset: 7204 blocks

Mount ID: 285

在上例里,第一个未刷新的事务位于块7222上(日志始于18号块)。但是由于7222号块上没有日志描述(见下面),所以实际上没有未刷新的块。

 

事务(Transactions

事务描述了文件系统的更改。文件系统树里需要新增或修改块时,不是直接进行,而是首先写到日志里,然后映射到文件系统里的实际位置。

一个事务包含一个事务描述块(transaction description block),一系列数据块,和最后的提交块(commit block)。所有的块在日志里都是连续的。

 

描述块(Description block

描述块里包含事务和mount id,事务拥有的数据块数,一个魔幻数,和(部分)块映射。

 

Name

Size

Description

Transaction ID

4

事务id

Len

4

事务的数据块数

Mount ID

4

Mount ID

Real blocks

Block size - 24

事务的块映射

Magic

12

魔幻数,“ReIsErLB

Real blocks”域的大小理论上依赖于块大小。头12个字节是事务id和长度,最后12个字节是魔幻数,中间的部分就是块映射区“Real blocks”。不过,在Linux版本2.4.x实现里,描述块的定义有:

__u32 j_realblock[JOURNAL_TRANS_HALF];

JOURNAL_TRANS_HALF是值为1018的常数,所以决定了Linux下块大小必须是4096

实际的块映射是这样的:“Real blocks”域是一个块编号数组,把事务里的每个数据块映射到文件系统里的某个块上。如果我们把数组里的每个值编号为r0r1...rn,那么事务的第0个数据块包含了块r0修改后的数据,第1个数据块包含块r1修改后的数据,以此类推。如果这里的“Real blocks”域不够用,那么会使用提交块(见下面)里的空间作为补充。一个事务最多能有2×(块大小 - 24/ 4个数据块(块大小为4K时结果为2036),但是超级块里会作出实际的限制。

 

提交块(Commit block

提交块是一个事务的结尾,它包含事务id和长度的副本,块的最后有一个16字节的保留域,暂时没有使用。块的其他空间用于存储前面放不下的块编号映射。

 

Name

Size

Description

Transaction ID

4

事务id

Len

4

事务的数据块数

Real blocks

Block size - 24

事务的块映射

Digest

16

事务所有块的摘要,未使用

示例:

下面描述了一个过时了的事务,起始块是7243(描述块),4个数据块(7244-7247),结束块是7248(提交块)。这里只显示了描述块,其他块的数据不重要。

00000000 1b 6e 02 00 04 00 00 00 1b 01 00 00 90 22 00 00  .n..........."..

00000010 07 f7 00 00 aa 22 00 00 10 00 00 00 00 00 00 00  .÷..ª"..........

00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

...

00000ff0 00 00 00 00 52 65 49 73 45 72 4c 42 00 00 00 00  ....ReIsErLB....

 

Transaction ID: 159259

Length: 4 blocks

Mount ID: 283

Real blocks[0]: 8848

Real blocks[1]: 63239

Real blocks[2]: 8874

Real blocks[3]: 16

Magic: ReIsErLB

这个例子里的块映射是:当事务提交/刷新的时候,把块7244写到块8848,块7245写到块63239,块7246写到块8874,块7247写到块16(超级块)。

注意:很多操作都需要更新超级块,比如空闲块数目更新了,或文件系统树的高度改变了。所以超级块的副本在很多日志事务里都能找到,这样如果通过匹配标志字符串(比如“ReIsEr2Fs”)来找寻超级块,可能会找到很多其他的块。实际上超级块“应该”是这些含有标志字符串的块中的头一个。

 

Reiser文件系统总结

为了能访问到特定的文件,我们必须搜索文件系统的目录树。Reiser文件系统的根目录(root directory)的key总是{1200}。目录下的文件的key可以在目录项的头信息里找到。由于Reiser文件系统的key把目录id放在首位,同一个目录下的对象会组织到一起。这样就使key的搜索更加局域化(locally),而不需要每次都从根目录开始。

key {ab00}总是表示状态项,紧接着就是它描述的目录或文件对象。状态项里有目标对象的字节大小,再利用后续项头信息里的大小信息,可以重建起目标目录/文件的所有key,和每部分的位置。在大部分情况下,这些项在磁盘上是连续存放的。

下面的3个例子显示了3中不同的文件:非常小的文件,只有一个状态项和尾数据;大一些的文件,有一个间接项;和非常大的文件,跨越几个间接项。我们仍用前面例子里使用的分区,它是SuSe Linux 8.0系统上装载到“/var”下的一个Reiser文件系统分区。

 

例子1:小文件

第一个例子的文件只包含一个状态项(stat item)和一个直接项(direct item)。文件名是“/var/log/y2start.log-initial”。根目录“/var”的key{1200},存储于块8416。文件夹“log”的key{21300},也存储于块8416上。文件“y2start.log-initial”的key{13163300}。通过查看块8482,我们发现这个key存储于叶子节点24224上。key {13163300}{13163312}的头信息如下:

00000090 0d 00 00 00 61 06 00 00 00 00 00 00 00 00 00 00  ....a...........

000000a0 ff ff 2c 00 a4 0b 01 00 0d 00 00 00 61 06 00 00  ÿÿ,.¤.......a...

000000b0 01 00 00 00 00 00 00 20 ff ff f0 00 b4 0a 01 00  ....... ÿÿð.´...

Key: {13, 1633, 0, 0}

Count: 0xffff

Length: 44 bytes

Location: byte 2980 (0xba4)

Version: 1 (new)

Key: {13, 1633, 1, 2}

Count: 0xffff

Length: 240 bytes

Location: byte 2740 (0xab4)

Version: 1 (new)

该块(24224)的偏移2740 (0xab4)字节处,我们找到了文件的直接项(状态项位于2980 (0xba4)字节处):

00000ab0             65 6e 76 0a 65 63 68 6f 20 59 32 44      env.echo Y2D

00000ac0 45 42 55 47 20 28 29 0a 6d 65 6d 69 6e 66 6f 20  EBUG ().meminfo

00000ad0 31 20 3d 20 4d 65 6d 3a 20 31 30 33 33 34 35 36  1 = Mem: 1033456

00000ae0 20 38 35 39 37 36 20 39 34 37 34 38 30 20 30 20   85976 947480 0

00000af0 36 34 32 34 20 35 37 31 37 32 0a 69 53 65 72 69  6424 57172.iSeri

00000b00 65 73 3d 31 0a 68 76 63 5f 63 6f 6e 73 6f 6c 65  es=1.hvc_console

00000b10 3d 31 0a 58 31 31 69 3d 0a 4d 65 6d 54 6f 74 61  =1.X11i=.MemTota

00000b20 6c 3d 31 30 33 33 34 35 36 0a 66 62 64 65 76 5f  l=1033456.fbdev_

00000b30 6f 6b 3d 31 0a 75 70 64 61 74 65 3d 0a 58 56 65  ok=1.update=.XVe

00000b40 72 73 69 6f 6e 3d 34 0a 58 53 65 72 76 65 72 3d  rsion=4.XServer=

00000b50 66 62 64 65 76 0a 78 73 72 76 3d 58 46 72 65 65  fbdev.xsrv=XFree

00000b60 38 36 0a 73 63 72 65 65 6e 3d 66 62 64 65 76 0a  86.screen=fbdev.

00000b70 6d 65 6d 69 6e 66 6f 20 32 20 3d 20 4d 65 6d 3a  meminfo 2 = Mem:

00000b80 20 31 30 33 33 34 35 36 20 39 32 34 30 34 20 39   1033456 92404 9

00000b90 34 31 30 35 32 20 30 20 38 32 33 32 20 36 30 35  41052 0 8232 605

00000ba0 31 36 0a 00 a4 81 05 00 01 00 00 00 ef 00 00 00  16..¤.......ï...

00000bb0 00 00 00 00 00 00 00 00 00 00 00 00 25 15 3e 3d  ............%.>=

00000bc0 25 15 3e 3d 25 15 3e 3d 08 00 00 00 d5 02 00 00  %.>=%.>=....Õ...

Mode: S_IFREG (regular file), -rw-r--r--

Num. links: 1

Size: 239

UID: 0

GID: 0

A/M/Ctimes: 07/23/2002 21:47:01

Blocks: 8

Gen: 725

值得注意的是,状态项显示了文件的实际大小为239字节,而直接项却占用了块内的240字节,所以第2979 (0xba3)字节并不是文件的数据。这个差异也许可以用实现中的字节对齐来解释。

 

例子2:有间接项的文件

文件“"/var/log/SaX.log”有7121字节长,不能放在一个直接项里,必须分开到2个无格式块里,并由一个间接项来描述。文件的key{13, 1490, 0, 0},从块8482里我们找到它位于叶子节点27444里。对象头信息如下:

00000040                         0d 00 00 00 d2 05 00 00          ....Ò...

00000050 00 00 00 00 00 00 00 00 ff ff 2c 00 a4 0b 01 00  ........ÿÿ,.¤...

00000060 0d 00 00 00 d2 05 00 00 01 00 00 00 00 00 00 10  ....Ò...........

00000070 00 00 08 00 9c 0b 01 00                              ........

Key: {13, 1490, 0, 0}

Count: 0xffff

Length: 44 bytes

Location: byte 2980 (0xba4)

Version: 1 (new)

Key: {13, 1490, 1, 1}

Count: 0

Length: 8 bytes

Location: byte 2972 (0xb9c)

Version: 1 (new)

间接块和状态项为:

00000b90                                     12 52 00 00              .R..

00000ba0 13 52 00 00 a4 81 05 00 01 00 00 00 d1 1b 00 00  .R..¤.......Ñ...

00000bb0 00 00 00 00 00 00 00 00 00 00 00 00 3f aa 4a 3d  ............?ªJ=

00000bc0 bd aa 4a 3d bd aa 4a 3d 10 00 00 00 54 05 00 00  ½ªJ=½ªJ=....T...

Mode: S_IFREG (regular file), -rw-r--r--

Num. links: 1

Size: 7121

UID: 0

GID: 0

C time: Fri Aug 2 10:50:23 2002

M/Atimes: Fri Aug 2 10:52:29 2002

Blocks: 10

Gen: 1364

Block 1: 21010

Block 2: 21011

该文件的数据位于块2101021011上。块21010上有4096字节,块21011上只有3025字节。(译注:前面第一个key代表状态项,起始位置是0xba4字节;第二个key代表间接项,起始位置是0xb9c字节。注意叶子节点里,key的顺序与项的顺序是相反的。不过有几点疑问。前面说过间接项头信息里Count域“表示最后一个无格式块的空闲字节数”,但是此处等于0;状态项里的“Blocks”域是“文件占用的块数”,此处值为10,不知是怎么算的。

 

例子3:大文件

文件“"/var/lib/rpm/fileindex.rpm”的大小超过了11MB,单个间接项已经不能描述它了。它的key{4, 7, 0, 0},位于块16822里。不过块16822只包含了它的状态项。它的间接项分布于3个块里:key {4, 7, 1, 1}在块13286里,key {4, 7, 4145153, 1}在块20171里,key {4, 7, 8290305, 1}在块20987里。块13286里的间接项为:

00000010                         04 00 00 00 07 00 00 00          ........

00000020 01 00 00 00 00 00 00 10 00 00 d0 0f 30 00 01 00  ..........Ð.0...

Key: {4, 7, 1, 1}

Count: 0

Length: 4048 bytes

Location: byte 48 (0x30)

Version: 1 (new)

接下来是1012个指向无格式块的指针,块20171的结构也是这样。块20987上的间接项只有830个指针,占用3320字节。注意这3key里的offset域是怎样来的:

1 + (1012 pointers * 4096 bytes blocksize) = 4145153

4145153 + (1012 pointers * 4096 bytes blocksize) = 8290305

  

原文《The structure of the Reiser file system

http://homes.cerias.purdue.edu/~florian/reiser/reiserfs.php

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章