Btrfs

Btrfs（B-tree档案系统，通常念成Butter FS，Better FS或B-tree FS），一种支持写入时复制（COW）的文件系统，运行在Linux操作系统，采用GPL授权。Oracle于2007年对外宣布这项计划，并释出源代码，在2014年8月释出稳定版。目标是取代当时在Linux上广泛使用的ext3文件系统，改善文件系统的限制，增加单个文件大小上限支持，总文件系统大小支持以及文件检查支持。Btrfs还加入了可写快照（writable snapshots）、快照的快照（snapshots of snapshots）、内建磁盘阵列（RAID），以及子卷（subvolumes）等高级文件系统功能。在宣传上，Btrfs的特点是“容错、修复及易于管理”。

## 历史

Btrfs的核心数据结构——写时复制B树——最初是由IBM研究员Ohad Rodeh在2007年USENIX会议上提出的。Chris Mason当时是SUSE公司的ReiserFS工程师，后来加入了Oracle，并开始开发一种基于B树的新文件系统。

2008年，ext3和ext4文件系统的主要开发者曹子德表示，虽然ext4有改进的功能，但它不是一个重大的进步；它使用了旧技术，是一个权宜之计。曹子德说，Btrfs是一个更好的方向，因为“它在可扩展性、可靠性和易管理性方面提供了改进”。Btrfs也有“一些与reiser3/4相同的设计思想”。

Btrfs 1.0版本具有最终确定的磁盘格式，于2008年底发布，并于2009年被接受进入Linux内核主线。几个Linux发行版开始在安装过程中提供Btrfs作为根文件系统的实验性选择。

2011年7月，Btrfs自动碎片整理和擦除功能被合并到Linux内核主线的3.0版本中。除了Oracle的Mason外，富士通的Miao Xie也贡献了性能改进。2012年6月，Chris Mason离开Oracle加入Fusion-io，一年后又与Josef Bacik一起加入Facebook。在这两家公司工作期间，Mason继续他对Btrfs的工作。

2012年，两个Linux发行版将Btrfs从实验性状态转变为生产或支持状态：3月份的Oracle Linux，8月份的SUSE Linux Enterprise。

2015年，Btrfs被采用为SUSE Linux Enterprise Server（SLE）12的默认文件系统。

2017年8月，红帽公司在Red Hat Enterprise Linux（RHEL）7.4的发布说明中宣布，不再计划将Btrfs转变为一个完全支持的功能（它自RHEL 6 beta以来一直作为“技术预览”包含在内），并指出它将在RHEL 7发布系列中保持可用。2019年5月，Btrfs从RHEL 8中移除。RHEL从RHEL 6中的ext4转移到RHEL 7中的XFS。

2020年，Btrfs被选为Fedora 33桌面变体的默认文件系统。

## 特性

### 特性列表

#### 已实现

- 联机碎片整理
- 联机卷生长和收缩
- 联机块设备增加和删除
- 联机负载均衡（块设备间的对象移动以达到平衡）
- 文件系统级的镜像（类RAID-1）、条带（类RAID-0）
- 子卷（一个或多个单独可挂载基于每个物流分区）
- 透明压缩（目前支持zlib、LZO和ZSTD (从 4.14 开始支援)）
- 快照（只读和可写，写复制，子卷复制）
- 文件克隆
- 数据和元数据的校验和（目前是CRC-32C）
- 就地转换（带回滚）ext3/4与ReiserFS
- 只读存储的联合挂载，称为文件系统播种（只读存储用作可写 Btrfs 的写时复制支持）
- 用户定义的事务
- 块丢弃支持
- 发送/接收（将快照之间的差异保存为二进制流）
- 带外数据去重（需要用户空间工具）
- 能够处理交换文件和交换分区

#### 已实现但不建议在生产环境中使用

- 分层的每个子卷配额
- RAID 5，RAID 6

#### 计划但尚未实现

