BTRFS
这篇文章介绍了 BTRFS 文件系统,它是一个现代的 COW (写时复制) Linux 文件系统,具有高效、高容错和易管理特性。文章解释了写时复制的工作原理,以及它如何实现文件快照,并提到写时复制可能导致的碎片化问题。 此外,文章还介绍了 BTRFS 的子卷功能,可以创建多个 POSIX 文件树,实现秒级系统回滚。文章最后还提供了将 Linux 安装在 BTRFS 上的步骤,包括创建虚拟硬盘、分区、格式化以及创建子卷等。
BTRFS
BTRFS是一个现代的COW(Copy On Write)Linux文件系统,旨在提供现代文件系统提供的高级功能的同时保持高效、高容错和易管理特性。
写时复制(Copy On Write)
在非写时复制的文件系统中,覆盖一个已存在的数据会对原始数据块进行覆写。而在一个写时复制的文件系统中,文件会使用元数据标记实际物理块,写入已存在数据时会寻找新的可用物理块写入并更改文件元数据指向新物理块,若原始物理块未被任何文件引用则被标记为空闲块以被重新利用。复制这个词可能比较令人迷惑因为整个过程并没有进行文件复制,而仅仅是更新了文件的元数据,但从结果上来看文件的原始物理数据块并没有抹除,相当于对原始数据进行了一次复制然后对复制数据进行了修改😅。
若我们先前存储了文件的原始物理块引用信息可以随时对文件进行恢复,BTRFS正是利用这个特性实现文件快照的。当你对一个文件进行了快照时,原始文件和快照文件都共享同一份物理块引用,当你对快照文件或原始文件进行修改时都会触发上边提到的写时复制机制,若此时需要对原始文件或快照文件进行复原时,只须使用对方的元数据覆盖更新即可。
写时复制的特性很棒,它很灵活而且磁盘空间利用率也非常高,但它也不是十全十美的。针对虚拟机虚拟磁盘这类又大而且读写十分频繁的文件,写时复制的特性会导致这个文件变得十分碎片化,进而影响文件系统的读写性能。值得注意的是,即使是普通文件的读写也存在文件碎片化问题,因为一个文件会是存储在多个物理数据块中的,当文件发生更改时可能仅更新的是文件的部分数据块引用。值得庆幸的是,BTRFS提供了对文件关闭写时复制的方法,但关闭写时复制特性同时也意味着放弃文件快照功能。
BTRFS子卷(Subvolume)
BTRFS子卷(Subvolume)是一个可挂载的POSIX文件树或POSIX文件命名空间,它属于文件系统层级不属于块设备层级。传统Linux文件系统仅包含一个POSIX文件树,在Linux启动时被挂载到根目录也就是我们熟悉的"/“目录,而BTRFS可以创建多个subvolume子卷,即多个POSIX文件树。当对一个分区进行BTRFS文件系统格式化后,分区默认有一个根或顶层subvolume,ID编号为5,该subvolume不能修改和删除,subvolume内部支持嵌套创建subvolume。以下展示根subvolume下的所有subvolume:
sudo mount /dev/mapper/main /mnt/btrfs
sudo btrfs subvolume list /mnt/btrfs
ID 268 gen 18202 top level 5 path @home
ID 269 gen 18202 top level 5 path @
top level N表示该subvolume的上级subvolume的编号为N,编号5代表最顶层subvolumepath XXX表示该subvolume在上级subvolume中的路径,一个subvolume即一个POSIX文件树,subvolume间的文件树是独立的。
按照惯例将根目录和用户目录存储于@和@home两个不同的subvolume以实现隔离快照,将系统安装在BTRFS subvolume上可以实现秒级文件系统级别系统回滚。《秒级文件系统级别系统回滚》这几个字的含金量不必我多说并且支持系统从快照中启动,系统安全问题?不存在的。Linux在启动时会使用类似以下方式挂载subvolume到根目录和用户目录:
sudo mount -o rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@ /dev/mapper/main /
sudo mount -o rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@home /dev/mapper/main /home
配合timeshift可以实现系统自动备份,在grub中选择挂载到”/“和”/home"的subvolume(即上述挂载命令中的subvol=部分),可实现快照启动。
sudo mount /dev/mapper/main /mnt/btrfs
sudo btrfs subvolume list /mnt/btrfs
ID 260 gen 13406 top level 5 path timeshift-btrfs/snapshots/2025-03-31_21-15-43/@
ID 264 gen 13406 top level 5 path timeshift-btrfs/snapshots/2025-04-01_12-21-06/@
ID 266 gen 13406 top level 5 path timeshift-btrfs/snapshots/2025-04-01_15-03-22/@
ID 267 gen 1297 top level 5 path timeshift-btrfs/snapshots/2025-04-01_15-03-22/@home
ID 268 gen 18244 top level 5 path @home
ID 269 gen 18244 top level 5 path @
ID 270 gen 13406 top level 5 path timeshift-btrfs/snapshots/2025-04-08_19-35-50/@
ID 271 gen 13408 top level 5 path timeshift-btrfs/snapshots/2025-04-08_19-35-50/@home
ID 272 gen 13472 top level 5 path timeshift-btrfs/snapshots/2025-04-08_20-08-10/@
ID 273 gen 13474 top level 5 path timeshift-btrfs/snapshots/2025-04-08_20-08-10/@home
BTRFS命令行操作
将Linux安装在BTRFS上
请在Linux系统上操作,为了保险起见我们使用虚拟硬盘来进行操作。
fallocate生成一个空文件,磁盘配额并不会减少。
fallocate -l 3g ~/vhd.img
gdisk对虚拟磁盘进行分区。
gdisk ~/vhd.img
GPT fdisk (gdisk) version 1.0.10
Partition table scan:
MBR: not present
BSD: not present
APM: not present
GPT: not present
Creating new GPT entries in memory.
Command (? for help): ?
b back up GPT data to a file
c change a partition's name
d delete a partition
i show detailed information on a partition
l list known partition types
n add a new partition
o create a new empty GUID partition table (GPT)
p print the partition table
q quit without saving changes
r recovery and transformation options (experts only)
s sort partitions
t change a partition's type code
v verify disk
w write table to disk and exit
x extra functionality (experts only)
? print this menu
Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y
Command (? for help): n
Partition number (1-128, default 1):
First sector (34-6291422, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-6291422, default = 6289407) or {+-}size{KMGTP}: +1G
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): ef00
Changed type of partition to 'EFI system partition'
Command (? for help): n
Partition number (2-128, default 2):
First sector (34-6291422, default = 2099200) or {+-}size{KMGTP}:
Last sector (2099200-6291422, default = 6289407) or {+-}size{KMGTP}:
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to 'Linux filesystem'
w命令则磁盘是安全的,不会进行任何更改)。
Command (? for help): w
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!
Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to ./vhd.img.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot or after you
run partprobe(8) or kpartx(8)
The operation has completed successfully.
losetup绑定虚拟硬盘到环回设备-fP参数自动选择一个未被使用的环回设备。不出意外的话,通过gdisk或lsblk可看到虚拟硬盘内部的分区。
losetup -fP ~/vhd.img
losetup -l
NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC
/dev/loop0 0 0 0 0 /home/rubitcat/vhd.img 0 512
lsblk /dev/loop0
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 3G 0 loop
├─loop0p1 259:3 0 1G 0 part
└─loop0p2 259:4 0 2G 0 part
sudo mkfs.btrfs /dev/loop0p2
btrfs-progs v6.13
See https://btrfs.readthedocs.io for more information.
Performing full device TRIM /dev/loop0p2 (2.00GiB) ...
NOTE: several default settings have changed in version 5.15, please make sure
this does not affect your deployments:
- DUP for metadata (-m dup)
- enabled no-holes (-O no-holes)
- enabled free-space-tree (-R free-space-tree)
Label: (null)
UUID: 4717c6cc-474a-444f-bfd5-5ec6d0c88440
Node size: 16384
Sector size: 4096 (CPU page size: 4096)
Filesystem size: 2.00GiB
Block group profiles:
Data: single 8.00MiB
Metadata: DUP 102.25MiB
System: DUP 8.00MiB
SSD detected: yes
Zoned device: no
Features: extref, skinny-metadata, no-holes, free-space-tree
Checksum: crc32c
Number of devices: 1
Devices:
ID SIZE PATH
1 2.00GiB /dev/loop0p2
mkdir ~/mnt
sudo mount /dev/loop0p2 ~/mnt
df -hT ~/mnt
Filesystem Type Size Used Avail Use% Mounted on
/dev/loop0p2 btrfs 2.0G 5.8M 1.8G 1% /home/rubitcat/mnt
~/mnt完成对应Linux发行版的初始化。针对ArchLinux用户,可以使用arch-install-scripts初始化根目录,并使用arch-chroot ~/mnt进入新系统的终端。
cd ~/mnt
sudo btrfs subvolume create @
Create subvolume './@'
sudo btrfs subvolume create @home
Create subvolume './@home'
sudo umount ~/mnt
sudo mount -o rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@ /dev/loop0p2 ~/mnt/
sudo mkdir ~/mnt/home
sudo mount -o rw,noatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvol=/@home /dev/loop0p2 ~/mnt/home