Linux 支持诸多的文件系统,而这些文件系统有着不同的优点和缺点,最经典的 Linux 文件系统一定是 EXT 系列,它们在以前几乎是每个 Linux 发行版的默认文件系统,也是目前为止最稳定的 Linux 文件系统之一。
但是随着硬件的进步以及用户对文件系统更加多元化的需求,越来越多的 Linux 发行版和用户开始使用像 XFS、Btrfs、ZFS 这些新型文件系统作为其默认文件系统,它们比 EXT 系列功能更丰富,性能更好,对新硬件也更加友好。
熟悉小山的都知道我一直在使用 ArchLinux,而我刚开始使用的文件系统是 EXT4,没过多久切换到了 XFS,用了好长一段时间,感觉也不错。但是某天我想要一个有快照功能的文件系统,可是 XFS 并不支持快照,而且据我所知支持快照并且流行的文件系统只有 ZFS 和 Btrfs。熟悉 Linux 的都知道 ZFS 因为开源许可证没有添加到 Linux 主线内核,想要使用还需要安装树外模块,不是很方便。我也不是很需要 ZFS 提供的其他功能以及性能,所以就选择了 Btrfs。
因为 Btrfs 有着诸多新特性,比如:子卷、快照、压缩、写时复制,用起来和其他文件系统还是有很大不同的,虽然你也可以像其他文件系统那样直接格式化使用,但那样显然不是最佳实践。
这篇文章我会根据使用体验,讲解一下 Btrfs 文件系统的几个核心功能,除了 Btrfs RAID,建议使用之前将 Linux 内核版本更新到 4.19 或以上以获得更好的体验。
Btrfs 格式化命令:mkfs.btrfs ${partPath}
或 mkfs.btrfs -n ${nodesize} ${partPath}
(指定节点大小),${partPath}
替换为你要格式化的分区路径,而指定节点大小是可选的,较低的节点大小会增加碎片、降低锁定争用,较高的节点大小会以增加内存操作为代价减少碎片。默认值为 16k (btrfs-progs 3.11 之前为 4k),最大允许值为 64k,值必须是扇区大小倍数和 2 的幂。
如果你当前的文件系统是 EXT3/4,可以使用btrfs-convert
工具将其转换为 Btrfs。详情查看:https://btrfs.wiki.kernel.org/index.php/Conversion_from_Ext3
格式化之后不要着急使用,先了解几个核心功能。
Btrfs 分区的大多数操作都由btrfs
工具完成,具体用法:https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs
子卷类似 LVM 的逻辑卷,但它并不是块设备,可以看作是一个独立的目录(专业点叫做 POSIX 命名空间),在文件结构中也表现为一个目录。子卷之间独立且互不影响,任何子卷可以被挂载到任何位置。
Btrfs 分区默认都有一个被称作顶级子卷的子卷。新子卷可以创建在任何子卷的下面。
创建子卷命令:sudo btrfs subvolume create ${subvolumePath}
,${subvolumePath}
是新子卷路径。
可以使用subvolid
或subvol
挂载选项挂载对应子卷,前者使用子卷 ID,后者使用子卷路径(相对于顶级子卷)。如果不指定任何子卷挂载选项,则使用默认子卷,默认是顶级子卷,可以被修改,如果你修改了默认子卷,需要通过顶级子卷的 ID 挂载顶级子卷,顶级子卷的 ID 是 5。推荐使用子卷 ID 挂载子卷,因为这样即使子卷被移动,也可以被正确的挂载,可以使用sudo btrfs subvolume list /
命令查看子卷列表。
子卷的使用方式一般分为嵌套子卷和平行子卷,或者两者混用,我更偏向两者混用。
假设有一块硬盘需要分区并安装 Linux,很多人喜欢给系统文件和家目录放在不同的分区里,一般的做法是创建两个分区或者使用 LVM 逻辑卷,但现在我们可以使用一个 Btrfs 分区,然后使用子卷做到这一点。并且子卷相对于普通分区,并不需要担心某个分区空间不足的情况。
嵌套子卷:嵌套子卷就是子卷间是相互嵌套的,因为子卷在文件结构中是一个目录,所以嵌套子卷的管理就像管理目录一样简单,并且支持自动挂载,但相对缺少灵活性。挂载顶级子卷然后安装系统,此时顶级子卷被挂载在/
,包含所有的系统文件,当然也包含家目录。不过我们只需要删除或者移动现有的/home
目录,然后在/home
创建一个子卷:sudo btrfs subvolume create /home
,由于是嵌套子卷,不需要手动挂载。此时顶级子卷下的home
目录是一个单独的子卷,如果你为顶级子卷创建/还原快照,并不会影响home
子卷。
平行子卷:平行子卷就是将所有子卷创建在顶级子卷下面,然后手动把每个子卷挂载到对应位置,这种方式管理起来相对方便,但是操作会复杂一些。首先挂载顶级子卷,然后创建root
和home
两个子卷,然后卸载顶级子卷,接着把root
子卷挂载到/
,home
子卷挂载到/home
。虽然挂载后的目录结构看着和嵌套子卷一样,但home
子卷并不是root
子卷下面,而是在顶级子卷下面,只是被挂载到了/home
。
两者混用就是在平行子卷的基础上使用嵌套子卷,比如我可以在home
子卷里创建个vms
子卷,由于是在home
子卷下面,所以挂载home
子卷的时候会自动挂载它。
删除子卷可以使用sudo btrfs subvolume delete ${subvolumePath}
命令,从 Linux 内核 4.18 开始,删除子卷目录也可以删除子卷。
如果你不需要快照功能,其实怎样使用子卷都问题不大。
Btrfs 子卷并不能替代 LVM 逻辑卷,它缺少 LVM 一些功能,以及它不是块设备。
我已经尽可能的将子卷如何使用给大家进行了讲解,如果还有不懂的地方,可以查看官方的子卷指南:https://btrfs.wiki.kernel.org/index.php/SysadminGuide#Subvolumes。
Btrfs 支持拷贝文件时不拷贝数据而只创建引用链接,引用链接和硬链接类似,都是不同文件指向同一份数据,和硬链接不同的是,引用链接不共享源文件的 Inode,所以修改引用链接并不会影响源文件。
只有当其中一个文件被修改时,另一个文件的数据会被复制并写入到新的块,这就是写时复制,只有当相同项目的某一项被修改时才会创建新的副本。
如果一个文件的数据不会被更改,即使创建几万个引用链接,它们实际占用的只是一个源文件的大小(以及引用链接的元数据大小)。
对于某些重复文件很多的场景,比如 Wine,引用链接可以显著减少空间的实际占用。
某些文件管理器支持复制文件时引用链接,如果你需要手动复制文件为引用链接,只需在cp
命令加上--reflink=always
参数即可。
写时复制虽然可以减少空间占用,但也会影响性能,对于某些写入较为频繁的目录或文件,比如虚拟机的虚拟硬盘,可以使用chattr
命令给文件或目录添加C
属性,添加此属性后默认会禁用写时复制,只有当存在引用链接时才会使用写时复制。目录添加此属性后默认会影响新创建的文件,建议为需要此属性的目录单独创建一个子卷。你还可以通过使用nodatacow
挂载选项禁用写时复制。
Btrfs 支持透明和自动数据压缩,文件写入时会自动压缩,文件读取时会自动解压,此特性以较少的 CPU 使用率换取较高的空间利用率。
默认不会启用数据压缩,可以使用compress
挂载选项启用数据压缩或更改压缩方式和压缩等级,默认压缩方式是 zlib,压缩等级是 3。可用压缩方式和等级:https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs(5)#MOUNT_OPTIONS
启用数据压缩后,当一个新文件被写入时,Btrfs 会使用一些方法检查文件是否可以被压缩,如果不可以,Btrfs 将会永久标注此文件为不可压缩。可以使用compress-force
挂载选项让 Btrfs 尝试压缩每个新写入的文件,但是已经创建的文件默认不会被压缩。
你还可以在不使用compress
挂载选项的情况下,使用chattr
命令给文件或目录加上c
属性为其启用压缩。
Btrfs 支持为子卷创建快照。快照本身实际上也是一个子卷,只不过利用了写时复制和引用链接,将源子卷的内容全部复制为引用链接到一个新的子卷。
创建快照命令:sudo btrfs subvolume snapshot root root_backup
,此命令为root
子卷创建了一个名为root_backup
的快照,但是root_backup
本身又是一个可用的子卷,所以如果需要还原快照,只需要挂载对应的快照子卷,或者将原子卷删掉,重命名快照子卷。如果你使用subvolid
挂载子卷,需要注意快照子卷 ID 和源子卷 ID 不一样。
如果为禁用写时复制的子卷创建快照,那么创建快照后可能会重新启用源子卷的写时复制。
更多 Btrfs 常见问题:https://btrfs.wiki.kernel.org/index.php/FAQ
更多 Btrfs 挂载选项:https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs(5)#MOUNT_OPTIONS
注意事项:
虽然 Btrfs 的子卷在挂载时可以看作是不同的分区或挂载项,但是在 Linux 内核看来它们依然是指向同一分区的项目。所以大多数挂载选项将用于整个分区,只有第一个挂载的子卷选项才会生效。
Btrfs 由于存在种种新特性,某些分区统计工具可能无法精准统计。要获取分区的精准使用情况,可以使用sudo btrfs filesystem usage ${mountPath}
查看具体信息,${mountPath}
是任意子卷的挂载路径。
如果修改了默认子卷,需要在顶级子卷下创建新的子卷时,需要先挂载顶级子卷,然后在顶级子卷挂载点下创建子卷,而不是在/
挂载点下创建子卷。
如果你对系统分区使用平行子卷,内核可能需要设置挂载子卷选项才能正常启动,可以通过rootflags
内核选项设置。
如果你使用 systemd 的 GPT 分区自动发现并且设置了内核选项root=gpt-auto
,这种情况下即使设置了rootflags
,systemd 也无法挂载对应子卷,除非更改默认子卷或gpt-auto
,建议使用后者。
这篇文章某些地方可能表达的不够通俗易懂,有任何问题欢迎加入 QQ 群与我讨论。
微信扫描二维码关注我们
如果觉得文章有帮助到你,可以点击下方的打赏按钮赞助下服务器费用。
文章评论