- 带内数据去重
- 在线文件系统检查
- 最多六个奇偶校验设备的 RAID，超越了 RAID 5 和 RAID 6 的可靠性
- 对象级 RAID 0，RAID 1 和 RAID 10
- 透明加密
- 持久读写缓存（L2ARC + ZIL，lvmcache 等）

### 克隆

Btrfs 提供了一个克隆操作，可以原子地创建一个文件的写时复制快照。这样的克隆文件有时被称为 `reflinks`，因为它们与拟议的相关 Linux 内核系统调用有关。

在克隆时，文件系统不会创建一个指向现有 `inode` 的新链接；相反，它会创建一个新的 `inode`，最初与原始文件共享相同的磁盘块。因此，克隆只能在同一个 Btrfs 文件系统的边界内工作，但是在某些情况下，自 Linux 内核 3.6 版本起，它可能会跨越子卷的边界。实际的数据块不会被复制；同时，由于 Btrfs 的写时复制（CoW）特性，对任何克隆文件的修改都不会在原始文件中可见，反之亦然。

克隆不应与硬链接混淆，硬链接是将多个文件名与单个文件关联的目录项。虽然硬链接可以被认为是同一文件的不同名称，但 Btrfs 中的克隆提供了最初共享所有磁盘块的独立文件。

GNU coreutils 自 7.5 版本支持了这个 Btrfs 特性，通过 `cp` 命令的 `--reflink` 选项可以使用克隆功能。

除了数据克隆（FICLONE），Btrfs 还支持通过 FIDEDUPERANGE 进行带外去重。这个功能允许两个具有（甚至部分）相同数据的文件共享存储空间。

### 子卷和快照

<image>

Btrfs子卷可视为一个单独的POSIX文件命名空间，向`mount`命令传递`subvol`或`subvolid`选项能够单独挂载相对应的子卷。其他子卷也可以通过挂载顶层子卷来访问，此时其他子卷作为顶层子卷的子目录供用户访问。

子卷可以在文件系统层次结构中的任何位置创建，也可以嵌套。嵌套的子卷在其父子卷中显示为子目录，类似于顶层子卷将其他子卷显示为子目录的方式。删除一个子卷前，需要确保该子卷内不存在嵌套的子卷；因此，不能删除顶层子卷。

Btrfs文件系统有一个默认子卷，最初设置为顶层子卷，在传递子卷选择选项时默认挂载。默认子卷可以根据需要更改。

Btrfs快照是一个与其他子卷共享数据（和元数据）的子卷，使用Btrfs的写时复制实现，对快照的修改在原始子卷中不可见。可写的快照可以视为原始文件系统的一个副本。例如，要回滚到一个快照，可以卸载修改过的原始子卷，并将快照挂载到它的位置来实现。此时，也可以删除原始子卷。

Btrfs的写时复制（CoW）特性意味着快照可以快速创建，同时刚刚创建时仅消耗很少的磁盘空间。由于快照是一个子卷，因此也可以创建嵌套的快照。对一个子卷进行快照不是一个递归的过程；因此，如果创建了一个子卷的快照，该子卷包含的每个嵌套子卷都会映射到同名的空目录中。

不能对一个目录进行快照，只有子卷才能有快照。然而，有一种涉及跨越子卷的reflinks的解决方案：创建一个新的子卷，包含指向目标目录内容的跨越子卷的reflinks。有了这个，就可以创建这个新卷的快照。

Btrfs中的子卷与传统的LVM逻辑卷非常不同。在LVM中，逻辑卷是一个单独的块设备，而Btrfs子卷更类似于POSIX命名空间。原始或副本中的任何一个在同一台计算机上挂载时，对btrfs进行dd或LVM快照会导致数据丢失。

### 子卷发送与接收

