版权声明:本文为CSDN博主「瞧见风」的原创文章,遵循CC 4.0 BY-SA版权协议
原文链接:https://blog.csdn.net/scaleqiao/article/details/52098546
(注:xfs文件系统是一套非常成熟的文件系统,目前对其原理进行学习并记录blog,方便工作中对涉及的文件系统进行维护)
0 文件系统
引用维基百科对文件系统的定义:“计算机的文件系统是一种存储和组织计算机数据的方法,它使得对其访问和查找变得容易,文件系统使用文件和树形目录的抽象逻辑概念代替了硬盘和光盘等物理设备使用数据块的概念,用户使用文件系统来保存数据不必关心数据实际保存在硬盘(或者光盘)的地址为多少的数据块上,只需要记住这个文件的所属目录和文件名。在写入新数据之前,用户不必关心硬盘上的那个块地址没有被使用,硬盘上的存储空间管理(分配和释放)功能由文件系统自动完成,用户只需要记住数据被写入到了哪个文件中。”
把其中核心的东西标记出来,即可看出文件系统的本质:一种方便管理、组织、访问数据的软件。
对于管理来说,主要是磁盘空闲空间的管理
对于组织来说,主要是通过引入文件(inode)、树形目录(dentry)来组织用户的数据。文件包含用户的数据、树形为用户提供了一个对数据进行分类的功能。
对于访问来说,通过目录+文件名的方式进行文件创建、删除、读、写(也就是所谓的增、删、查、改)。
在以上核心的功能之上,加入错误异常处理的机制比如通过日志保证操作的原子性以及数据的一致性、通过fsck机制确保文件系统的正确,加入权限控制,加入一些高级特性比如快照、重复数据删除等,加上文件的并发控制等就是一个文件系统了。
1 XFS简介
XFS最早针对IRIX操作系统开发,是一个高性能的日志型文件系统,能够在断电以及操作系统崩溃的情况下保证文件系统数据的一致性。它是一个64位的文件系统,后来进行开源并且移植到了Linux操作系统中,目前CentOS 7将XFS+LVM作为默认的文件系统。据官方所称,XFS对于大文件的读写性能较好。
性能问题对于大多数文件系统而言,都是一个头疼的事情,特别是在高并发大量小文件这种场景下,而XFS采用了一些好的思想比如引入分配组、B+树、extent等方法来提高性能,这些将在下面介绍。
注:文中所有实验均基于test环境server 68中/dev/sdb2上的XFS文件系统。
2 XFS实现原理介绍
XFS的一些设计实现文档:
http://xfs.org/index.php/XFS_Papers_and_Documentation
2.1 分配组(Allocation Group)
分配组是XFS抽象程度最高的概念。XFS文件系统内部被分为多个“分配组”,它们是文件系统中的等长线性存储区。每个分配组各自管理自己的inode和剩余空间。文件和文件夹可以跨越分配组。这一机制为XFS提供了可伸缩性和并行特性——多个线程和进程可以同时在同一个文件系统上并行执行I/O操作。这种由分配组带来的内部分区机制在一个文件系统跨越多个物理设备时特别有用,使得优化对下级存储部件的吞吐量利用率成为可能。
在一个磁盘上创建XFS文件系统之后,磁盘会被格式化成如下格式:
在CentOS7上默认的是创建4个AG。每个AG都相当于是1个独立的文件系统,维护着自己的free space以及inode,其主要包括以下信息:
superblock:描述整个文件系统的信息。
空闲空间管理。
inode的分配和记录管理
结构如下:
解析上面的图,发现一个AG可以分为5个部分:
1) 第一个区(共1个block):我将其称为全局信息描述区,包括:superblock(SB)、free space描述(AGF)、inode信息描述(AGI)、预留free space信息描述(AGFL)。每一部分都对应一个结构,用于描述其整体信息。
2) 第二个区(共3个block):B+树根节点描述区,包括:inode对应的B+树的根节点(通过AGI中对应字段指向)、管理free space对应的两颗B+树的根节点(AGF中对应的字段指向),这两颗B+树的索引key不同,一个以block number为key,一个以block count为key。
3) 第三个区(共4个block):存放预留freespace节点数据的区域,由AGFL中对应的字段指向。
4) 第四个区:存放inode树型节点的区域。
5) 第五个区:存放文件系统具体数据和元数据的区域。
所以从整个结构来看,核心的结构为superblock、AGF以及AGF中指向的管理freespace的B+树、AGI以及AGI所包含的inode的B+树以及AGFL和其对应的B+树,下面将会依次介绍这些核心元素。
2.2 superblock
superblock位于AG数据中的第一个sector,它会包含AG中的所有元数据信息。其中几个核心的元数据为:
• sb_blocksize/sb_dblocks,文件系统中使用的block的大小,以及整个文件系统用于存放data和metadata的block个数。
• sb_sectsize,指定底层磁盘一个扇区的大小,这个值决定了I/O操作时,数据的最小对齐粒度。
• sb_agblocks/sb_agcount,文件系统中一个AG包含的block个数,以及整个文件系统AG的个数。
• sb_inodesize/sb_inopblock,记录inode的大小以及每个block中包含inode的个数。
• sb_logstart/sb_logblocks,如果使用同一块盘存放XFS的journal,这两个值用于表示存放journal的第一个block以及用于存放log的总block个数。
• sb_icount/sb_ifree,文件系统中已经分配的inode个数以及剩余可用的inode个数,这个只在Primary的Superblock中维护。
•sb_versionnum/sb_features2,Filesystem的version number,这是一个bitmap类型的变量。用于表示文件系统中包含的features。如果其中包含了XFS_SB_VERSION_MOREBITSBIT这个位,sb_features2中 也会包含一些扩展的数据位。
2.3 AGF以及其指向的B+树
2.3.1AGF
AGF是”AG Free SpaceBlock”的缩写,它包含了两个空闲空间B+树的信息以及剩余空间的信息。其对应的数据结构为xfs_agf,具体如下:
其中agf_roots指向了两棵B+树根节点的blocknumber。
2.3.2AGF指向的B+树
XFS中每个AG都使用了两颗B+树来管理空闲空间,它们的区别是索引的key不同,第一棵B+树是基于block的偏移(offset)构建的,而第二颗树则是基于block的size构建的,这样XFS可以快速的通过offset或者size进行空闲空间的分配。
B+树的每个叶子节点都包含两部分:节点的元数据信息使用xfs_btree_sblock_t结构来表示,另外是空闲空间信息,使用xfs_alloc_rec_t, xfs_alloc_key_t结构来表示,它包含了一个排过序的offset/count的key pair数组作为node的key。
比如,下图即为一个深度为1的B+树的示意图:
而非叶子节点是由三部分组成,除了以上两部分之外,还有一个Node 指针数组,包含是相对于AG的block pointers。
下图是一个深度为2,但只包含一棵B+树的示意图:
其中,左边为中间节点,右边为叶子节点。中间节点包含了一些free space的索引信息(由keys数组记录)用于快速检索,并且通过ptrs指向了所有叶子节点。每个叶子节点都包含了空闲数据块(由recs数组记录)。
2.4 AGFL
AG Free List(AGFL),存在在每个AG的第四个sector中,它包含了在AG空间内一个存放指向预留空间的block pointers的数组,这些预留空间的是为了保证Free Space(AGF) B+树的正常工作。它总共占4个block,并且这段空间不能被普通用户用来存放inodes、 data、 direcotries 和extended attributes。随着free space碎片的出现,它们的空间也将被消耗,这时会有额外的block加入到这个free list 数组中。
因为free listarray位于一个sector中,一般这个数组中会包含128个元素。AGF中三个元素agf_flfirst、agf_fllast、agf_flcount。这些预留的block保证了free space B+ trees在一个full的AG中,当有block被释放之后free space B+ trees能够正常更新。
如上面例子中AGF 0 结构中的第二部分就记录了AGFL的信息。
2.5 AGI和AGI指向的B+树
所有的文件、目录和连接都对应inodes,他存储在磁盘上的。
每一个AG都管理自己的inodes,Superblock中第三个sector包含了AG的inode信息,这个信息简称为AGI。
Inode的分配是以64个inode为一个chunk进行分配的。其组织结构是B+树,具体使用的数据结构以及方法和AGF非常类似,只是结构的名字不同罢了。所以你只要理解了前面AGF的管理方法,AGI的也就能理解了。这里针对其管理方式也不再赘述了,只是把具体的数据结构和示意图列出来。
AGI的结构为:
深度为1级的inode B+树示意图:
其中xfs_inobt_block_t实际上和AGF中的xfs_btree_sblock_t结构一样,存在于叶子节点和非叶子节点中作为节点的header,记录节点的元信息。xfs_inobt_rec_t和AGF中的xfs_alloc_rec_t结构有同样的作用,存放了节点包含的数据信息,它是一个数组。数组中的每一项都通过startino、freecnt和fmask三个元素管理着一个包含若干inode数据信息的chunk,每个chunk会包含64个inodes,不论大小。其中startino标识了这个chunk中包含的inode的number,freecnt表示该chunk还有多少个可用的entry,fmask是一个64位的bit array,用于指示chunk中具体哪些entry是free的。
深度为2级的inode B+树示意图:
至此AG中的前三个区都介绍完了,接下来介绍剩下的inode区以及data区的组织和管理。
2.6 inode数据区
了解了inode管理B+树之后,大概可以了解到分配一个inode过程:
找到AG的全局信息描述区,从中查询AG的inode个数,如果还有剩余,则会找到AGI结构,从中找到inode B+树的根节点。
根据根节点遍历整棵B+树,从每个节点中读取节点的head信息,然后从recs数组中定位一个包含inode的chunk
从chunk中找到一个可用的inode,并分配出去。
当然,这里有一个问题,就是inode number是在创建文件时就指定好的,然后根据这个指定好的inode number去inode B+树中查找这个inode。还是先去B+树中查找一个可用的inode之后,在把这个inode number 分配给上面的文件,这个需要进一步确认。
2.6.1inode number
我们都知道每个inode都是一个整数,那么它就是简单递增的吗?当然不是,这个整数是按照bit位分为好几个段的,每个段都有自己的含义。
XFS中的inodenumber分为两种,一种是相对于AG的,一种是全局绝对的。前者存储在AG的inode结构中,后者存储在directory entry的结构中。具体分段如下图所示:
可以看到绝对的inode number就是在MSB(mostsignificant bit)中加入具体的AG number。而relative inode number包含两部分,它们的长度分别有superblock中的sb_agblklog位和sb_inoplog位决定。
但具体这两部分的值怎么计算出来的,目前还不得而知。
在了解了inode number的结构之后,我们接着看一下inode的结构是什么样的。
2.6.2inode结构
文件系统中的每一个存储在磁盘上文件、目录、连接都会对应一个inode的结构,所以inode可以说是一个文件系统最核心的元数据之一了。
一个inode的机构可以分成四个部分:inodecore、inode unlink pointer、data for以及extended attribute fork,如下图所示。接下来依次介绍这四部分的结构
1) inode core(di_core):core相当于一个inode的结构的元信息,它定义了这个inode所描述的文件的信息、文件的属性以及确定了data fork和extended attribute fork的结构。具体结构如下所示:
其中需要特殊注意到是di_format这个结构。这个结构可以是以下几个值:
对于文件、文件夹和目录而言,可以使用”local”这个值,意味值所有的metadata和数据都存储在inode结构结构里。也可以使用”extents”这个值,意味着inode包含了一个extents的数组,每个extent都指向若干blocks,metadata和数据都存储在这些blocks中。还可以使用”btree”,意味着inode中包含了一个B+tree的根节点,这个B+树维护的blocks中存储了具体的metadata和data。”dev”主要用于字符和块设备,”uuid”目前没有在用。
2) inode unlink pointer(di_next_unlinked):这个值用于记录那些已经被删除(unlinked)但仍然还被引用的inode。当有这种情况出现时,inode会被添加到AGI的agi_unlinkedhash buckets中,AGI的unlinked bucket指向了一个inode,而这个inode的di_next_unlinked值会指向下一个inode,依次形成一个链表,最后一个inode的di_next_unlinked值被置为NULL。如下图所示:
一旦inode的最后一个reference释放之后,inode会从这个unlinked hash chain中移除。当文件系统crash时,XFS的recovery线程会处理完这个list中的所有inode节点。
3) data fork:定义文件的具体data存储区域。它的结构以及大小由inode的type以及inode core中的di_format域共同确定。data fork的起始地址是inode空间偏移100个字节的位置,这也被称为是inode的literal area的起始地址。data fork的结束位置(最大值)由inode的大小和inode core中的di_forkoff(扩展属性的起始地址)决定。
XFS系统中的数据是以extents这种结构来为文件分配存储空间,用于存放文件的数据的。extents中也包含了其相对于文件的逻辑偏移,这使得文件能够通过extent map(Fiemap)支持sparse文件。在分配文件数据空间时,XFS会尽可能连续分配,如果当前AG没有空间或者busy,也可能会在其他AG内分配空间。
inode中的di_u结构中存放了找到文件数据对应的extents的索引信息,其结构如下所示:
它是一个union类型的结构,具体使用哪个结构取决于组织extents的方式。不同类型的inode:文件、目录、连接它们的组织方式也不尽相同:
对于文件来说,datafork指定了文件的data extents,extents指定了文件的具体数据存储在文件系统的位置。extents有两种类型,具体有di_format的值确定:
XFS_DINODE_FMT_EXTENTS:extent索引数据完全包含在inode结构里,inode包含了一个文件指向文件系统数据block的extents数组
XFS_DINODE_FMT_BTREE:extent索引数据包含在一个B+树的叶节点中。inode中包含了树的根节点。
对于目录来说,Datafork包含了目录的entries和相关的数据。entries的格式由di_format的值来确定,大概分成3种:
XFS_DINODE_FMT_LOCAL:directory entries完全包含在inode。
XFS_DINODE_FMT_EXTENTS:directory entries存放在文件系统的block中,inode中包含了一个extents的数组,这些extents指向了文件系统的blocks中。
XFS_DINODE_FMT_BTREE:directory entries包含在一个B+树的叶子节点中。
对于符号连接来说,Datafork包含了连接的所有内容。link的结构可以通过di_format的值来确定:
XFS_DINODE_FMT_LOCAL:符号连接的内容完全包括在inode中。
XFS_DINODE_FMT_EXTENTS:符号连接存储在另外一个block中,inode包含了指向这些block的extents数组。
其中常用的是使用extent list(XFS_DINODE_FMT_EXTENTS)或者B+树(XFS_DINODE_FMT_BTREE)来存储数据的两种情况详细介绍inode中di_u的结构。而扩展属性还经常使用XFS_DINODE_FMT_LOCAL这种方式,比如Ceph中将object info等信息以local的方式存储在object文件的扩展属性中(inline xattr)。在data fork先着重介绍前两个。
在介绍这个之前我们先认识一下extent:
extent是用来组织inode数据的一种方式,是一个 128bit的数字,但这128bit的数字是需要按照bit解析的,它对应的结构是xfs_bmbt_rec_t,并且使用 大端方式存储。xfs_bmbt_rec_32_t和xfs_bmbt_rec_64_t结构类似,只是extent结构表示方式不同。
而在inode core中管理的extents使用了xfs_bmbt_irec_t结构:
Ø XFS_DINODE_FMT_EXTENTS
对于使用XFS_DINODE_FMT_EXTENTS这种方式来管理inode对应的数据信息时,其di_u的组织结构如下图所示:
文件中所能包含的extent的个数由inode的大小,以及inodecore中的di_forkoff确定。对于一个256Byte大小的inode来说,如果没有扩展属性它能包含多少个extent呢?(256-100)/128 = 9,这里extent使用128bit的结构存储。当文件需要的extent超过inode中di_u中能够包含的个数时,超出部分会以B+树的形式存储。
在这种组织方式下,查找文件的数据就非常简单了。
根据数据相对于文件的偏移,在di_bmx中找到对应的项,并读出block number
根据blocknumber以及block count进行数据访问。
Ø XFS_DINODE_FMT_ BTREE
对于使用B+树的方式管理extent时,inode的data fork会存储B+树的root node,B+树中使用的blockpointer均为64bit的绝对block number。
B+树的组织形势无非在于节点数据结构的使用,通常分为三种root node、intermediate nodes以及leaf nodes。其中对于root node而言,包括以下三种结构:
分别对应di_bmbt,bb_keys以及bb_ptrs,bb_keys和bb_pts为两个数组。bb_keys数组的元素即为extent相对于文件的偏移,它是查找数据时使用的key。bb_ptrs指向了包含extents信息的叶子节点,或者B+树的中间节点。
对于intermediate nodes来说,其包含以下三种结构:
其中xfs_bmbt_block_t变量维护了该节点的B+树的元数据。bb_keys数组中的元素即为extent相对于文件的偏移,它是查找数据时使用的key。bb_ptrs数组中的元素指向了包含extents数据的叶子节点。中间节点和root节点的数据结构很像,唯一的区别就是节点header部分的定义。
对于叶子节点来说,包含以下两种结构:
extents的数组即为xfs_bmbt_rec_t结构的,具体指向包含数据的block。
B+树的非叶子节点都会包含一个keys数组,用于加快数据查询速度。Key都是以数据相对文件的逻辑偏移构建的。
了解了这些信息之后,我们来看一下,inode的数据是如何通过B+树来管理的。对于一级的B+树来说,其管理extent的示意图如下所示:
整体上分为三部分,最上面是rootnode,存储在inode的di_u结构里,bb_keys中的元素,是每一个叶子节点包含的起始extents元素指向数据相对于文件的偏移。bb_ptr中的元素指向了中间的叶子节点。中间叶子节点的head部分会通过bb_leftsib/bb_rightsib将节点串联起来。而extents数组中包含了所有extent信息,每一个extent都指向了具体存放数据的block。
对于一个多级的B+树,其对应的示意图如下所示:
总结一下,使用B+树管理extent时,数据的查询过程:
根据数据相对于文件的偏移,在inode的di_u结构中找到对应的bb_keys数组中的项
根据bb_keys中的项从bb_ptrs数组中找到指向对应节点的blocknumber。
以同样的方法遍历B+树的中间节点,直到找到叶子节点。
读出extents数组中对应的extent项,找到存放数据的block,从中读出数据。
4) extended attribute fork
文件系统的扩展属性为用户提供了一种可以在inode中存放key/value键值对结构的数据。这种结构可以用于存储一个文件的元数据信息。键值对的key可以是一个大到256byte的字符串,而values可以达到64KB的大小。
目前为止,ACL(Access Control List)以及DMF(Data Migration Facility)都使用了扩展属性来存储数据。
XFS的扩展属性有两个版本:"attr1"和"attr2",attribute version通过superblock中sb_features2的XFS_SB_VERSION2_ATTR2BIT标志来标记。version决定了inode中di_u和di_a中的额外空间如何组织,当然也决定了di_forkoff的值如何在inode core中如何被管理,下图比较清晰的说明了两者组织方式的不同:
Inode中的extended attributefork包含了和inode关联的扩展属性的存放位置。extended attribute fork的起始地址通过inode core中的di_forkoff域指定,该值是相对于inode的literal area的,也就是datafork的起始地址的。如果di_forkoff的值为0,表明该inode没有扩展属性,如果非零,起始地址为di_forkoff * 8(以字节为单位),其最大值为2048 byte。
extendedattribute fork的结构由inode core中的di_aformat结构决定,它可以是以下的任意一种,另外attribute必须以64bit为单位进行分配。
XFS_DINODE_FMT_LOCAL:extended attributes完全包含在inode中,可以通过将XFS_DFORK_APTR指向的指针转换为xfs_attr_shortform_t*来访问扩展属性的值。
XFS_DINODE_FMT_EXTENS:属性存储在另外的block中,inode中包含了指向这些block的一个指针数组。可以通过将XFS_DFORK_APTR指向的指针转换为xfs_bmbt_rec_t*的类型来访问。
XFS_DINODE_FMT_BTREE:包含attributes的extents存储在一个B+树的叶子节点中。inode中包含了这个树的根节点,可以通过将XFS_DFORK_APTR的指针转化为xfs_bmdr_block_t*的类型来访问。
这几种方式的区别就是扩展属性的存放位置以及存放管理方式不同。下面着重介绍以下常用的几种方式:
Ø XFS_DINODE_FMT_LOCAL
这种方式下,inode的di_aformat被设置为”local”,inode的所有扩展属性能够存储在inode的di_a中,而对应的di_a使用一个称之为shortform的结构。
其和inode中对应的示意图为:
其中namelen和valuelen标识了扩展属性中的key/value数组的长度。而key/value两者的数据统一存储在nameval结构中。
当inode的attributefork空间被shortform attributes使用完时,attribute format会迁移到”extents”模式。
Ø XFS_DINODE_FMT_EXTENTS
因为这中方式目前还没有太多的使用场景,所以,这里仅贴出其数据组织图:
3 总结
至此,XFS的核心架构及实现已经基本介绍完了。
————————————————