Btrfs 文件系统入门

Table of Contents

Btrfs 是一种新型的 写时复制 ( Copy on Write ) Linux 文件系统,已经并入内核主线。Btrfs 在设计实现高级功能的同时,着重于 容错修复 以及易于 管理

它由 Oracle, Red Hat, Fujitsu, Intel, SUSE, STRATO 等企业和开发者共同开发,Btrfs 以 GNU GPL 协议授权,同时也欢迎任何人的贡献

警告: Btrfs 有一些功能被认为是实验性的特性 

准备工作

要使用一些用户空间工具的话,需要 安装 基础操作必须的 btrfs-progs 软件包

如果需要从 Btrfs 文件系统引导(比如说内核和内存盘在一个 Btrfs 的分区上),请检查 启动引导器 是否支持 Btrfs 

创建文件系统

下文展示了如何创建一个新的 Btrfs 文件系统

单一设备上的文件系统

要在分区 /dev/partition 上创建一个 Btrfs 文件系统,执行:

$ mkfs.btrfs -L mylabel /dev/partition

Btrfs 用于元数据的 默认节点大小 (node size) 为 16KB ,而用于数据的 默认扇区大小 (sector size) 等于 页面大小 (page size) 并会自动检测。 要对元数据使用较大的节点大小 (必须为扇区大小的倍数,最大允许 64KB),请通过 -n 开关为 node size 指定一个值。如下例所示,使用 32KB 块大小:

$ mkfs.btrfs -L mylabel -n 32k /dev/partition
根据 mkfs.btrfs(8) 手册页内容:

较小的节点大小会增加碎片,但也会让 B-trees 更高,进而使得锁定争用(locking contention)更少

较高的节点大小则能有更好的打包(packing)和更少的碎片,但代价是,更新元数据块时会使用更多的内存

多设备文件系统

警告: Btrfs 的 RAID 5 和 RAID 6 模式存在致命缺陷, 不应当用于任何场景,除非用来做丢失数据的测试

多个设备可以用来创建一组 RAID

支持的 RAID 级别有 RAID 0, RAID 1, RAID 10, RAID 5 和 RAID 6

从 5.5 版本内核开始,也有了对 RAID1c3 和 RAID1c4 的支持,它们分别是 3 份冗余和 4 份冗余的 RAID 1

数据和元数据的 RAID 等级可以独立地用 -d-m 参数指定:默认情况下 元数据 使用镜像 ( RAID1 ),而 数据 则会被 条带化 (RAID0)

$ mkfs.btrfs -d raid0 -m raid1 /dev/part1 /dev/part2 ...

可以使用 -d single -m raid1 来创建一个 JBOD 配置 ,它会将磁盘视为一个文件系统,但是不会复制文件

要将多个 Btrfs 设备作为一个池使用的话,需要将 udev 钩子或者 btrfs 钩子加入到 /etc/mkinitcpio.conf 中 

注意:

  • 可以稍后再将设备添加到多设备文件系统中
  • 多个设备可以大小各异。但是,如果在一个 RAID 配置中一个硬盘的大小比其他的都大,那么它多出的空间将不会被使用
  • 有些 引导加载程序 不支持多设备文件系统,比如 Syslinux
  • Btrfs 不会自动从速度最快的设备读取,因此混合使用不同类型的磁盘会导致性能表现不一致

配置文件系统

写时复制 (CoW)

默认情况下 Btrfs 对所有文件使用 写时复制 (CoW)

参阅 Btrfs 系统管理指南相关章节 以获取实现细节以及它的优点和缺点 

停用 CoW

要对某个子卷上的新文件停用写时复制,使用 nodatacow 挂载 选项:

  • 这只会影响 新创建的文件 ,写时复制仍然会在已存在的文件上生效
  • nodatacow 参数同样会 禁用压缩
注意: 在单个文件系统中,无法使用 nodatacow 参数挂载某些子卷,而其他的使用 datacow 参数。第一个被挂载子卷的挂载参数将会应用于其他所有子卷 

要单文件或目录禁用写时复制特性,请使用下面的命令:

$ chattr +C [文件/目录的地址(path)]

这会为这个文件的单个引用停用写时复制,如果这个文件不只有一个引用(例如通过 cp –reflink=always 生成或者在文件系统快照中),写时复制依然生效

