🤖 AI文章摘要 qwen-turbo-latest
加载中...

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 @

按照惯例将根目录和用户目录存储于@和@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
创建虚拟磁盘的GPT表。
Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y
创建Linux的EFI分区和数据分区, EFI分区的Hex code为ef00, Linux数据分区的Hex code为8300。
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参数自动选择一个未被使用的环回设备。不出意外的话,通过gdisklsblk可看到虚拟硬盘内部的分区。
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
很好,我们现在在分区2格式化为BTRFS文件系统。
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