Btrfs文件系统深度剖析|磁盘布局及BTree

什么是Btrfs

Btrfs是新一代的文件系统(虽然已经开发十几年了),说其是新一代文件系统是因为从特性上它不仅具有普通文件系统所有特性,还囊括了卷管理的特性。也就是说,Btrfs可以实现对多个磁盘的管理、可以创建子卷、还可以打快照等等,同时Btrfs甚至还支持数据的加密和压缩。总的来说, Btrfs文件系统几乎是无所不能

也可能是它的特性太多了,因此虽然已经开发了十几个年头了,但稳定性似乎还不是特别好。不管如何,它是一个比较先进的文件系统,下面本文将给大家介绍一下这个文件系统。由于Btrfs非常复杂,完全介绍清楚估计得写一本书,因此本号会分几篇文章进行介绍,今天先介绍一下Btrfs是如何管理磁盘数据的

Btrfs磁盘管理简述

Btrfs可以在多个磁盘上建立文件系统,管理磁盘的能力类似于Linux下面的卷管理(LVM),可以实现RAID0、RAID1、RAID5、RAID6和RAID10等模式。以RAID0为例,如图1所示,Btrfs会将两个磁盘抽象为一个大的逻辑硬盘。这样,所有文件系统级别的操作(例如分配磁盘空间等)都是在这个逻辑磁盘(这个逻辑盘在操作系统层面并不存在)上进行的。而当IO提交到逻辑盘之后,Btrfs中的插件根据磁盘的RAID形式和chunk树中存储的设备信息进行重新换算得到实际的物理设备及偏移信息,并重新构建新的bio。

Btrfs文件系统深度剖析|磁盘布局及BTree

图1 Btrfs RAID0示意图

仍然以2个磁盘构成的RAID0为例介绍一下写数据时IO处理的大致过程。假设该文件系统条带大小是4K,IO大小是8K,且是4K对齐的。文件系统的前期处理流程与其它文件系统没有多少差异,前期仍然是给该写请求的数据分配磁盘空间,也就是确定数据落入磁盘的偏移。而此时的偏移是上面所述的逻辑磁盘的偏移。最终是调用writepages接口将缓存数据写入磁盘,此时不是直接调用submit_bio接口,而是调用btrfs自己封装的一个submit_one_bio接口,该接口用于向逻辑磁盘提交IO,而在该流程中封装了一个IO重映射的逻辑,也就是将上述逻辑地址转换成物理地址。对于RAID0,此IO将会被切割为2个IO,并映射到2个独立的物理磁盘,然后调用submit_stripe_bio将切割后的IO提交到物理磁盘。

Btrfs文件系统深度剖析|磁盘布局及BTree

图2 RAID0请求处理示意图

文件系统磁盘布局

Btrfs遵循Linux文件系统的惯例,照例以超级块作为文件系统的入口,并在超级块中描述了该文件系统的属性。在超级块中有2个比较重要的域,分别是root和chunck_root,这两个域分别指向了树根树Chunck树的根的逻辑位置(注意,这里是逻辑位置,而不是磁盘的物理位置,下文详述)。

超级块与B+树

Btrfs文件系统的所有数据都是通过这2棵树进行维护的。一个是Chunk树,这个里面保存着文件系统的设备信息(例如具体包含几块磁盘)。另外一个是树根树,在这个树里面包含着很多其它树的树根。一个刚刚格式化的文件系统包含如下根:

- 树根树

- 分配extent的树

- 缺省子卷的树

Btrfs文件系统深度剖析|磁盘布局及BTree

图3 磁盘数据布局概图

树根树记录了extent树的根块以及每个子卷树和快照树的根块和名称。当有事务提交时,根块的指针将会被事务更新为新的值。此时该树新的根块将被记录在FS的超级块中。

在文件系统中树根树好像是其它树的文件夹,而且它有该文件系统的记录着所有快照和子卷名称的文件夹项。在树根树中,每一个快照和子卷都有一个objectid,并且最少有一个对应的btrfs_root_item结构体。树中的目录项建立了快照和子卷名称与这些根项(btrfs_root_item)的关联关系。由于每次事务提交的时候根项Key都会被更新,因此目录项的generation的值是(u64)-1,这样可以使查找代码找到最近的可用根。

extent树用于在设备上管理已经分配的空间。剩余空间能够被划分为多个extent树,这样可以减少锁碰撞,而且为不同的空间提供不同的分配策略。

如图3描绘了一个树根的集合。超级块指向根树,根树又指向extent树和子卷树。根树也有一个文件夹来映射子卷名称和在根树中的btrfs_root_items结构体。本文件系统有一个名为default的子卷(该子卷在格式化时创建)和一个该子卷的名为snap的快照(由管理员在稍晚的时候创建的)。本例中,该子卷在快照创建后没有发生任何变化,因此两者的指针是指向同一颗树的根。