给定任意一对子卷（或快照），Btrfs可以在它们之间生成二进制差异（通过使用`btrfs send`命令），稍后可以重放（通过使用`btrfs receive`），可以接收到另一个Btrfs文件系统上。发送-接收功能实际上创建（并应用）了将一个子卷转换为另一个子卷所需的一组数据修改。

发送/接收功能可以与定期安排的快照一起使用，用于实现文件系统复制的简单形式，或用于执行增量备份。

### 配额组

<image>

配额组可以对一个子卷或快照可能消耗的空间设定上限。新的快照最初不消耗配额，因为它的数据与其父卷共享，但之后会因为新文件和对现有文件的写时复制操作而产生空间上的开销。当配额功能启用时，每个新的子卷或快照都会自动创建一个配额组。这些初始的配额组是可以分组（使用`btrfs qgroup`命令）成层次结构来实现配额池的构建块。

配额组只适用于子卷和快照，而不能对单个子目录、用户或用户组强制执行配额。然而，可以通过使用不同的子卷来实现所有需要强制执行配额的用户或用户组。

### 原地转换ext2/3/4和ReiserFS

由于在固定位置的元数据非常少，Btrfs可以适应后端存储设备的各种的空间布局。`btrfs-convert`工具可以通过在其未分配空间中嵌套等效的Btrfs元数据——同时保留原始文件系统的未修改副本，即原地转换一个ext2/3/4或ReiserFS文件系统。

转换需要创建整个ext2/3/4元数据的副本，而Btrfs文件只是指向与ext2/3/4文件使用的相同的块。这使得在不可逆转换之前，两个文件系统共享大部分块。由于Btrfs的写时复制特性，在所有文件修改过程中，原始版本的文件数据块都保持不变。在不可逆转换之前，只有在ext2/3/4中标记为空闲的块才用于保存新的Btrfs修改，这意味着转换可以随时撤销转换（但撤销转换会擦除在转换为Btrfs后所做的任何更改）。

所有转换后的文件都在Btrfs的默认子卷中。转换工具会在一个单独的子卷中创建包含对原始ext2/3/4文件系统所有引用的稀疏文件；这一文件可以作为一个只读磁盘映像单独挂载，允许同时访问原始文件系统和转换后的文件系统。删除这个稀疏文件会释放原有文件系统额外占用的空间，并使转换变得不可逆。

在主线Linux内核的4.x版本中，就地ext3/4缺乏使用与测试，不能保证稳定性。然而，该功能在2016年的`btrfs-progs` 4.6从头重写。Btrfs的就地文件系统转换功能自此宣布稳定。

就地从ReiserFS转换在2017年9月引入到Linux 4.13内核中。

### 联合挂载/种子设备

在创建一个新的Btrfs时，可以使用一个已存在的Btrfs作为一个只读的“种子”文件系统。新的文件系统将作为种子上的一个写时复制覆盖层，作为一种联合挂载的形式。种子可以稍后从Btrfs中分离，在此之前，重平衡器将简单地复制任何仍然被新文件系统引用的种子数据，然后分离。Btrfs的主要贡献者Mason认为这可能对Live CD安装程序有用，它可以从光盘上的一个只读Btrfs种子启动，在用户继续工作的同时，在后台将自己重平衡到安装磁盘上的目标分区，然后弹出光盘以完成安装而不需要重新启动。

### 加密

在2009年的采访中，Chris Mason表示计划为Btrfs增加加密支持。目前，将加密与Btrfs结合使用的一种解决方案是使用诸如dm-crypt / LUKS之类的全盘加密机制在底层设备上，并在该层之上创建Btrfs文件系统。

开发人员正在努力添加类似HMAC（SHA256）的键控哈希。

### 检查和恢复

Unix系统传统上使用fsck程序来检查和修复文件系统。这个功能是通过`btrfs check`程序实现的。从4.0版本开始，这个功能被认为是相对稳定的。然而，截至2023年6月，btrfs文档建议只有在“开发者或经验丰富的用户”建议时才使用其`–repair`选项。截至2023年6月，SLE文档建议使用一个Live CD，执行一个备份，并只在最后的手段时使用修复选项。

