- 原文:https://www.kernel.org/pub/linux/utils/fs/xfs/docs/xfs_filesystem_structure.pdf
- 参考的源码版本:V4.5.0(CentOS 7.7的xfs版本)
- 因原文与源码有很多出入,未完全按原文翻译,以源码为准。如有错误,欢迎指正,直接评论或邮件me@liudonghua.net。
2.1 定长B+树(Fixed Length Record B+trees)
XFS采用B+树来索引所有的元数据。它能在尽可能少的查询次数下,高效地顺序和随机访问元数据。
每个B+树块既可以是含记录的叶节点,也可以是含索引且指向其他B+树的块。定长B+树包含一个指定若干个其它块的根块,最底层的块只能包含记录。
叶块的存储格式:
![553baf5edbfeb79426cad1fda740bf0b.png](https://i-blog.csdnimg.cn/blog_migrate/662eb3bc682570a226623c15fdbd4b35.png)
内部块的存储格式:
![f6f92a26ace1abf5cab0da96f264eb80.png](https://i-blog.csdnimg.cn/blog_migrate/ec1317a2d254a2531ec937796d85b116.png)
如果B+树的记录包含内部块,而且允许重叠(这里的“重叠”未明白什么含义?),则存储格式如下:
![7f627f978bc3e34802f0ab998c5c47ba.png](https://i-blog.csdnimg.cn/blog_migrate/e9755da5737b4547867ecfdbcc6bdfdc.png)
其中header就是下面的xfs_btree_block。
定长B+树有两种:短B+树(Short Format B+trees)和长B+树(Long Format B+trees),如下述代码,详见xfs_btree_block中的联合体。(注意:在v4之前【具体详细版本不确定】分别对应xfs_btree_sblock_t和xfs_btree_lblock_t两个结构体,下文有些图解中可能会提到)
struct
2.1.1 短B+树
短B+树适用于单个AG(分配组),块指针是32位大小。
- bb_magic: 给每个AG的B+树分配的特定的magic数字。
- bb_level: 块在树中的层级,0表示叶块,非0表示内部块。层级的值从叶到根不断增加(a.是否是每一层级+1?b.如果块合并或拆分,是否意味着树所有块的level需要更新?)。
- bb_numrecs: 块中的记录数。
- bb_leftsib: B+树块的左子树的AG块编号。
- bb_rightsib: B+树块的右子树的AG块编号。
- bb_blkno: B+树块的FS块编号(FS block指什么?)。
- bb_lsn: 块最后一次写操作的日志编号。
- bb_uuid: 块的UUID,必须与sb_uuid或sb_meta_uuid匹配,它们依赖于特性设置。
- bb_owner: B+树块所在的AG编号。
- bb_crc: 校验码。
2.1.2 长B+树
长B+树适用于文件,文件数据可以有64位的块偏移大小。
- bb_magic: 给每个AG的B+树分配的特定的magic数字。
- bb_level: 块在树中的层级,0表示叶块,非0表示节点块。
- bb_numrecs: 块中的记录数。
- bb_leftsib: B+树块的左子树的FS块编号。
- bb_rightsib: B+树块的右子树的FS块编号。
- bb_blkno: B+树块的FS块编号(FS block指什么?)。
- bb_lsn: 块最后一次写操作的日志编号。
- bb_uuid: 块的UUID,必须与sb_uuid或sb_meta_uuid匹配,它们依赖于特性设置。
- bb_owner: B+树块
- bb_pad: 填充这个结构体至64个字节。
2.2 变长B+树(Variable Length Record B+trees)
目录和extent属性以key-value的形式存储在文件块中,并以可变长度的线性数组存储在fork的低位块中(原文中的fork怎么理解?)。
变长B+树,也称dir/attr B+树或dabtree。(定长B+树和变长B+树的索引key有待详细了解)
key指针和记录的格式(对应下文的xfs_da_node_entry_t):
![cecefcc0391477852b3c911a45ebe373.png](https://i-blog.csdnimg.cn/blog_migrate/1a2c1b77538ce69024ed9d51780d199e.png)
2.2.1 块头(Block headers)
typedef
- forw:同level中的前一个块的偏移。
- back:同level中的后一个块的偏移。
- magic:magic数。
- pad:填充使结构体对齐。
在v5文件系统中的结构体是struct xfs_da3_blkinfo:
struct
2.2.2 内部结点(Internal Nodes)
这个结构好理解,源码的注释已经很清楚了。
typedef
在v5文件系统中的结构体是struct xfs_da3_intnode,区别就是hdr是对应的struct xfs_da3_blkinfo。
2.3 分配组(Allocation Groups)
XFS文件系统内部被分为多个大小相同的“分配组(AG)”,每个AG可以看成独立维护自己空间的文件系统。在CentOS7上默认的是创建4个AG。
AG有如下特点:
- 一个描述整个文件系统的超级块
- 空闲空间管理
- Inode分配和追踪
- 反向块映射索引(可选)
- 数据块引用计数索引(可选)
整个文件系统的空闲空间和所有inode数量只由第一个AG(primary)维护。通过mkfs.xfs格式化后,主AG的磁盘布局如下图,此时其它AG还没分配任何inode。
![0141e98c164ee3c12f83233d0135fd1c.png](https://i-blog.csdnimg.cn/blog_migrate/0c13e2e04565b198c0bd29f1465111e3.jpeg)
下面详细介绍各个结构。
2.3.1 超级块(SuperBlocks)
每个AG都是以超级块开始。第一个AG(记为AG 0)的主超级志(primary superblock)保存了所有AG信息。其它的超级块仅当主超级块损坏时,通过xfs_repair使用。超级块占用一个扇区的大小。存储结构如下:
typedef
超级块的结构较为复杂,下面只介绍几个主要的变量:
- sb_magicnum:标识文件系统,值是XFS_SB_MAGIC (0x58465342)。
- sb_blocksize:块大小,最小空间分配的单位大小,通常是4096字节(4KB),可以设为512-65536字节。
- sb_dblocks:整个文件系统中可用于数据和元数据的块数量。
- sb_rblocks:实时磁盘设备中的块数量(实时磁盘设备后面会介绍)。
- sb_rextents:实时磁盘设备中的extent数量。
- sb_uuid:文件系统的UUID (Universally Unique ID),可以通过UUID挂载。
- sb_logstart:同一块设备中日志的第一个块编号。0表示日志在其他设备上时,而且对应日志设备的第一个块就是日志的开始。
- sb_rootino:文件系统的root inode编号,通常情况下,root inode就是可以存储inode空间的第一个块。在AG 0中,如果块大小是4KB,则这个值是128。
- sb_agblocks:AG中块的数量。最后一个AG的实际大小可能会不一样,后面空闲空间部分有介绍。
- sb_agcount:文件系统中AG的数量。
- sb_logblocks:日志用的块数量
- sb_sectsize:扇区的大小(字节),通常是512或4096字节。这个决定了最小I/O对齐的大小,尤其是direct I/O。
- sb_inodesize:inode的大小(字节),默认是256字节(一个扇区2个inode),但在创建文件时,最大可以设置为2048。在版本5中,默认和最小大小都是512字节。
- sb_inopblock:每块的inode数量,等于sb_blocksize /sb_inodesize。
- sb_icount:文件系统可分配的inode总数量,只在第一个超级块里维护。
- sb_ifree:文件系统空闲的inode总数量,只在第一个超级块里维护。
- sb_fdblocks:文件系统空闲数据块的总数量,,只在第一个超级块里维护。
- sb_frextents:文件系统空闲实时extent的总数量,只在第一个超级块里维护。
- sb_lsn:最后一次更新超级块的日志序号。
可以使用xfs_db工具在umount时通过如下命令查看:
xfs_db> sb
xfs_db> p
2.3.2 AGF管理(AG Free Space Management)
XFS文件系统通过两个B+树来追踪空闲空间,一个是基于块编号索引,另一个是基于空闲块的大小索引。这种方案使XFS可以快速的找到邻近给定块编号或大小的空闲空间。
所有的块编号、索引、数量都是相对于AG的。
2.3.2.1 AGF块(AG Free Space Block)
AG第二个扇区包括两个空闲空间的B+树和AG空闲空间的,简称AGF,结构如下:
typedef
这个扇区剩余的字节都置为0。XFS_BTNUM_AGF是3,下标0是给基于块编号索引的空闲空间B+树使用的,下标1是给基于extent大小的空闲空间B+树使用的,下标2是给反向映射的B+树使用的。(4.5.0的源码里是2,可能是因为反向映射索引是可选,而这个版本没有实现这个)
下面介绍部分变量含义:
- agf_magicnum:AG扇区的magic数,XFS_AGF_MAGIC(0x58414746)。
- agf_versionnum:AFG版本号,目前是1。
- agf_seqno:扇区的AG序号。
- agf_length:AG中块的数量,所有AG的这个值必须等于超级块中的sb_agblocks,除了最后一个AG。最后一个AG可能会小于sb_agblocks,必须由这个值来决定。(推测是因为最后一个AG无法保证与其它AG大小相同)
- agf_roots:两个B+树和反向映射索引(如果支持的话)root的块编号。
- agf_levels:B+树的层级或深度,与agf_roots一一对应。对于新创建的AG,这个数组大小只有1,对应“roots”指向层级为0的单个叶节点。
- agf_flfirst:第一个空闲链表块的索引,后面会详细介绍空闲链表。
- agf_fllast:最后一个空闲链表块的索引。
- agf_flcount:空闲链表中的块数量。
- agf_freeblks:当前AG的空闲链表中的块数量。
- agf_uuid:当前块的UUID,必须与sb_uuid或sb_meta_uuid匹配,具体依赖于设置的特性。
- agf_lsn:最后一次AGF写操作的日志序号。
2.3.2.2 AGF的B+树(AG Free Space B-trees)
前面提到有两个空闲空间的B+树,这里不再赘述。叶节点包含一个以offset/count(也是节点的key)排序的有序数组,结构如下。
/*
- ar_startblock:空闲空间首个的AG块编号。
- ar_blockcount:空闲块的数量。
- xfs_alloc_ptr_t:(内部)节点指针是相对于AG的块指针。
下图是只有一层叶节点的B+树(注意:xfs_btree_sblock_t在v4及之后的版本是struct xfs_btree_block,前面有详细介绍):
![5b9124f1a8771c1e1e45d913f9f4950e.png](https://i-blog.csdnimg.cn/blog_migrate/f376f624cb50aeeaa9e7d8e17a50ca04.jpeg)
下图是2层的空闲空间B+树:
![4e80b92fdde06f6b34fffdc920b1defa.png](https://i-blog.csdnimg.cn/blog_migrate/dd6f0bc36dc71bfb787f3aaccaaf7561.jpeg)
2.3.2.3 AGFL(AG Free List)
AG空闲链表位于AG的第四个扇区,简称AGFL。它包含了在AG空间内一个存放指向预留空间的块指针的数组。这个空间不能用于任何类型的用户数据。
文件系统刚创建时,AGFL占用了4个块大小,紧随AGF的B+树root块之后(块4-7)。随着这些块被用完时,AG会预留其他块,并添加到AFGL的数组中。其大小可能会随着功能的增加而增加,具体大小由XFS_AGFL_SIZE宏决定。
这个数组中的活跃元素取决于xfs_agf中的agf_flfirst、agf_fllast、agf_flcount。
结构如下(各变量很好理解,相同后缀的与前面介绍的一样,这里不再赘述):
typedef
AGFL的布局:
![507d73ff155f63a9f0bfad66797db3c7.png](https://i-blog.csdnimg.cn/blog_migrate/2af5f9d7653b3b9658aa2adf91ceee4a.png)
xfs_db工具中结合agf和agfl命令可以查看。
2.3.3 AGI管理(AG Inode Management)
AGI是AG Inode的缩写。
2.3.3.1 Inode编号(Inode Numbers)
在XFS中,inode编号有两种形式:AG相对和绝对。
AG相对的inode编号是32位整数,具体多少位取决于超级块中的sb_inoplog和sb_agblklog,如下图所示。
绝对的inode编号的高位包含AG编号,后面的位与相对的inode编号一样,如下图所示。绝对的inode编号在目录实体(后面会介绍)和超级块中会用到。
inode编号的格式:
![4c79c54eeb93859b03f08dce918c83d5.png](https://i-blog.csdnimg.cn/blog_migrate/3ef0481b60c9cd32978ecf86a5719edb.png)
2.3.3.2 Inode信息(Inode Information)
每个AG管理自己的inodes。在AG的第三个扇区包含了AGI信息,结构如下:
typedef
- agi_magicnum:magic数,XFS_AGI_MAGIC (0x58414749)。
- agi_versionnum:AGI版本号,XFS_AGI_VERSION = 1。
- agi_seqno:AG序号
- agi_length:AG块的总数量
- agi_count:AG的inode总数量
- agi_root:AG中inode B+树根的块编号
- agi_level:inode B+树的层级
- agi_freecount:AG中空闲inodes的数量
- agi_newino:最近分配的chunk的AG相对inode编号
- agi_dirino:已废弃,赋值NULL(-1)
- agi_unlinked[64]:已删除的inode的hash表,后面有详细介绍。
- agi_uuid:当前块的UUID,必须与sb_uuid或sb_meta_uuid匹配,具体依赖于设置的特性。
- agi_crc:AGI扇区的校验码
- agi_pad32:填充,用于对齐。
- agi_lsn:最后一次块写操作的日志序号。
- agi_free_root:AG中空闲inode B+树根的块编号
- agi_free_level:空闲inode B+树的层级。
2.3.4 Inode B+树(Inode B+trees)
习惯上按64个inode划分成chunk,用一个B+树来跟踪这些chunk的分配和空闲情况。B+树的根块保存在xfs_agi的agi_root中。如果启用了XFS_SB_FEAT_RO_COMPAT_FINOBT功能,则会用另外一个B+树来跟踪包含空闲inode的chunk。这是一个优化,用来提升inode的分配性能。
这个B+树的节点和叶节点的头也用的是xfs_btree_block(是短B+树)。
叶节点包含如下结构的数组:
typedef
- ir_startino:当前chunk中数值最小inode编号。
- ir_freecount:当前chunk空闲inode的数量。
- ir_free:64位的bitmap,用来表示chunk中哪些inode是空闲的。
(其中sp看起来是用来保存稀疏inode的,但实际没用这个,用的是xfs_inobt_rec_incore_t,详见下文)
(内部)节点中key/pointer对的结构:
typedef struct xfs_inobt_key {
__be32 ir_startino; /* starting inode number */
} xfs_inobt_key_t;
只有一层的inode B+树(其中xfs_inobt_block_t就是struct xfs_btree_block):
![43708ea14853b3e6f5440f0917cc602c.png](https://i-blog.csdnimg.cn/blog_migrate/5e0e3aaa2f185f9b8e0736462407954b.jpeg)
两层的inode B+树:
![6ae8c4f73e432ce567d3275475e8966b.png](https://i-blog.csdnimg.cn/blog_migrate/ffdc41e622b69ffd5bdb36bf61c55757.jpeg)
xfs_db工具中结合agi和inode命令可以查看。
2.3.5 稀疏Inode(Sparse Inode)
前面提到XFS按64个inode划分成chunk。如果没有足够大的空闲extent来分配一个完整的chunk,那么inode分配会失败,XFS会提示空间已用完。在一个空闲空间碎片化很严重的文件系统中,会导致在空闲块远远还没用完之前就会报空间已用完的错误。
稀疏inode功能用于跟踪inode B+树中,不是完整chunk时,但跟踪的是已使用了的chunk中未分配的部分inode。如果完全需要的话,这允许XFS一次给inodes分配一个块。
(内部)节点和叶节点的B+树头也是strut xfs_btree_block(短B+树)。
叶节点的结构如下:
typedef
其它变量与xfs_inobt_rec_t含义一样,不再赘述。
- ir_holemask:16位的bitmap,表示chunk中不分配的inode(即分配给其它chunk的),每一位代表4个inode。这是被标记的位,在ir_free中对应的位也必须标记。(ir_holemask大于0表示是稀疏inode,参考xfs_inobt_issparse函数)
- ir_count:chunk中已分配的inode数量。
可以通过xfs_db工具下面的这些命令来查看:
xfs_db> agi 0
xfs_db> p
xfs_db> addr root
xfs_db> p
xfs_db> addr ptrs[1]
xfs_db> p
2.3.6 实时设备(Real-time Devices)
标准XFS分配器的性能依赖于文件系统中各元数据索引的内部状态。对于应用来说,需要最小分配延时的抖动,因此XFS支持“实时设备”。这是一个区别于常规文件系统的特殊设备,它的extent分配用一个bitmap跟踪,以及空闲空间用一个二维数组来索引。如果一个inode被标记为XFS_DIFLAG_REALTIME(值为1),那么它的数据存储在实时设备上。下文会有详细介绍。
把实时设备(和日志)分开放在其他高性能存储设备上的话,可以大大减少在I/O不确定性时操作元数据的响应时间。
XFS每个AG的B+树不涉及实时文件,实时文件不能用于共享数据块。
2.3.7 其他
【反向映射B+树(Reverse-Mapping B+tree)和引用计数B+树(Reference Count B+tree)需要的时候再补充。原文也有提示,这两部分在建造中,可能会变更。】
2.4 日志(Journaling Log)
在文件系统中,XFS的日志作为预留的块extent存在于磁盘上,或者作为单独的日志设备。日志本身就是一系列的记录,每条日志包含部分或所有事务。一个事务由一系列操作日志的头(log items)、格式化结构体、原始数据组成。这些操作记录了事务开始到提交之间表述元数据变更的操作。如果没有提交操作,则事务不完整,不能用于恢复数据。
(journal与log在中文里似乎不能很好的区分,但两者确实有差异,个人理解是分别表示整体与个体的记录。有些文章会把journal译为“日记”,本文没考虑那么严谨哈)
2.4.1 日志记录(Log Records)
XFS日志被分割成一系列的日志记录。日志记录可以看作对应于in-core日志缓冲,日志缓存最大256KB。每条记录有一个日志序号,在v5中与元数据的LSN相同。
日志序号是64位数,由两个32位数组成。高32位是每次XFS循环遍历日志都增加的循环数(cycle number),低32位是事务提交时赋值的块编号,需要与日志里块的偏移量对应。
日志记录头的结构如下,占512字节:
typedef
大部分变量的含义参考注释很好理解,这里只列出部分不好理解的。
- h_cycle:日志记录的循环数。
- h_tail_lsn:第一个有未提交buffer的日志记录的日志序号。
- h_cycle_data:每个日志扇区的头4个字节必须包含循环数。如果日志条目缓冲格式化时,没有考虑到这个要求,那么日志中每个扇区的头4个字节的原始内容会被拷贝到这个数组对应的元素中。然后这些扇区的头4个字节会被写入循环数。这个过程在恢复时,做相反的处理。如果日志记录需要比这个数组更多的扇区空间,那么cycle data需要同样多的连续扇区,每个扇区的格式是xlog_rec_ext_header,详见下文。
- h_fmt:日志格式,定义如下:
![6b8254fc6820aac7864a85b6ec55ba67.png](https://i-blog.csdnimg.cn/blog_migrate/87980a2e74bbcd0e8f86138b205cdb51.jpeg)
(这里的cycle的含义没搞明白)
xlog_rec_ext_header结构:
typedef
- xh_cycle:日志记录的循环数,需要与h_cycle匹配。
- xh_cycle_data: h_cycle_data存不下的cycle data。
2.4.2 日志操作(Log operations)
在日志记录中,日志操作是记录一系列由操作头和紧随其后的数据区组成的数据。结构如下:
typedef
- oh_tid:事务ID
- oh_len:数据区字节数
- oh_clientid:操作发起方,取值有:XFS_TRANSACTION、XFS_VOLUME、XFS_LOG。
- oh_flags:操作关联的标记位,可以由下面的值组合使用(大部分情况同时只会设置一个值)。
![2f4800530593c0c1f6c3b4649ee91faf.png](https://i-blog.csdnimg.cn/blog_migrate/3b5630b0d329f8a23447e9d1f5af9e40.jpeg)
- oh_res2:填充,用于对齐。
2.4.3 日志条目(Log Items)
跟在xlog_op_header之后的是各种类型的日志条目,类型定义如下。缓冲数据只出现在xfs_ buf_log_format之后,inode core只出现在xfs_inode_log_format。
日志条目类型:
#define XFS_LI_EFI 0x1236
2.4.3.1 事务头(Transaction Headers)
结构如下:
typedef
- th_magic:magic数,XFS_TRANS_HEADER_MAGIC(0x5452414e)。注意:和XFS其值一样,这个值是主机字节序,不是大端字节充(应该是指与CPU有关,XFS没有做特殊处理)。
- th_type:事务类型,目前只有XFS_TRANS_CHECKPOINT,而且在启用了CIL的文件系统日志时才有效。
- th_tid:事务ID,未使用(应该是日志操作里的事务ID就够了)。
- th_num_items:日志条目的数量。
2.4.3.2 部分类型的日志条目结构
其中xxx_type就是前面介绍的日志条目类型(XFS_LI_XXX)。各个类型的日志条目不做详细介绍,只列出部分结构。
inode的日志条目:
struct
缓冲区的日志条目:
struct
2.5 内部节点(Internal Inodes)
XFS在创建文件系统时,会分配几个inode。它们是文件系统内部用的,在标准的目录结构中不能访问,只有超级块才能访问。
2.5.1 Inode配额(Quota Inodes)
如果使用配额,会分配两个配额分别用于用户配额管理和组配额管理。如果使用工程配额,会用来替代组配额管理,因此会使用组配额inode。
- 工程配额的主要目的是用于跟踪和监控磁盘目录的使用情况。为实现这一点,目录inode必须设有XFS_DIFLAG_PROJINHERIT标记,这样所有该目录下的inode就都会继承对应的工程ID。
- ID为0的配额所属的inode和block都没有强制的配额,但只是用于配额核算。(这句翻译可能不准)
- 扩展属性不计入ID配额。
- 在文件中ID的偏移量乘以sizeof(xfs_dqblk_t)的位置,可以找到每个ID配额的信息。
inode配额分布:
![b14919c72502c067d27e7db985f87681.png](https://i-blog.csdnimg.cn/blog_migrate/63ad7dee48c606f7c33d2b430f9cfdf7.jpeg)
配额信息保存在两个预留配额inode的数据extent中,是一个xfs_dqblk数组。
结构如下:
typedef
部分变量含义:
- d_magic:magic数,XFS_DQUOT_MAGIC(0x4451)或‘DQ’。
- d_version:版本号,XFS_DQUOT_VERSION。
- d_flags:标识,取值有XFS_DQ_USER、XFS_DQ_PROJ、XFS_DQ_GROUP等。
- d_id:配额ID,与d_flags有关,可能是uid、gid、projid。
- d_blk_hardlimit:配额可拥有块数量的硬性限制,超过这个空间大小会报ENOSPC错误。
- d_blk_softlimit:配额可拥有块数量的软性限制,这个配额使用的空间大小可以临时超过这个限制,但不超时d_blk_hardlimit的数量。在配额ID为0的d_btimer时间之前,如果空间没有释放,那么配额会拒绝更多的空间申请,直到块的总拥有数量降到d_blk_softlimit之下。
- d_ino_hardlimit:配额可拥有inode数量的硬性限制,与d_blk_hardlimit功能一样。
- d_ino_softlimit:配额可拥有inode数量的软件性限制,与d_blk_softlimit功能一样。
2.5.2 实时inode(Real-time Inodes)
有两个inode用于管理实时设备的空间,位图inode(Bitmap Inode)和摘要inode(Summary Inode)。
2.5.2.1 位图inode(Bitmap Inode)
实时位图inode(超级块中的sb_rbmino)用于跟踪在实时设备中的已用和空闲空间。每个bit对应每个实时extent。每个extent的大小由超级块中的sb_rextsize决定。
位图inode使用的块数量等于sb_rextents/sb_blocksize,这个值保存在sb_rbmblocks。每个实时块都在位图中占用一个bit。
2.5.2.2 摘要inode(Summary Inode)
实时摘要inode(超级块中的sb_rsumino)用于跟踪在实时设备中已用和空闲空间的核算信息。这些文件索引在每个实时设备的空闲extent上,先通过计算log2(extent大小)来得到大概位置,再通过实时位图来得到具体的块编号。
摘要inode文件的大小 = sb_rtbitmap x log2(实时设备大小) x sizeof(xfs_suminfo_t)
尽管这个数据结构不是特别节省空间,但它快速地为普通文件提供了与两个空闲B+树相同数据的,因为空间是预分配的,以及元数据的维护是最少的。
2.6 写成最后
到此XFS的全局存储结构已介绍完毕,动态分配的存储结构(Dynamically Allocated Structures)和辅助数据结构(Auxiliary Data Structures)将在后续的文章中介绍。