除了上述树之外,还有在树根树中还管理着很多其它的树。例如,FS Tree 管理文件相关的元数据,如 inode,dir等; checksum Tree 保存数据块的校验和等等。

Btrfs BTree的组织形式

既然说到了Btree(本文所说的BTree也就是B+Tree,在概念上本文不做过多区分),就不能不提Btrfs中的一些Btree的相关数据结构,先来看下extent_buffer。顾名思义,即是extent在内存中的缓冲,它是btrfs文件系统磁盘空间管理的核心,btrfs通过btree来管理各种元数据,比如inode、目录项等。这些B+树的每一个节点(包括叶子节点和上层节点)都存储在一个单位的extent中,每次要读取元数据或者要向磁盘写入元数据,则通常先先将数据读入extent_buffer或者向extent_buffe写入数据。

Btrfs的BTree提供了一种通用的方法来存储不同的数据类型(具体类型请参考后文)。在其内部,它仅仅知道3中数据结构,分别是键、项目和块头。这3中数据结构的定义如图4所示,分别是btrfs_disk_key、btrfs_item和btrfs_header

Btrfs文件系统深度剖析|磁盘布局及BTree

图4 BTree的主要数据结构

树的上层节点(非叶子节点)仅仅包含键-块指针对。树的叶子节点被分为2部分,并且向彼此生长(也就是前半部分由磁盘块的头部向尾部使用空间,而后半部分则是由磁盘块的尾部向头部使用空间)。叶子节点包含一个固定大小(也就是btrfs_item数据结构)组成的数组,以及存储项数据的区域。在btrfs_item数据结构中的偏移和大小域指示了如何从本叶子节点找到项数据。图5是BTree叶子节点布局的示意图。

Btrfs文件系统深度剖析|磁盘布局及BTree

图5 BTree叶子节点的布局

项数据的大小是可变的,并且定义了各种文件系统数据结构来表示不同类型的项数据。数据结构btrfs_disk_key中的type字段指示存储在项中的数据类型。

块头(btrfs_header)包含块内容的校验和、拥有该块的文件系统的UUID、树中该块的层次(区别该块为叶子节点还是中间节点)以及该块应该所在的块号。这些字段允许在读取数据时验证元数据的内容。任何指向BTree块的内容都存储了它期望该块具有的generation字段。这允许BTRFS检测介质上的虚写或错位写入。

较低节点的校验和不存储在节点指针中,以简化FS写回代码。在块插入BTree的时候可以知道generation的值,但校验和仅在将块写入磁盘之前才会计算。使用generation将允许BTRFS检测幻象写入(假写),而不必在每次更新较低节点校验和时查找和更新较高节点。

generation字段与分配块的事务ID相对应,该ID使增量备份变得容易,并由copy-on-write事务子系统使用。

说了这么多,也用图示的方式说明了叶子节点的磁盘布局情况,下面是叶子节点的数据结构。可以看出最前面是该数据结构最前面是btrfs_header数据结构,紧跟着是btrfs_item结构的数组。而项数据并不在该数据结构中体现,因为项数据类型是不确定的,关于项数据的获取是通过项的记录动态解析的。

struct btrfs_leaf {

struct btrfs_header header;

struct btrfs_item items[];

} __attribute__ ((__packed__));

前文重点介绍了叶子节点,除了叶子节点之外还有非叶子节点,也就是中间节点和根节点。前文已经交代,BTree只在叶子存储具体的数据信息,而非叶子节点则只存储键-指针对。非叶子节点涉及的数据结构如图6所示,其与叶子节点的差异是在btrfs_header之后是btrfs_key_ptr数据结构的数组。

Btrfs文件系统深度剖析|磁盘布局及BTree

图6 BTree非叶子节点的数据结构

可以看出其前面跟叶子节点一样,同样包含一个btrfs_header,而之后则是btrfs_key_ptr数据结构。该数据结构内部包含一个btrfs_disk_key和其指向的磁盘块的位置及generation信息。

最后给出一个包含非叶子节点和叶子节点的简单结构的BTree示意图,这里面包含一个根节点和两个叶子节点。图中红色虚线是数据块的指向关系。蓝色虚线指示该数据在磁盘上的存储位置,灰色虚线则用于说明该数据结构的详细定义。

Btrfs文件系统深度剖析|磁盘布局及BTree

图6 BTree结构示意图

最后需要强调的是,在Btrfs文件系统中是用BTree来管理所有的内容,包括磁盘空间、文件、文件内容数据、校验和和事务日志等等。其中的差异是包含的数据类型不同。

本文先到这,后续文件本文将介绍一下Btrfs BTree中的不同的数据类型及数据结构。

相关推荐