还有另一个工具`btrfs-restore`，可以用于从一个不可挂载的文件系统中恢复文件，而不修改损坏的文件系统本身。

在正常使用中，Btrfs基本上是自我修复的，并且可以在挂载时从损坏的根树结构中恢复，这是因为默认每30秒向永久存储刷新数据。因此，孤立的错误会导致下一次挂载时最多30秒的文件系统更改丢失。这个周期可以通过在`commit`挂载选项中指定所需的值来更改。（以秒为单位）

## 设计

Ohad Rodeh在2007年的USENIX提出的原始方案指出，广泛用作数据库的磁盘数据结构的B+树不能有效地实现基于写时复制的快照，因为它的叶子节点是连接在一起的：如果一个叶子被写时复制，它的兄弟节点和父节点也必须如此，以及它们递归的兄弟和父节点，直到整个树被复制。他建议使用一个修改过的B树（没有叶子链接），并且每个树节点都有一个引用计数，但是存储在一个专门的自由映射结构中，并且对树的平衡算法进行了一定的放松，使它们对写时复制友好。结果将是一个适合高性能对象存储的数据结构，可以执行写时复制快照，同时保持良好的并发性。

同年，在Oracle工作的Chris Mason开始了一个能够使用这种数据结构的快照能力文件系统的工作，几乎完全采用这种数据结构——不仅用于元数据和文件数据，而且递归地用于跟踪树本身的空间分配。这允许所有遍历和修改都通过同一份代码，只需要实现一次诸如写时复制、校验和和镜像等特性，就可以在整个文件系统的各个地方使用。

Btrfs包含树组成的几层结构，所有树都使用相同的B树实现。树存储了按照136位键排序的通用项。键中最高的64位是一个唯一的“对象id”。中间8位是一个项类型字段，用于硬编码到代码中作为树查找中的项过滤器。“对象”可以有多个类型的多个项。剩下的64位以类型特定的方式使用。因此，同一对象的项会在树中彼此相邻，按类型分组。通过选择某些键值，对象可以进一步按照类型排列。

内部树节点只是键-指针对的平面列表，其中指针是子节点的逻辑块号。叶节点包含打包到节点前面的项键和打包到节点末尾的项数据，在叶填充时两者向彼此增长。

### 文件系统树

在每个目录中，目录条目以“目录项”的形式出现，它们的键值的最低位是它们文件名的CRC32C哈希。它们的数据是一个“位置键”，或者是它指向的inode项的键。目录项一起可以作为路径到inode查找的索引，但不用于迭代，因为它们按照它们的哈希排序，有效地随机排列它们。这意味着在一个大目录中迭代和打开文件的用户应用程序将产生更多的磁盘寻道，因此在非相邻文件之间寻道——这是其他文件系统中具有哈希排序目录的显著性能损耗，例如ReiserFS， ext3（启用了Htree索引）和ext4，它们都有微型加密算法哈希过的文件名。为了避免这个问题，Btrfs每个目录条目都有一个"目录索引项"，其项的键值被设置为一个每个目录递增的计数器。因此，对这些索引项的迭代大致按照存储在磁盘上的相同顺序返回条目。

在多个目录中具有硬链接的文件有多个引用项，每个父目录一个。在同一目录中具有多个硬链接的文件将所有链接的文件名打包到同一个引用项中。这是一个设计缺陷，它将同一目录中的硬链接数量限制为多少个可以放入单个树块中。（在默认块大小为4 KiB、平均文件名长度为8字节和每个文件名头部为4字节的情况下，这将少于350个。）观察到大量使用同一目录中多个硬链接的应用程序，在这个限制下会失败，例如git、GNUS、GMame和BackupPC。

这个限制最终被移除（截至2012年10月已经合并等待在Linux 3.7中发布）通过引入溢出的"扩展引用项"来保存不适合的硬链接文件名。

