目录
一,CentOS下Xfs文件系统的安装
二,准备工作
三,AG结构
四,AG超级块
五,AG空闲磁盘空间管理
六,ABTB的B+tree
七,ABTB/ABTC的节点块管理
八,inode节点管理
九,inode节点结构
十,普通文件数据结构(extents)
十一,目录文件数据结构
十二,总结
一,CentOS下Xfs文件系统的安装
Xfs早已经合到了Linux内核主线,所以在Linux下对Xfs的使用比较简单,但是CentOS默认并没有安装Xfs的相关用户层工具,所以这里记录一下,后续将对Xfs做更深层次的研究。
在VM虚拟机里先添加了一块128G的硬盘,然后创建了一个分区:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
安装Xfs的用户层工具,然后试试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
二,准备工作
对于Xfs文件系统的磁盘布局分析内容会比较多,所以这将是一系列的文章,争取在元旦三天内写完,由于对于Xfs系统的接触刚刚开始,看其代码和文档也就是前几天的事情,所以由于这些文章都只是我目前的理解,如果有错,后续会进行修正。这是本系列第一篇文章,主要介绍准备工作。
首先是Linux系统,要确保内核的XFS选项是打开的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
其次是Xfs应用层工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
接着是磁盘,在虚拟机上再挂一个4G的磁盘,然后建立一个分区,并挂载到系统上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
|
需要说明的是Xfs文件系统磁盘布局受很多mount参数的影响,而这里全部采用默认值,也就是最主要的两个参数sector为512字节,block为4096字节,当然,磁盘大小为4G。先简单测试一下,OK,一切搞定,只等开始分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
参考资料:
源码下载:Getting the latest source code - xfs.org
Xfs文件系统结构(本系列文章参考的最主要资料,但该文档有些陈旧,部分内容已经与Xfs现状不符):http://xfs.org/docs/xfsdocs-xml-dev/XFS_Filesystem_Structure//tmp/en-US/html/index.html
三,AG结构
Xfs文件系统会把磁盘分成多个同等大小的块组(Allocation Groups,简称AG)来进行管理,而对于一块容量在128MB到4TB之间的磁盘,其将被分为4个AG(见函数:calc_default_ag_geometry;注意:文章里所有提到的函数、结构体以及代码片段等,除非有特殊说明,否则都是应用程序xfsprogs里的代码)。每一个AG的磁盘布局结构完全一致,它们各自管理着自己的磁盘空间,但是第0块AG被称为主AG(primary AG),有一些全局统计信息的使用主要是从该AG内获取,而其它AG保存的信息仅做备份。
一块AG的布局结构如下图所示:
1,AG的第0块block存放着xfs_sb、xfs_agf、xfs_agi、xfs_agfl这四个信息块,而每一个信息块占去一个sector,所以剩余2048空闲;
2,接下来的3块block都是存放的btree,但是设定的类型各不一样,分别为ABTB、ABTC、IABT(见宏block_to_bt与全局静态变量btrees);
3,再接下来的4块block保留使用,不能用来存放任何普通用户数据。保留起来做什么呢?总不能白白浪费吧?是用来存放前面第2点中所提到的ABTB和ABTC的,因为随着磁盘的使用,不可能1块ABTB或ABTC就足够用了,而这4块block在一开始就为此目的而保留下来了。
4,再接下来就是inode节点的存放了,Xfs下inode节点的分配总是以64个为单位来进行,64个inode形成chunk再被前面的IABT btree管理起来。
5,最后接下来的磁盘空间就没有固定格式了,可以用来存放任何类型的数据。
实践验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
|
四,AG超级块
AG超级块内保存着很多非常重要的信息,想要了解一个被格式化为Xfs的磁盘分区就要从AG超级块着手,其对应的结构体为xfs_sb_t,下面就来分析并验证这其中的一些重要相关字段。
查看AG超级块内的数据,我们仍然可以利用hexdump命令来查看,不过为了方便,这里使用xfsprogs提供的xfs_db工具:
[root@localhost xfsprogs]# ./db/xfs_db /dev/sdb1
xfs_db> sb 0
xfs_db> p
magicnum = 0x58465342
魔术数,即是‘XFSB’。
blocksize = 4096
逻辑块大小4096,单位字节。
dblocks = 1048233
磁盘总逻辑块数,1048233*4096/1024/1024/1024 = 3.998691558837890625,即约为4G,正好是我挂载的磁盘大小。
rblocks = 0
rextents = 0
uuid = d008f5d2-7b96-4ae0-b6b5-3b3ecd76536b
logstart = 524292
rootino = 128
根节点inode号,我这里把磁盘挂载在/home/lenky/xfs/sdb1文件夹下,所以这个rootino就是这个sdb1的inode节点号:
1 2 3 4 5 |
|
rbmino = 129
rsumino = 130
rextsize = 1
agblocks = 262059
每一个AG的块数目,agblocks = dblocks/agcount,即262059 = 1048233/4;向上取整。
agcount = 4
磁盘的总AG数目。
rbmblocks = 0
logblocks = 2560
versionnum = 0xb4b4
所以超级块有多种类型,用该字段来区分。
sectsize = 512
一个sector的大小,默认512。
inodesize = 256
一个inode的大小,之前提到inode都是以64个为单位进行分配和管理,所以一次需要4个block,即是256*64/4096 = 4。
inopblock = 16
每一个block可以存储的inode数目,16 = 4096/256。
fname = “\000\000\000\000\000\000\000\000\000\000\000\000″
文件系统的名称,可以在格式化时指定,但不要超过12个字节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
blocklog = 12
块block大小的log2数值,即是log2(4096) = 12。
sectlog = 9
sector大小的log2数值,即是log2(512) = 9。
inodelog = 8
inode大小的log2数值,即是log2(256) = 8。
inopblog = 4
每一个block可以存储的inode数目的log2数值,即是log2(16) = 4。
agblklog = 18
每一个AG的块数目的log2数值,即是log2(262059) = 18,向上取整。
rextslog = 0
inprogress = 0
imax_pct = 25
icount = 7296
ifree = 82
fdblocks = 889105
frextents = 0
uquotino = 0
gquotino = 0
qflags = 0
flags = 0
shared_vn = 0
inoalignmt = 2
unit = 0
width = 0
dirblklog = 0
logsectlog = 0
logsectsize = 0
logsunit = 1
features2 = 0xa
bad_features2 = 0xa
xfs_db>
其它未说明的字段是我目前为关心到的字段,后续看到对应的部分后再补上。
五,AG空闲磁盘空间管理
Xfs文件系统的AG空闲磁盘空间管理信息保存的位置紧接在AG超级块后面,其对应的数据结构体为xfs_agf_t,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Xfs文件系统使用两个B+tree来对空闲磁盘空间进行管理,而这两个B+tree又分别根据空闲block块号和空闲block块数(如果空闲block块数相等,则再按空闲block块号,都是升序)来对空闲磁盘进行跟踪,因此,结构体xfs_agf_t中最重要的两个字段也就分别是agf_roots和agf_levels,这两个字段都是数组,数组元素个数都为2,分别表示ABTB(对应0号元素)和ABTC(对应1号元素)的根节点(agf_roots)和树深度(agf_levels):
1 2 3 4 5 6 7 8 9 |
|
我们仍然先来看实例数据,这样便于理解和记忆:
[root@localhost xfsprogs]# ./db/xfs_db /dev/sdb1
xfs_db> agf 0
xfs_db> p
magicnum = 0x58414746
魔术数,即是‘XAGF’。
versionnum = 1
版本号,这里由宏XFS_AGF_VERSION定义,数值为1。
seqno = 0
agf的序号,第一块AG的agf序号为0,第二块AG的agf序号为1,……
1 2 3 4 5 6 7 |
|
length = 262059
本AG内的block块数,前面提到过Xfs会把磁盘等分为多块AG,但不能保证完全的等分(比如不能整除),所以最末一块AG的block块数不一定和前面的AG的block块数完全相等,但也不会差太多。
1 2 3 4 5 6 7 8 9 10 |
|
bnoroot = 1
类型为ABTB的B+tree的根节点所在的块号,前面提到AG的第0块block存放着xfs_sb、xfs_agf、xfs_agi、xfs_agfl这四个信息块,并且还剩余了2048空闲,而第1块block存放的就是ABTB,其对应的块号由这个字段指明。
cntroot = 2
和上一个字段类似,指示类型为ABTC的B+tree的根节点所在的块号。
bnolevel = 1
类型为ABTB的B+tree的树深度为1。
cntlevel = 1
类型为ABTC的B+tree的树深度为1。
flfirst = 0
该字段后面再提。
fllast = 3
该字段后面再提。
flcount = 4
该字段后面再提。
freeblks = 192061
本AG内空闲块数目。一块新建立的AG,其块数目可以做下试验,/dev/loop0是一块新的磁盘(见前面的操作),格式成Xfs文件系统,但没有任何文件数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
可以看到,一块新格式化的Xfs文件系统,其primary AG的freeblks = agblocks-12,而其它AG的freeblks = agblocks-8,这根据前面文章《Xfs文件系统磁盘布局之二:AG结构》中的图和说明不难理解,另外可以看出除了primary AG的其它AG并没有一开始就分配inode chunk。
longest = 192061
B+tree树内节点最长的块数目,当前只有一个根节点,并且根节点包含了所有的空闲块,所以longest就等于freeblks。
btreeblks = 0
被用来存放ABTB和ABTC这两种类型B+tree的树节点的block块数,但不包括根节点所占的块,即不包括第1块和第2块。因为当前B+tree的树深度为1,即是没有其它节点占用block,所以btreeblks的值当然为0。
xfs_db>
六,ABTB的B+tree
关于B+tree数据结构不是本系列文章讨论的内容,其基本概念可以参考:http://en.wikipedia.org/wiki/B%2Btree。我们仍然先直接把实例数据给弄出来,这样便于分析和理解。新建一块磁盘并格式化,然后弄些数据上去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
|
这是一个树深度为1的B+tree,就先分析它吧。ABTB类型的B+tree对应的结构体为xfs_btree_block,而ABTB其中的union是使用的“short form pointers”,这从全局静态变量btrees可以看出,另一侧面也说明了块号都是相对本AG而言的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
|
分析对应的字段数据:
xfs_db> fsblock 1
xfs_db> type bnobt
xfs_db> p
magic = 0x41425442
魔术数,即是‘ABTB’。
level = 0
0表示这是一个叶子节点,1表示为中间节点。
numrecs = 49
本叶子节点实际所跟踪的“空闲block块”,这些跟踪数据通过一个数组的形式链接起来,所以这个字段指定该数组的实际大小。
leftsib = null
叶子节点会以双向链表的形式链接起来,这里左节点为空。
rightsib = null
叶子节点会以双向链表的形式链接起来,这里右节点也为空。
recs[1-49] = [startblock,blockcount] 1:[12,2] 2:[18,1] 3:[25,3] 4:[32,4] 5:[40,4] 6:[48,6] 7:[58,2] 8:[64,2] 9:[68,4] 10:[76,4] 11:[83,3] 12:[98,2] 13:[104,77] 14:[189,40] 15:[233,36] 16:[270,9] 17:[565,63] 18:[637,62] 19:[701,2] 20:[705,32] 21:[740,40] 22:[782,100] 23:[886,13] 24:[900,8] 25:[910,4] 26:[915,3] 27:[919,2] 28:[922,4] 29:[928,132] 30:[1063,1080] 31:[2163,1528] 32:[3711,1722] 33:[5457,570] 34:[6061,74] 35:[6185,1] 36:[6194,8] 37:[6206,4006] 38:[10266,61] 39:[10369,3938] 40:[14322,304] 41:[14634,2] 42:[14640,36] 43:[14677,1] 44:[14680,21] 45:[14702,80] 46:[14784,75] 47:[14888,59] 48:[14960,57] 49:[15018,1366]
本叶子节点实际所跟踪的“空闲block块”,这些跟踪数据通过一个数组的形式链接起来,这个字段指定该数组的实际值。
xfs_db>
布局图示如下:
可以直接hexdump磁盘对比一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
继续验证空闲块的管理,这里我们先拷贝个新的磁盘(关联到loop2),便于对比,先挂载这个loop2磁盘,并写入一个文件,然后再卸载(保证数据已经写到磁盘上),具体操作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
再来看一下ABTB:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
对比上一篇的ABTB数据,只有两处不同:
freeblks = 15653
freeblks = 15652
与
recs[1-49] = [startblock,blockcount] 1:[12,2] …
recs[1-49] = [startblock,blockcount] 1:[13,1] …
即第12(编号从0开始)块block被占用了,所以总空闲块数(freeblks)少了一块,并且空闲块链里的[12,2]变成了[13,1],看一下第12块block的具体数据是什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
刚好是文件lenky.xfs里的内容,这说明这块原本的空闲block被用来存放文件lenky.xfs了。一个文件还有inode信息,为什么没有看到lenky.xfs的inode占去空闲块呢?因为之前提到过inode总是以64为chunk进行分配和释放,所以lenky.xfs的inode正好利用到了之前申请的但尚未使用的inode空间。
有了上面的这些基础认识,再来看多层(即树深度不为1)的情况。要找一个多层的实例,如果有现成的使用了很久的Xfs文件系统分区就最好了,没有的话就可以这样尝试自己弄一个,我这里是先尽量把4G的磁盘写满,然后再随机的删一大部分,这样让它的Btree打乱,以形成多层结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
从上面的数据可以看出,ABTB类型的B+tree深度为2,而根节点所在的block块号为5:
bnoroot = 5
bnolevel = 2
1 2 3 4 5 6 7 8 9 10 11 |
|
根节点的level为1,表示这是一个中间节点,前面提到过叶子节点里数据的布局,而对于中间节点,多了“指针”字段,如下,图中xfs_alloc_rec即为xfs_alloc_rec_t,xfs_alloc_key即为xfs_alloc_key_t,xfs_alloc_ptr即为xfs_alloc_ptr_t,后面说明类此:
对于中间节点,存放“指针”字段xfs_alloc_ptr的开始地址并不是紧接在xfs_alloc_key[bb_numrecs – 1]之后,如果紧接着xfs_alloc_key[bb_numrecs – 1],一旦数目增加或减少(即bb_numrecs发生改变),那么岂不是要向前或者向后移动xfs_alloc_ptr的数据?对于xfs_alloc_ptr[0]的起始地址,通过函数btblock_ptr_offset可以看出端倪。通过计算出最大可能的bb_numrecs值,即xfs_alloc_ptr前面最大可能有多少个xfs_alloc_key,预留出需要的最大的空闲地址之后,就是xfs_alloc_ptr的开始地址,具体计算是这样:
一个节点占用一个block,这里默认的block大小为4096。
减去前面xfs_btree_block所占的空间,由于ABTB为“short form block”,即是XFS_BTREE_SBLOCK_LEN=16,那么,4096-16=4080。
最大可能的bb_numrecs值为:4080/(sizeof(xfs_alloc_key)+sizeof(xfs_alloc_ptr))=4080/(8+4)=340。
xfs_alloc_ptr的开始地址(相对本block偏移)xfs_alloc_ptr[0]=16+340*8=2736=0xAB0。
看实际数据验证我们的分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
单独看了叶子节点,也单独看了中间节点,现在再整体来看,也就是这两行数据:
keys[1-8] = [startblock,blockcount] 1:[32,3] 2:[47931,66] 3:[70425,1] 4:[75668,2] 5:[99725,12] 6:[139125,51] 7:[151459,3] 8:[153765,1]
ptrs[1-8] = 1:1 2:324 3:4 4:473 5:6 6:138 7:139 8:110
还是很好理解的,要注意的是key是由两个值组成的,另外同级节点还会以双向链表的形式组织起来(图中未画出):
ABTC的B+tree于此类似,所以就不讲了。最后,看一下ABTB的实例数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
|
七,ABTB/ABTC的节点块管理
前面几篇文章都是描述的如何利用ABTB/ABTC类型的B+tree来对AG空闲磁盘空间块进行管理,而这篇文章主要讲对ABTB/ABTC类型的B+tree各个节点本身所占去的磁盘block块的管理。
前面提到过,一块刚建立的Xfs文件系统,AG(仍然是以primary AG为例)的第1块block和第2块block分别被用来作为了ABTB和ABTC类型的B+tree的根节点,并且保留了第4、5、6、7这4块block作为B+tree增长所需。再来看一下:
[root@localhost ~]# /home/lenky/xfs/xfsprogs/db/xfs_db /dev/loop0
xfs_db> agf
xfs_db> p
magicnum = 0x58414746
versionnum = 1
seqno = 0
length = 32768
bnoroot = 1
被ABTB类型的B+tree根节点占去的第1块block。
cntroot = 2
被ABTC类型的B+tree根节点占去的第2块block。
bnolevel = 1
cntlevel = 1
flfirst = 0
fllast = 3
flcount = 4
freeblks = 32756
longest = 32756
btreeblks = 0
xfs_db> agfl
xfs_db> p
bno[0-127] = 0:4 1:5 2:6 3:7
为ABTB/ABTC类型的B+tree增长所需而保留下来的第4、5、6、7这4块block。
xfs_db>
保留下来的block的块号被记录在agfl里,前面文章的图示中给出过agfl的位置,即是在第0块block的第3个sector内:
1 2 3 4 5 6 |
|
一个sector大小为512,而指示一个block号需要4个字节,所以agfl总共可以保存128块block的块号,也即是上面的bno[0-127]被显示为一个具有128个元素的数组。agfl内指定的block并不全是有效的,有一些block可能已经被当作BTB/ABTC类型的B+tree节点使用,也可能已经释放或做它用,只有一部分有效,哪些有效呢?agf里的三个字段(flfirst、fllast和flcount)指明了这一点,即是bno[flfirst]到bno[fllast],这flcount个元素指定的block块才是有效的,值得注意的是,bno被组成了一个环状,也就是说如果flfirst=126,fllast=1,flcount=4,那么有效block的块号分别为bno[126]、bno[127]、bno[0]、bno[1]。这些保留下来的有效的block块不能用来保存任何其它数据,只能用来作为当ABTB/ABTC类型的B+tree增长需要新的中间节点或叶子节点的存储空间。保留block块的申请释放在函数xfs_alloc_fix_freelist内,被释放出保留队列的block块当然就可以用来存放其它数据了。
看实际数据验证我们的分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
ABTB占用的块:
1、4、5、6、110、138、139、324、473
ABTC占用的块:
2、7、58、106、108、471、472、474、475
保留队列:
0:4 1:5 2:6 3:7 4:470 5:471 6:472 7:473 8:474 9:475 10:6 11:7 12:58 13:138 14:139 15:106 16:108 17:110 18:134 19:160 20:324 21:407 22:419 23:421 24:64 25:134 26:99
红色字体block块是当前做为ABTB/ABTC类型的B+tree节点在使用的,一共有16块,符合上面btreeblks的值(这个字段记录ABTB和ABTC这两种类型B+tree的树节点所占的block块数,但不包括最开始根节点所占的那两块,即是第1块和第2块),蓝色字体block当前是处于保留状态,而绿色字体block是之前做过保留、被当作B+tree节点使用之后,现在已被释放,但释放的134这块block现在又处于了保留状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
一定程度上可以看到470和160这两块block已经放了数据(不能确定这些数据当前是否有效,但可以确定其一定没有作为B+tree节点在使用),而134这块block被保留下来了(可以确定其当前一定没有存放其它有效数据)。
八,inode节点管理
作为标记一个文件的inode号,对于它的管理当然也是十分重要的。inode的分配与释放总是以64为单位组成chunk块来进行,那么一个AG对于inode整体的管理也就是对这些chunk块的管理,AG仍然还是使用B+tree这个数据结构来管理这些chunk块的分配与释放。这个B+tree的类型(指人为的对其所占用的block块进行magic标记和区分)为IABT,和ABTB/ABTC类似。
IABT类型的B+tree的根节点所在block块号由xfs_agi指定,xfs_agi存储在第0块block的第2块sector内,一块新建立的Xfs文件系统的AG的agi如下:
[root@localhost xfsprogs]# ./db/xfs_db /dev/loop0
xfs_db> agi 0
xfs_db> p
magicnum = 0x58414749
魔术数,即是‘XAGI’。
versionnum = 1
版本号,由XFS_AGI_VERSION定义,值为1。
seqno = 0
agi的序号,第0块AG内的agi序号为0,第1块AG内的agi序号为2,类此。
length = 32768
本AG内的block块数,其值=磁盘总大小/block块大小/AG数目,这里loop0磁盘大小为512M,即是:512*1024*1024/4096/4=32768。
count = 64
已分配的inode数目,这是一块新建立的primary AG,所以其一开始就分配了一个inode chunk,即是64个inode。如果是其它AG,一开始是没有分配inode chunk的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
root = 3
指定IABT类型的B+tree的根节点所在block块号,一开始就是第3块,后续随着分区的使用,inode的分配与释放,B+tree发生变化,随之根节点也会变到其它block块上。
level = 1
指定IABT类型的B+tree的树深度。
freecount = 61
当前可用的inode数,即是已经分配但尚未被使用的inode。新分区为什么只剩下了61个空闲inode?不是64个么?事实上,是因为1个(inode号为128)被用来作为了根节点,另外两个(inode号分别为129和130)被用来作为实时设备空间(real-time device’s space)的管理节点,即是位图节点和综述节点(the Bitmap Inode and the Summary Inode),关于这两个节点后续再讨论。这三个inode节点在代码路径(main(xfs_mkfs.c:2631)-> parse_proto(proto.c:592) -> parseproto(proto.c:564) -> rtinit(proto.c:645) -> …)里被使用掉,可以ls命令看一下根节点:
1 2 3 4 5 6 7 |
|
newino = 128
新分配的inode chunk块内的起始inode号。
dirino = null
最后一个目录inode chunk块号。
unlinked[0-63] =
该字段后续会讲到。
xfs_db>
前面已经详细讲解了ABTB/ABTC类型B+tree的组织与结构,与此类似的IABT类型B+tree也差不多,只是B+tree里具体的数据字段以及含义有点变化而已。先来看深度为1的IABT类型B+tree,在此之前先弄点数据上去:
1 2 3 4 5 6 7 8 9 10 |
|
再看agi和IABT:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
上面的agi信息给出:IABT的根节点在第3块block,树深度为1,当前空闲的inode数为73,最新分配的inode chunk块内的起始inode号为1184;而从第3块block里的信息可以看出一些inode的分布,比如从[128,1,0×80000000000000]可以看出第8(起始inode号是128,那么推算出该inode chunk所在的block号为128/16=8,其中16是指1块block可以存放16个inode,当然是指默认配置下)块block被用来作为inode chunk,并且还有一个inode处于空闲状态,该空闲inode通过0x80000000000000掩码来标识出。
看实际数据验证我们的分析,注意inode的魔术数,即‘IN’:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
再来看深度为2的IABT类型B+tree,这意味着该B+tree有中间节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
IABT类型B+tree的叶子节点和中间节点内存布局如下所示:
和前面所讲过的ABTB/ABTC类型的B+tree的中间节点一样,对于xfs_inobt_ptr[0]的起始地址为:16+510*4=2056=0x808,其中510=(4096-16)/(4+4)。
看实际数据验证我们的分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
其它的关于这个B+tree的整体组织,前面已经描述过ABTB/ABTC类型的B+tree,所以不再多讲,下一篇开始inode本身结构的分析。
九,inode节点结构
Linux下一切皆文件,所以文件的类型有很多,比如普通文件、目录文件、软链接文件、设备文件等等,ls -l命令显示的文件列表的第一个字符表示该文件的类型:b:块设备、c:字符设备、d:目录、p:命名管道、f:普通文件、l:软连接、s:socket,可以利用find的命令查找系统上指定类型的文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Linux任何一个文件,除了本身的固定属性(比如文件类型、访问权限、所有者、最后访问时间、最后修改时间等),还有文件数据(后面特称之为文件内容)与其它扩展属性(比如用户自定义的属性),与此一一对应,inode结构也就被分成了三部分,即inode核心数据、文件内容、扩展属性(文件内容和扩展属性之间为什么是虚线后面会讲到,另外为了后续文章的叙述清楚,下图中的inode核心数据、文件内容和扩展属性分别所占的磁盘空间就分别以inode core、data fork、attribute fork代称,即一说到inode core,那就是指图中斜网格填充的这一块磁盘空间区域,其它类此。):
其中inode核心数据由结构体xfs_dinode描述,有几个字段比较重要,它们决定了后面数据的安放。第一个重要字段是di_mode,这个字段记录该文件的类型和权限,而不同类型的文件存放的据内容和格式很明显是不一样的,比如目录文件和普通文件。第二个重要字段是di_format,这个字段决定了文件内容的存放方式。这个字段的取值定义在枚举体xfs_dinode_fmt内,其中“uuid”目前尚未用到,“dev”表示本inode关联的是一个字符或块设备,“local”表示文件内容直接存放在本inode所在的这个block块内,“extents”表示文件内容存放在其他block块内,而这些block的块号被以数组的形式组织起来存放在本inode所在的block块内,“btree”表示文件内容存放在其他block块内,而这些block的块号被以B+tree的形式组织,并且该B+tree的根节点存放在本inode所在的block块内。可以看到“local”->“extents”->“btree”的形式可以存放由少到多的文件内容。
文件内容(根据前面几句话的描述,文件内容不一定就是文件的实际数据,也可能是其他存放实际文件内容的block块号或B+tree根节点等,这里统一认为是文件内容,当然,标准说法为Data Fork)存放的起始地址紧跟在inode核心数据之后,由于结构体xfs_dinode占100个字节,所以文件内容存放的起始地址也就是100。
看实际数据验证我们的分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
|
从命令“ls -lai”里可以看到目录文件“.”对应的inode是128,普通文件lenky.xfs对应的inode是131,128的di_format字段为1(local),表示该文件的内容(因为这是一个目录文件,所以文件内容就是该目录的子文件、子目录等信息)存放在本inode所在的这个block块内,即是100字节的起始位置;另一方面,可以看出131的di_format字段为 2(extents),它的数据存放在其它block内,block号为12;先hexdump验证一下,由于128的inode在第8块block(不清楚的话看前面的文章),所以偏移为32768:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
再看继续看一下,可以看到随着子文件的增多,信息已经无法在当前inode所在的block块存放了(一个inode一共才256字节的空间),所以由“local”变成了“extents”的。另一方面,对于普通文件,就算只有2个字节,其存放方式也是“extents”(实验结论):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
|
下篇分析各种不同类型文件的文件内容的具体结构与组织,附带相关数据结构体的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
|
十,普通文件数据结构(extents)
对于普通文件来说,文件内容可以以“extents”或者“btree”两种方式存放,前一篇已经讲过这两种方式所存放数据的大致格式,下面详细描述;
先看“extents”,“extents”是将存放文件实际数据的block块号以数组的形式存放在inode核心数据之后,当然,不仅仅只是块号那么简单,假设称其中的每一个数组元素为一个“extent”,那么,一个“extent”具体就是如下四个信息:
1,这一部分文件内容对应到文件整体内容中的逻辑起始地址(也以block块为单位)。
2,存放这一部分文件内容的起始block块号。
3,有多少块block用来存放这一部分文件内容。
4,该“extent”的状态标记。
一个文件可能由多个“extent”组成,于是此时就由多个“extent”形成“extents”数组,有多少“extent”记录在inode核心数据对应的结构体xfs_dinode的di_nextents字段内。一个“extent”对应的数据结构是xfs_bmbt_rec,它看上去很简单:
1 2 3 |
|
这是在磁盘上对应的数据结构,占128bit,大端字节序,而在实际Xfs操作代码里,为了更方便的获取上面提到的四个信息,所以其对应的数据结构是xfs_bmbt_irec:
1 2 3 4 5 6 7 8 9 10 |
|
很明显,xfs_bmbt_irec是xfs_bmbt_rec的扩展形式,那么这个结构体如何对应呢?在文件include/xfs_bmp_btree.h头文件里已有详细说明:
1 2 3 4 5 6 7 8 9 10 11 |
|
看一个实例,根据实例数据来分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
磁盘extents数据为:
00008460 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 |…………….|
00008470 01 80 00 01 00 00 00 00 00 00 02 00 00 00 00 00 |…………….|
00008480 03 20 00 05
即是:
(la.0):00 00 00 00 00 00 00 00 (la.1): 00 00 00 00 01 80 00 01
(lb.0):00 00 00 00 00 00 02 00 (lb.1): 00 00 00 00 03 20 00 05
解压缩后的数据:
u.bmx[0-1] = [startoff,startblock,blockcount,extentflag] 0:[0,12,1,0] 1:[1,25,5,0]
1,la.0:63和lb.0:63都为0。
2,la.0:9-62为0,而lb.0:9-62为1(看最末的2,二进制即为0010,注意这个蓝色的0要排除,因为它是lb.0:8,所以lb.0:9-62的结果为1)。
3,la.0:0-8和la.1:21-63组合为:0 00 00 00 00 00 01 8,最后的18的二进制表示为0001 1000,同样注意这个蓝色的0要排除,因为它是la.0:20,所以最后有效位值为1100,即12。与此类似,lb.0:0-8和lb.1:21-63组合为:2 00 00 00 00 00 03 2,第一位16进制数2属于lb.0:0-8的第8bit为0,最后的32的二进制表示为0011 0010,同样排除蓝色的0,因为它是lb.0:20,所以最后有效位值为11001,即25。
4,la.1:0-20为1,而lb.1:0-20为5。
也许全部展开可以一目了然(以lb,[1,25,5,0]为例,其中颜色显示:startoff、startblock、blockcount、extentflag):
127-96: 00000000 00000000 00000000 00000000
95-64: 00000000 00000000 00000010 00000000
63-32: 00000000 00000000 00000000 00000000
31-0: 00000011 00100000 00000000 00000101
另外,0:[0,12,1,0] 1:[1,25,5,0]的具体含义表示这个文件由两个extent存放实际数据,第一个extent的起始block块号为12,长度为1;而第二个extent的起始block块号为25,长度为5,即占用25、26、27、28、29这五块block,它对应的文件的逻辑起始地址为4096(因为startoff=1),对比看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
一个inode最大大小为256个字节,除去inode核心数据100个字节,假设该文件没有扩展属性,那么最多可以有(256-100)*8/128=9.75个“extent”,即数组“extents”最多只能有9个元素,当文件增大到一定程度,这些“extent”无法以数组的形式存放时,就会以B+tree的形式组织起来,这将是下一篇文章的内容。最后,看一个图:
仍然按照我的习惯,先弄出一B+tree的“extents”,采用的方法就先把磁盘给填满,然后随机删除一些文件,然后再写一个超大文件,由于前面两步操作使得磁盘上的free block很碎并且不连续,所以这个大文件占用的磁盘空间也都不连续,所以需要更多的“extent”来标记这些block块,上一篇提到当“extent”数目大于9时,Xfs文件系统就只能使用B+tree来对它们进行管理了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
core.format = 3 (btree),达到了我们想要的结果。这颗B+tree的根节点存放在data fork内,所以根节点最大只能占有156字节,正因为如此,所以这棵树的节点布局要把根节点单独出来,所以就有三种。
根节点:
xfs_bmdr_block_t的起始位置偏移inode节点的100字节处,xfs_bmbt_key_t紧跟在xfs_bmdr_block_t之后,而xfs_bmbt_ptr_t的起始位置如前面介绍的B+tree类似,并不会紧接在xfs_dfiloff_t br_startoff[n-1]元素之后,而是会预留出足够的空闲空间用以存放xfs_bmbt_key_t元素可能的增长,具体的值当然可以计算出来:(256-100-4)/(8+8)*8+100+4=176=0xB0(注意其中的整型计算),即相对当前inode节点起始地址偏移的176字节处,可以验证(其中的35072是inode 137的起始地址,即是137*256=35072):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
00008960 ff ff ff ff 00 01 00 01 00 00 00 00 00 00 00 00 |…………….|
00008970 10 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 |… …………|
00008980 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |…………….|
*
000089b0 00 00 00 00 00 00 10 33 00 00 00 00 00 00 00 00 |…….3……..|
000089c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |…………….|
*
00008a00
叶子节点占用一整的block块,其布局图示如下:
实例,具体就不一个个字段对比了,数据如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
其它中间节点也是占用一整的block块,所以其内部布局虽然和根节点有点类似,但开始的一部分有点不同,得换成xfs_btree_block结构体,另外xfs_bmbt_ptr_t的起始地址当然也不是176了,不过也可以计算出来,这里都不累述。最后,看它的图示如下:
十一,目录文件数据结构
对于目录类型的文件来说,其文件内容就是该目录下管理的所有文件(也即是子目录、普通文件等等)的信息(比如文件名、inode号等),随着目录文件内容的多少不同,存放的方式也不同,本篇就先介绍最简单的存放方式,称为简短方式(Shortform Directories)。前面说过,目录文件对应inode的data fork最大可有156个字节,利用这些磁盘空间也是可以存放一些信息的,简短方式就是直接把目录文件下的子文件相关信息存放在该目录文件对应inode的这156个字节空间内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Xfs在使用简短方式存储子文件信息时会采用尽可能的方法压缩这些子文件信息,以便在156字节空间里存储最大容量的项目:
1,只存储子文件的文件名(包括文件名长度以及文件名字符串)、inode号、offset偏移(为了迭代调用readdir函数而设定的字段,但其具体含义以及怎么被readdir函数使用,目前尚未搞清楚)。
2,代表当前目录的“.”没有存储,因为就是当前inode本身;代表父目录的“..”直接存放在头结构体xfs_dir2_sf_hdr_t的parent字段内。
3,为了尽量节省每个项目占用的字节数,代表inode号的项目字段inumber为union类型,既可以是8字节,又可以是4字节。当所有子文件的inode号都可以用4字节完整表示时,项目的字段inumber就用4字节字段足以,此时整个项目数记录在头结构体xfs_dir2_sf_hdr_t的count字段内,与此相对的i8count字段值为0;只要有一个子文件的inode号需要用8字节才能完整表示,此时每个项目的字段inumber就要都占用8字节(因为无法知道具体哪个项目的inumber字段需要8字节,所以此时想节省空间也没有办法,只能全部占用8字节),整个项目数记录在头结构体xfs_dir2_sf_hdr_t的i8count字段内,与此相对的count字段值为0;
简短方式比较简单,看一个实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
|
当我们删除一个子文件时,如果这个子文件对应的项目不是最后一个,那么接在后面的项目必须要前移,这些都是很容易想到的事情,因为如果不前移,那么按照头结构体里的count或i8count字段而遍历后续“数组”找到的项目就不对了。
当一个目录文件下的子文件比较多,其子文件的相关信息以简短方式无法在inode的data fork内存放时,就要被转移到一个新的单独的“directory block”块内。“directory block”块是存放目录子文件相关信息所需磁盘的基本申请和释放单位,一块“directory block”的大小不一定和一块逻辑block大小一致,因为一块“directory block”可以由多个连续逻辑block组成,具体大小bytes = sb_blocksize * 2sb_dirblklog,但在默认情况下sb_dirblklog = 0,也即是一块“directory block”就由一块逻辑block组成,默认大小同为为4096。另外,一块“directory block”的大小最大值为35536(bytes)。
利用新的单独的“directory block”块来更多存放子文件的相关信息,对这些“directory block”的组织又有多种方式,如前面所说,包括数组“extents”、“Btree”等。先来看看“extents”方式,此时目录文件inode核心数据的di_format字段值将由XFS_DINODE_FMT_LOCAL(数值1,简短方式时的值)变成XFS_DINODE_FMT_EXTENTS(数值2),各个“directory block”块号等信息以数组的形式存放在data fork空间内,数组元素的个数由inode核心数据的di_nextents标记,数组元素内记录的一个“directory block”的信息包括startoff、startblock、blockcount、extentflag,这和前面介绍的普通文件的extents数据结构是一样的。
先看数组元素只有一个的情况,这是一种特例情况,此时di_nextents = 1,di_nblocks = “directory block”/“logic block” (我这里,“directory block”和“logic block” 大小一样,所以di_nblocks = 1),数组第0个元素内记录的“directory block”的信息:startoff恒等于0、startblock指向对应的起始block块好、blockcount等于“directory block”/“logic block”、extentflag记录flag标记。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
00008960 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 |…………….|
00008970 04 80 00 01 00 00 00 00 00 00 02 00 00 00 00 00 |…………….|
非0的数据“04 80 00 01”拆成2进制为“00000100 10000000 00000000 00000001”,即startblock的二进制值为100100(十进制为36),blockcount的二进制值为001(十进制为1),对比前面是符合数据一致的。
上一篇实例里讲到“directory block”块存放在startblock指向的逻辑block块内,块号为36(注意我这里“directory block”和“逻辑block”大小一致),先看看其内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 |
|
“directory block”块结构比较复杂,相关的结构体就有好几个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
一块“directory block”的结构示意图如下所示:
1,同样先是四字节的magic魔术数,0x58443242 “XD2B”,
2,接下来是长度为3的xfs_dir2_data_free_t数组变量bestfree,这个变量的每个元素记录了本“directory block”内空闲的磁盘块,因为只有三个元素所以只能记录三个空闲块,但这三个都是空闲容量按从大到小排列为前三的。如果空闲块少于三个,那么后面的数组元素就会为空,比如一开始时,还没有形成空洞,那么此时空闲块就只有一个,那么此时数组第1和第2元素为0,如前面实例所示;当用户进行了子文件删除等操作,这样就会释放一些entry形成空闲块,此时数组第1和第2元素才会有记录。记录的信息主要是:offset为空闲块的起始地址(相对比本“directory block”偏移),length为空闲块的长度。
3,空闲块以一个xfs_dir2_data_unused_t结构体变量作为头信息,这个头信息里的freetag恒为0xffff,length为本空闲块的长度,tag记录空闲块的起始地址(相对比本“directory block”偏移)。
4,紧接在数组变量bestfree之后就是存放子文件信息的地方,子文件信息由结构体xfs_dir2_data_entry_t表示,几个字段的含义也比较明显,其中tag表示本xfs_dir2_data_entry_t变量元素的起始地址(相对比本“directory block”偏移),该地址会按8 (XFS_DIR2_DATA_ALIGN)字节对齐。
5,在“directory block”的最后存放着xfs_dir2_block_tail_t结构体对应变量的值,两个字段,占用8个字节,分别为count和stale,其中count记录leaf元素的总数,而stale记录已失效的leaf元素个数,也即是,假设一开始有128个子文件,删除3个后,那么此时count=128,stale=3。
6,紧跟着xfs_dir2_block_tail_t结构体对应变量之前就是第5点中提到的leaf数组,这个数组的增长是从“directory block”末尾反向向前增长的,每个数组元素记录一个子文件的hash值与存放的起始地址(需要乘以8(XFS_DIR2_DATA_ALIGN)),整个数组按hash进行了排序,这样便于进一步快速查找。
当仅一块“directory block”无法存放和组织所有的子文件相关信息时,此时就需要更多块的“directory block”,这些“directory block”块信息仍以数组的形式组织起来存放在inode的data fork里,不过和只有一块“directory block”时稍有不同,将会把xfs_dir2_data_union_t和xfs_dir2_leaf_entry_t拆开放在不同的“directory block”内。leaf信息固定放在一个“directory block”内,因为一个leaf可以对应更多的data数据,所以可以有多个“directory block”存放data数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
前di_nextents-1个“directory block”用来存放data,而最后一个“directory block”存放leaf。如上图所示,阴影部分的extent元素即用于指示对应的leaf“directory block”,并且它有一个特定的offset值XFS_DIR2_LEAF_OFFSET,默认情况下数值为0x800000(十进制为8388608)。
上面示例中,data“directory block”在36和136逻辑block块上,而leaf“directory block”存放在135逻辑block块上。
相比上一篇文章里描述的“directory block”的结构示意图,这里由于把leaf信息单独出去了,所以磁盘布局里少了最后的与leaf相关的xfs_dir2_leaf_entry_t和xfs_dir2_block_tail_t,对应的结构体定义为(其中结构体xfs_dir2_block为上一篇提到的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
结构体示意图为(注意magic魔术数已经变成了0x58443244 “XD2D”):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
块36已经没有空闲区域了,所以bestfree元素全为0,而块136有一个空闲区域。
leaf“directory block”的布局采用同样的结构,没什么好多讲的,直接看图:
bests元素内记录的是对应data“directory block”的bestfree[0].length的值。相关结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
十二,总结
关于Xfs文件系统磁盘布局结构的分析到此就算结束,经过一段时间的分析,Xfs在这方面主要设计基本已经了解,后面还有的磁盘布局,包括软链接、扩展属性等与前面的这些内容存放方式与布局并无多大差别,所以不准备继续写下去了,自己看一下即可。所有的文章内容主要依靠官方文档与实验简单验证而没有分析到具体的代码,所以很多细节可能被漏过,甚至还包含有错误,后续若看到后再进行文章修订。
后续仍会继续关注Xfs文件系统,当然,不再是磁盘布局这么基础的内容,而是会从它的一些特性上去去分析它是怎么设计的、代码怎么实现的、是否存在问题和是否可有改进等。