注意: 在 Btrfs 上,'C' 标志应该被设置在新建的或者是空白的文件/目录,如果被设置在已有数据的文件,当块分配给该文件时,文件将不确定是否完全稳定

如果 'C' 标志被设置给一个目录,将不会影响目前的目录,但在该目录创建的新文件将具有 No_COW 属性

提示: 可以用下面的方法为已存在的文件或目录停用写时复制:

$ mv /path/to/dir /path/to/dir_old
$ mkdir /path/to/dir
$ chattr +C /path/to/dir
$ cp -a /path/to/dir_old/* /path/to/dir
$ rm -rf /path/to/dir_old
需要保证这个过程中目标文件不会被使用,同时注意下面描述的 mv 或 cp --reflink 并不起作用 

创建轻量副本

默认情况下,使用 cp 复制 Btrfs 文件系统上的文件时,会创建 实际副本 。要 创建 引用 原始数据轻量级副本 ,请使用 reflink 选项:

$ cp --reflink source dest 
参阅 cp 的手册页获得关于 --reflink 标志的更多信息

压缩

只有在加入挂载选项后创建或修改的文件才会被压缩

Btrfs 支持 透明自动 压缩 。这不单减小了文件的大小,在某些特定的场景下 (比如单线程、重文件 I/O) 还 提高了性能

尽管在其他的场景下(比如多线程和/或具有大文件 I/O 的 CPU 密集型任务)还是显著地影响了性能

使用更快的压缩算法,比如 zstd 和 lzo ,通常可以获得更好的性能,这个 性能测试 提供了详细的对比

compress=alg 挂载选项可自动考虑评估为每个文件启用压缩,其中的 alg 处可以选填为 zlib , lzo , zstd , 或者 no (即不压缩)。通过此选项,Btrfs 将检查 数据的第一部分 是否能将其 压缩

  • 如果是,则会压缩该文件的整个写入
  • 否则将不会压缩任何内容
由此,如果数据的第一部分没有被缩减,那么即使数据的其余部分将能大大缩减,写入时也不会被压缩

这样做是为了防止让磁盘一直等待着写入,直到所有要写入的数据传递给 Btrfs 并被压缩后为止 

另外可以改用 compress-force =alg 挂载选项,这将让 Btrfs 跳过对 压缩是否可缩减数据的第一部分 的检查,并对每个文件启用自动压缩

最坏的情形下,这可 (稍微) 导致更多的空间被占用,并无故提高 CPU 占用率

不过,对多个混合使用系统的经验测试表明,与仅使用 compress=zstd (其也具有 10% 磁盘压缩率) 相比,使用 compress-force=zstd 可以显著提高约 10% 的磁盘压缩率,从而节省了 20% 的总的有效磁盘空间

给现存文件启用压缩,可使用 btrfs filesystem defragment -calg 命令,alg 处可选填为 zlib,lzo 或 zstd。举例来说,要用 zstd 方式给整个文件系统重新压缩,执行下列命令:

$ btrfs filesystem defragment -r -v -czstd /

要在新的 Btrfs 分区上安装 Linux 时就启用压缩功能 请在 挂载 文件系统时使用 compress 选项:

$ mount -o compress=zstd /dev/sdxY /mnt/

在配置过程中,请在 fstab 文件中把 compress=zstd 添加到 根目录文件系统挂载选项

通过执行 chattr +c,也可以在不使用 compress 选项的情况下为每个单文件启用压缩属性。对目录执行会使这个目录下新文件自动被压缩。

如果使用 zstd 参数,使用较旧版本内核或者尚不支持 zstd 的 btrfs-progs 的系统可能不能读取或修复您的文件系统。

GRUB 在 2.04 版本中引入了对 zstd 的支持。使用此后版本时,请通过手动运行 grub-install (需添加适用于机器 BIOS/UEFI 设置的选项参数) 确保安装在 MBR/ESP 中的引导加载程序已确实升级,因为这些事情不会自动完成

rEFInd 在 0.11.4 以前的版本缺少对 zstd 的支持,可换用 refind-gitAUR,使用单独的没有启用 zstd 的引导分区,或者使用下例命令将引导文件的压缩方式重置为其它受支持的压缩方式:
$ btrfs filesystem defragment -v -clzo /boot/*

查看压缩类型和压缩比

compsize 软件包能获取出一个文件列表 (或一整个 Btrfs 文件系统),并测量出它们使用的压缩类型和其有效压缩比

不过,其给出的未压缩时大小数值不一定能和其他程序 (比如 du) 给出的数值吻合

因为一个文件可能被多次引用或者即使文件的一部分不再被任何地方使用 (但其未被垃圾回收),每一文件所占空间范围也只计数一次

-x 选项可让程序运行保持在单一个文件系统上,这在 compsize -x / (检查根目录) 之类的情况下很有用,可以避免程序去尝试访问非 Btrfs 子目录从而导致整个程序运行失败

子卷

btrfs 子卷不是 (也不能看作) 块设备,一个子卷可以看作 “POSIX 文件名字空间”,这个名字空间可以通过子卷上层访问,也可以独立挂载 

每个 btrfs 文件系统都有一个 ID 为 5顶层子卷 。它可以挂载为 / (默认情况下),或者可以挂载为 另一个子卷

子卷可以在文件系统中移动,它们通过其 ID 而不是路径来标识

创建子卷

要创建一个子卷:

$ btrfs subvolume create /path/to/subvolume

列出子卷列表

要列出 当前路径 (path) 下的子卷和它们的 ID:

$ btrfs subvolume list -p path

删除子卷

要删除一个子卷:

$ btrfs subvolume delete /path/to/subvolume
自 Linux 4.18 起, 用户可以像移除常规目录一样删除一个子卷 (用 rm -r, rmdir 命令)

挂载子卷

可以使用 subvol=/path/to/subvolumesubvolid=objectid 挂载标志来安装子卷,就像文件系统分区一样

例如,可以拥有一个名为 subvol_root 的子卷,并将其挂载为 /

通过在文件系统的顶层创建各种子卷,然后将它们挂载到适当的挂载点,可以模仿传统的文件系统分区

因此,可以使用 #快照 轻松地将文件系统(或其一部分)恢复到先前的状态

提示:

  • 不使用顶层子卷 (ID=5) 挂载为根目录,可以更方便地修改子卷的布局结构
  • 相反,可考虑创建新的子卷,然后挂载为 /
注意: 大多数挂载选项适用于整个文件系统,并且只有要挂载的第一个子卷的选项才会生效,这是因为没有实现,未来可能会发生变化

以 root 用户身份挂载子卷

要使用一个子卷作为根挂载点,可以使用 rootflags=subvol=/path/to/subvolume 一个 内核启动参数 指定子卷,并在 /etc/fstab 中编辑根挂载点并指定挂载选项 subvol=

或者用 rootflags=subvolid=objectid 作为内核参数,并可以/etc/fstab 中用 ID 指定子卷 subvolid=objectid 作为挂载选项 

改变默认子卷

如果挂载时 不指定 subvol= 选项 便会挂载默认子卷。要改变默认子卷,执行:

$ btrfs subvolume set-default subvolume-id /

subvolume-id 可以通过#列出子卷列表获得

注意: 在安装了 GRUB 的系统上,在改变默认子卷以后不要忘记运行 grub-install

通过 btrfs subvolume set-default 修改默认子卷将会导致文件系统的最顶层无法访问,除非使用 subvol=/ 或者 subvolid=5 挂载参数

配额

警告: Qgroup 尚且不稳定且在有(过多)快照的子卷上应用配额可能会导致性能问题,比如在删除快照的时候

Btrfs中的配额支持是通过使用 配额组Qgroup 在子卷级别实现的:默认情况下,每个子卷都以 0/subvolume_id 的形式 分配 配额组

但是,如果需要的话,可以使用任意数字创建配额组 

要使用 Qgroup,首先需要启用它:

$ btrfs quota enable path

从此时开始,新创建的子卷将由这些配额组控制。为了能够为已创建的子卷启用配额:

  1. 正常启用配额
  2. 使用它们的 subvolume_id 为每个子卷创建一个配额组
  3. 重新扫描它们:
$ btrfs subvolume list path | cut -d' ' -f2 | xargs -I{} -n1 btrfs qgroup create 0/{} path
$ btrfs quota rescan path

Btrfs 中的配额组形成 树层次 结构,其中 Qgroup 附加到子卷。大小限制由每个 Qgroup 独立配置且在并在包含给定子卷的树中达到任何限制时应用。配额组的限制可以应用于 总数据 使用, 非共享数据 使用, 压缩数据 使用或 全部

文件复制和文件删除可能都会影响限制,因为如果删除原始卷的文件并且只剩下一个副本,则另一个 Qgroup 的非共享限制可能会更改

例如,新快照几乎与原始子卷共享所有块,对子卷的新写入将向专用限制提升,一个卷中的公共数据的删除将升高到另一个卷中的专用限制

要对 Qgroup 应用限制,请使用命令 btrfs qgroup limit 。根据具体情况,使用 总限制非共享限制 (-e)或 压缩限制 (-c)。显示文件系统使用中给定路径的使用情况和限制:

$ btrfs qgroup show -reF path

提交间隔

将数据写入文件系统的频率由 Btrfs 本身和系统的设置决定。Btrfs 默认设置为 30 秒 检查点间隔,新数据将在 30 秒内被提交到文件系统。 这可以通过在 /etc/fstab 增加 commit 挂载参数来修改:

LABEL=arch64 / btrfs defaults,noatime,compress=lzo,commit=120 0 0
系统范围的设置也会影响提交间隔,它们包括 /proc/sys/vm/* 下的文件,这超出了本文章的范围

SSD TRIM

Btrfs 文件系统能够从支持 TRIM 命令的 SSD 驱动器中 释放 未使用的块

内核从 5.6 版本开始提供了 异步丢弃 (asynchronous discard)支持,可使用挂载参数 discard=async 启用

已释放的空间范围不会被马上丢弃,它们会被集中起来并在稍后由一个单独的工作线程进行 TRIM,这将能改善提交延迟

使用

交换文件

自 Linux 内核版本 5.0 起 Btrfs 提供 交换文件 支持。对于内核版本 5.0+, Btfrs 有原生但带些许限制的交换文件支持:

  • 交换文件不可以放在快照子卷上,正确的过程是 创建一个新子卷 来存放交换文件
  • Btrfs 不支持跨多设备文件系统上的交换文件
警告: Linux 内核 5.0 之前的版本不支持交换文件

在 5.0 之前的内核版本上搭配 Btrfs 使用交换文件可导致文件系统损坏

显示已使用的/空闲空间

像 df 这样的用户空间工具可能不会准确的计算剩余空间,因为并没有分别计算文件和元数据的使用情况 

推荐使用 btrfs filesystem usage 来查看使用情况。比如说:

$ btrfs filesystem usage /

碎片整理

Btrfs 支持通过配置挂载参数 autodefrag 来实现在线的碎片整理。要手动整理根目录的话,可以使用:

$ btrfs filesystem defragment -r /

使用不带 -r 开关的上述命令将导致仅整理该目录的子卷所拥有的元数据,这允许通过简单地指定路径进行单个文件碎片整理

对具有 CoW 副本(快照副本或使用cp --reflink或 bcp 创建的文件)进行碎片整理以及使用带压缩算法的 -c 开关进行碎片整理可能会导致生成两个不相关的文件从而增加磁盘使用量

RAID

检修 (Scrub)

Scrub 是一种 "在线文件系统检查工具"。它能读取文件系统中的文件和元数据,并使用校验值和 RAID 存储上的镜像区分并修复损坏的数据

警告: 运行 scrub 会阻止系统待机

手动启动

启动一个(后台运行的)包含 / 目录的文件系统在线检查任务:

$ btrfs scrub start /

检查该任务的运行状态:

$ btrfs scrub status /

通过服务或者定时器启动

  • btrfs-progs 软件包带有 btrfs-scrub@.timer 系统单元,用来每月运行 scrub 命令
    • 通过添加挂载点的参数来启用它,例如btrfs-scrub@-.timer (/) 或者 btrfs-scrub@home.timer (/home).
  • 也可以通过启动 btrfs-scrub@.service 来手动运行 scrub (使用同样的挂载点参数)
    • 相较 (以 root 用户身份运行) btrfs scrub,这么做的优点是会记录在 Systemd 日志中

数据平衡 (Balance)

Balance 将会通过分配器再次传递文件系统中的所有数据。它主要用于在 添加删除 设备 时跨设备重新平衡文件系统中的数据

如果设备出现故障,余额将为冗余 RAID 级别重新生成缺失的副本

在单设备文件系统上,余额对于(临时)减少分配但未使用(元)数据块的数量也是有用的。有时候这对于解决 "filesystem full" 故障 来说也是必须的:

$ btrfs balance start /
$ btrfs balance status /

快照

快照是和特定子卷共享文件和元数据的特殊子卷, 利用了 btrfs 的写时复制特性

要创建一个快照:

$ btrfs subvolume snapshot source [dest/]name
  • source:要创建快照的对象
  • [dest/]name:快照安放路径
  • 加入 -r 参数可以创建一个 只读快照
    • 为只读快照创建一个快照可以获得一个 只读快照的可写入版本
注意: 快照不是递归包含的,这意味着子卷内的子卷在快照里是空目录

发送和接收

可以通过 send 命令发送一个快照,通常会与 btrfs 中的 receive 组成管道。例如将快照 /root_backup (也许是/的备份) 发送到 /backup:

$ btrfs send /root_backup | btrfs receive /backup

注意:只能发送 只读快照

上面的命令在将子卷复制到外部设备 (例如备份驱动器) 时会很有用 

也可以只发送两个快照间发生变化的部分,例如如果已经发送了快照 root_backup ,然后又建立了一个新的只读快照 root_backup_new ,可以这样完成增量发送:

$ btrfs send -p /root_backup /root_backup_new | btrfs receive /backup

现在的 /backup 的快照会是 root_backup_new

去重

使用写时复制,Btrfs 能够复制文件或整个子卷而无需实际复制数据

但是,无论何时更改文件,都会创建一个新的“真正的”副本

重复数据删除更进一步,通过主动识别共享公共序列的数据块并将它们组合到具有相同写时复制语义的范围内

专用于 Btrfs 分区去重的工具包括 duperemovebedupAURbtrfs-dedup 。人们可能还希望仅使用基于文件的级别对数据进行重复数据删除,比如 rmlintjdupesAUR 或者 dduper-gitAUR

此外,Btrfs开发人员正致力于带内(也称为同步或内联)重复数据删除,这意味着在将新数据写入文件系统时完成重复数据删除

目前,它仍然是一个在 out-of-tree 开发的实验

已知问题

加密

Btrfs 目前还没有内建的加密支持,但未来可能加入此功能。可以在运行mkfs.btrfs前加密分区

如果已经创建了文件系统,可以使用EncFS或TrueCrypt,但是这样会无法使用 btrfs 的一些功能

TLP

使用 TLP 需要特殊的预防措施,以避免文件系统损坏

检查 btrfs 文件系统问题

btrfs check 工具目前有一些已知问题,在继续深入阅读了解之前,您不应该直接运行它

提示和技巧

无分区 Btrfs 磁盘

警告: 大多数用户不希望这种类型的设置,应该在常规分区上安装 Btrfs

此外,GRUB 强烈建议不要安装到无分区磁盘,请考虑为 mkfs.btrfs 使用 --alloc-start 参数以留出更大空间给 GRUB

Btrfs 能应用到整个设备上,替代 MBR 或 GPT 分区表,但是并不要求一定这么做,最简单的方法是 在一个已存在的分区上创建 Btrfs 文件系统。 如果选择用 Btrfs 替代分区表, 可以用 #子卷 模拟不同的分区。下列是在单个无分区设备上使用 Btrfs 文件系统的限制:

  • 不能在 同一磁盘 上的 不同分区 上放置 其它的文件系统
  • 如果使用 5.0 之前版本的 Linux 内核,则不能使用 交换分区 ,因为 Btrfs 在 5.0 前不支持 交换文件,并且也无处创建 交换分区
  • 不能使用 UEFI 启动

运行下面的命令把整个设备的分区表替换成 Btrfs:

$ mkfs.btrfs /dev/sdX

如果设备上存在分区表,则需要使用:

$ mkfs.btrfs -f /dev/sdX
例如 /dev/sda 而不是 /dev/sda1

后一种形式会格式化现有的分区而不是替换掉原有的分区表

由于根分区是 Btrfs 文件系统,请确保已将 btrfs 编译进内核, 或者将 btrfs 放入 mkinitcpio.conf#MODULES 中并且 重新生成 initramfs

像使用普通的 MBR 分区表存储设备一样安装 启动管理器, 参考 GRUB 相关文档

如果内核因为 Failed to mount /sysroot. 错误无法启动, 请在 /etc/default/grub 里添加 GRUB_PRELOAD_MODULES="btrfs" 并生成 GRUB 配置文件 

从 Ext3/4 转换

到 2015 年中后期,Btrfs 的邮件列表中报告了多起转换失败的案例。尽管近期的更新可能有所修复,但还是建议谨慎使用。在开始之前请确定有可用的备份并且愿意承担丢失数据的风险

5.6.1 及之前版本的 btrfs-progs 有一个 Bug,它会导致生成的 Btrfs 文件系统的最后一块组 (last block group) 含有大小错误。这个 Bug 已经在 5.7 版本中修复。请在 5.7-1 及以上版本的 btrfs-progs 上使用 brtfs-convert 功能

从安装 CD 启动,然后转化分区:

$ btrfs-convert /dev/partition
  1. 挂载转换后的分区
  2. 修改 /etc/fstab 文件,指定分区类型 (type 为 btrfs,并且 fs_passno[最后一列] 要修改为0,因为 Btrfs 在启动时并不进行磁盘检查)
  3. 分区的 UUID 将有改变,所以使用 UUID (指定分区) 时,请更新 fstab 中相应的条目
  4. chroot 到系统并重建 GRUB 条目
    • 如果正在转换根目录,还需要在 chroot 环境中重建初始化内存盘 (mkinitcpio -p linux)
    • 如果 GRUB 不能启动 (例如有 'unknown filesystem' 错误),则需要:
      1. 重新安装 (grub-install /dev/partition)
      2. 重新生成配置文件 (grub-mkconfig -o /boot/grub/grub.cfg)
注意: 如果转换过程中有任何异样,不管是无法挂载新转换的 Btrfs 文件系统或是无法往其中写入数据,只要备份子卷 /ext2_saved 还在,就可以进行回滚

请使用 btrfs-convert -r /dev/partition 命令进行回滚,这将会丢弃任何对新转换 Btrfs 文件系统的更改 

确认没有问题后,通过删除 ext2_saved 备份子卷完成转换的最后一步。请注意,如果没了它 (备份子卷),将没办法还原回 ext3/4 文件系统。

# btrfs subvolume delete /ext2_saved

最后通过 Balance 回收空间

请记住,以前安装的一些应用程序必须适配 Btrfs

值得注意的是,需要特别小心以避免文件系统损坏,不过其他应用程序也可能从 Btrfs 某些功能中获益

校验和 (Checksum) 硬件加速

CRC32 是英特尔 (Intel) SSE4.2 中的新指令

要验证 Btrfs 校验和是否有硬件加速:

$ dmesg | grep crc32c
Btrfs loaded, crc32c=crc32c-intel

如果看到的是 crc32c=crc32c-generic,则很有可能是因为根分区是 Btrfs,并且须要之后将 crc32c-intel 编译进内核中才能使其正常生效

单纯将 crc32c-intel 放入 mkinitcpio.conf 是不会生效的

损坏恢复

警告: btrfs check 工具有一些已知问题

btrfs-check 不能在一个已挂载的文件系统上工作。为了能够在不从 Live USB 启动的情况下使用 btrfs-check,需要将其添加到初始内存盘,编辑/etc/mkinitcpio.conf:

BINARIES=("/usr/bin/btrfs")

然后重新生成 initramfs ,之后如果启动时出现问题,则可以使用该实用程序进行修复。

注意: 如果 fsck 进程必须使空间缓存 无效 ,那么随后的引导会挂起一段时间,这是正常的

进程可能会给出关于 btrfs-transaction 挂起的控制台消息,系统应该在一段时间后从中恢复正常 

引导进入快照

要引导进入快照,因为快照可以像子卷那样被挂载,所以请像 挂载 子卷根分区 那样进行同样的流程

如果使用 GRUB,则可以在 grub-btrfs 或 grub-btrfs-gitAUR 的帮助下,在重新生成配置文件时使用 Btrfs 快照自动填充启动菜单