#### 区段

文件数据保存在树外的“区段”中，它们是连续的磁盘数据块。区段块默认大小为4 KiB，没有头部，只包含（可能压缩的）文件数据。在压缩的区段中，单个块不是单独压缩的；相反，压缩流跨越整个区段。

文件有“区段数据项”来跟踪保存其内容的区段。项的键值是区段的起始字节偏移量。这使得在具有多个区段的大文件中进行有效的寻找，因为任何给定文件偏移量的正确区段可以用一个树查找来计算。

快照和克隆文件共享区段。当一个大的这样的区段的一小部分被覆盖时，结果的写时复制可能创建三个新的区段：一个小的包含被覆盖的数据，和两个大的包含覆盖两边未修改的数据。为了避免重写未修改的数据，写时复制可能会创建“书签区段”，或者只是现有区段的切片。区段数据项允许这样做，通过包含一个到它们正在跟踪的区段的偏移量：书签项是那些具有非零偏移量的项。

### 区段分配树

“区段分配树”充当文件系统的分配映射。与其他树不同，这个树中的项没有对象id。它们表示空间区域：它们的键值保存了它们所代表的区域的起始偏移量和长度。

文件系统将其分配的空间划分为“块组”，它们是可变大小的分配区域，交替地偏好元数据区段（树节点）和数据区段（文件内容）。数据和元数据块组的默认比例是1:2。它们旨在使用奥尔洛夫块分配器的概念来分配相关文件，并通过在组之间留下空闲空间来抵抗碎片化。（然而，Ext3块组有固定的位置，是根据文件系统的大小计算出来的，而Btrfs中的块组是动态的，并根据需要创建）每个块组都与一个“块组项“相关联。文件系统树中的inode项包含对它们当前块组的引用。

“区段项”包含一个到占用该区段的树节点或文件的反向引用。如果区段在快照之间共享，可能有多个反向引用。如果有太多的反向引用不能适合于项中，它们会溢出到单独的“区段数据引用项“中。反过来，树节点也有对它们所包含的树的反向引用。这使得通过对一对括住该区域的偏移量进行B树范围查找，然后跟随反向引用，可以找到任何空间区域中的哪些区段或树节点。对于重新定位数据，这允许从重新定位的块进行有效的向上遍历，快速找到并修复对这些块的所有向下引用，而不必扫描整个文件系统。这反过来又允许文件系统有效地在线缩小、迁移和碎片整理其存储。

区段分配树，与文件系统中的所有其他树一样，是写时复制的。写入文件系统可能会导致一个级联，其中改变了树节点和文件数据导致新的区段被分配，导致区段树本身发生变化。为了避免创建一个反馈回路，仍然在内存中但尚未提交到磁盘的区段树节点可以就地更新，以反映新复制的写时复制区段。

从理论上讲，由于区段分配树充当了一个B树版本的BSP树，因此不需要传统的可用空间位图。但实际上，为了加速分配，使用了页大小位图的红黑树。这些位图被持久化到磁盘（从Linux 2.6.37开始，通过`space_cache`挂载选项）作为特殊的区段，它们不受校验和和写时复制的影响。

### 校验和树和擦除

对数据和元数据都计算CRC-32C校验和，并存储为“校验和树”中的“校验和项”。元数据校验和有256位的空间，而数据校验和最多可以有一个完整的节点（大约4 KB或更多）。Btrfs有为未来版本的文件系统添加额外的校验和算法的计划。

每个连续的已分配块都有一个校验和项，每个块的校验和紧密地打包到项数据中。如果有太多的校验和不能适合，它们会溢出到一个新叶子中的另一个校验和项中。如果文件系统在读取一个块时检测到一个校验和不匹配，在内部镜像或RAID技术正在使用的情况下，它首先尝试从另一个设备 – 获取（或创建）这个块的一个好的副本。

Btrfs可以通过触发一个在后台执行的文件系统擦除作业来启动对整个文件系统的在线检查。擦除作业扫描整个文件系统的完整性，并自动尝试报告和修复它在沿途发现的任何坏块。

### 块和设备树

块设备被划分为1 GiB用于数据的物理块，和256 MiB用于元数据的物理块。多个设备上的物理块可以镜像或条带化成一个单一的“逻辑块”。这些逻辑块被组合成一个单一的逻辑地址空间，供文件系统的其余部分使用。

“块树”通过将其中的每个设备存储为一个“设备项”和逻辑块作为“块映射项”来跟踪这一点，提供了从逻辑到物理地址的正向映射。块映射项可以是以下几种类型之一：

single : 1个逻辑到1个物理块

dup : 1个逻辑块到1个块设备上的2个物理块

raid0 : N个逻辑块到N≥2个设备上的N≥2个物理块

raid1 : 1个逻辑块到N≥2个设备中的2个物理块，与传统的RAID 1不同，它有N个物理块

raid1c3 : 1个逻辑块到N≥3个设备中的3个物理块 ;

raid1c4 : 1个逻辑块到N≥4个设备中的4个物理块 ;

raid5 : N（对于N≥2）个逻辑块到N+1个设备上的N+1个物理块，其中1个物理块用作奇偶校验 ;

raid6 : N（对于N≥2）个逻辑块到N+2个设备上的N+2个物理块，其中2个物理块用作奇偶校验 “N”是在分配块时仍然有空闲空间的块设备的数量。如果N对于选择的镜像/映射不够大，那么文件系统就有效地没有空间了。

### 重定位树

碎片整理、缩小和重新平衡操作需要重新定位区段。然而，对正在重新定位的区段进行简单的写时复制将破坏快照之间的共享并消耗磁盘空间。为了保持共享，使用一个更新和交换算法，一个特殊的重定位树作为受影响的元数据的临时空间。首先将要重新定位的区段复制到它的目的地。然后，通过沿着受影响的子卷的文件系统树向上跟随反向引用，逐渐更新指向旧区段的元数据，使其指向新的区段；任何新更新的项都存储在重定位树中。一旦更新完成，重定位树中的项与受影响子卷中的对应项交换，然后丢弃重定位树。

### 超级块

文件系统中的所有树——包括块树本身——都存储在块中，这会产生一个引导问题，当挂载文件系统时。为了引导到一个挂载点，块树和根树所属的块的物理地址列表存储在超级块中。

超级块镜像保存在固定位置：每个块设备64 KiB处，以及64 MiB、256 GiB和1 PiB处的额外副本。当更新一个超级块镜像时，它的生成号递增。在挂载时，使用生成号最高的副本。除了SSD模式，所有超级块镜像都同时更新；而在SSD模式下，为了提供一定程度的磨损平衡，超级块镜像之间交替更新。

## 基本资料

- 开发者：SUSE、Meta、西部数据、甲骨文公司、富士通、Red Hat
- 发布：Linux
- 发布：稳定版本：6.4，2023年6月、不稳定版本：6.4，2023年6月
- 目录内容：B树
- 文件分配：extents
- 最长文件名：255字节
- 最大文件数量：264（每个子卷）
- 最大卷容量：16 EiB
- 最大文件尺寸：16 EiB
- 文件名字符集：除`'/'`和`NUL`（`'\0'`）以外的所有字符
- 日期记录：内容更改时间（mtime），属性更改时间（ctime），访问时间（atime）
- 日期分辨率：纳秒
- 属性：POSIX，扩展文件属性
- 文件系统权限：POSIX，访问控制表
- 透明压缩：是（zlib、LZO和ZSTD（从Linux 4.14开始））
- 透明加密：否（计划支持）
- single_instance_storage：是（计划支持，通过补丁支持）
- 操作系统：Linux , ReactOS