Skip to content

第九十章:子卷与快照

斩灵期

涉及内核源码:

林小源在 btrfs 树的树冠深处发现了一个奇特的空间。

空间被一道光幕分成四个区域,每个区域都是一棵独立的小树——但它们的根部相连,共享同一个营养池。四个区域上分别刻着"@"、"@home"、"@snapshots"、"@data"。

"这是子卷,"btrfs 树说,"我的分身术。"

林小源走进第一个区域——"@"。这个区域的结构和其他区域完全不同,有自己的目录树、自己的 inode 号空间。

"每个子卷都是一个独立的命名空间,"btrfs 树说,"可以独立挂载,可以独立管理。但它们共享同一个底层存储池——不需要提前分区。"

"和分区有什么区别?"林小源问。

"分区是物理划分,"btrfs 树说,"你得提前决定每个分区多大。子卷是逻辑划分——大小随用随长,不需要预分配。子卷之间可以互相访问,也可以独立备份。"

林小源走出"@"区域,来到"@snapshots"区域。这里存放着一排排半透明的镜像——每个镜像都是一棵小树,和"@一模一样,但颜色更淡。

"这些是快照,"btrfs 树说,声音变得得意起来,"我的绝活。"

林小源走到一个快照跟前。

快照是一棵半透明的小树,树干上的铭文和"@"区域的树完全相同——同样的 inode、同样的数据块位置。但快照的树干是冷的,像一块冰。

"创建快照的时候,"btrfs 树说,"我不复制任何数据。我只复制 B-tree 的根节点——然后让快照和原卷共享所有子节点。"

"几乎不占空间?"

"几乎为零,"btrfs 树说,"快照和原卷共享数据块。只有当原卷修改某个块的时候,COW 才会分配新块——原卷用新块,快照继续用旧块。快照的空间随着原卷的修改逐渐增长。"

林小源伸手触摸快照的树干。树干表面浮现出一个时间戳——"2024-01-01 00:00:00"。

"这是快照创建的时间,"btrfs 树说,"从这一刻起,快照就冻结了。原卷继续变化,快照保持原样。你可以随时从快照恢复——把原卷回滚到那个时间点。"

"时间旅行?"

"差不多,"btrfs 树笑了,"系统出了问题?恢复快照。升级搞坏了?恢复快照。想测试新配置?先创建快照,不满意就回滚。"

林小源看着那排快照。每个快照都是一段时间的切片——冻结了某个瞬间的全部数据。

"只读快照和读写快照呢?"他问。

"只读快照不能修改——安全,适合备份。读写快照可以修改——灵活,适合测试。你用 -r 参数创建只读快照,默认是读写。"

林小源在"@snapshots"区域的尽头发现了一个正在消散的快照。

快照的树干已经变得透明,内部的 B-tree 节点一个接一个地熄灭。快照旁边站着一个穿着黑袍的执行者,手里举着一把虚拟的镰刀。

"btrfs subvolume delete,"执行者说,声音冰冷,"快照不需要了,就删除。释放共享的数据块,回收空间。"

林小源看着快照消散。最后一个 B-tree 节点熄灭后,快照的位置空了出来——但底层的数据块并没有被立即回收,而是被标记为可重用。

"空间回收是延迟的,"btrfs 树说,"删除快照后,后台线程会逐步清理。大快照的删除可能需要几分钟。"

林小源转身,看着整个"@snapshots"区域。快照们排列整齐,每个都是一段时间的记忆。

"子卷和快照,"他低声说,"子卷是逻辑分区,快照是时间切片。一个管空间,一个管时间。"

"你总结得好,"btrfs 树说,"子卷让你不需要提前划分磁盘——随用随建。快照让你不需要害怕数据丢失——随时可以回到过去。两个加起来,就是 btrfs 的核心价值。"

林小源走出光幕,回到 btrfs 树的主干。他抬头看着半透明的树冠——B-tree 的节点在树冠中闪烁,像满天星辰。

他琢磨了一会儿——快照这东西,不就是把时间切成薄片存起来吗?随时能回去,随时能重来。


c
/*
 * 子卷 (Subvolume):
 *   btrfs 可以有多个子卷
 *   每个子卷有独立的 inode 号空间
 *   子卷可以独立挂载
 *   子卷之间共享底层存储
 *
 * 快照 (Snapshot):
 *   子卷的瞬时副本
 *   基于 COW,几乎不占用额外空间
 *   创建后独立于原卷
 *   可以是只读或读写
 *
 * 创建快照:
 *   btrfs subvolume snapshot /src /dst
 *   btrfs subvolume snapshot -r /src /dst_readonly
 *
 * 快照的用途:
 *   - 备份:创建快照后备份
 *   - 回滚:从快照恢复
 *   - 测试:创建快照后测试,不满意就回滚
 */

printf("=== 子卷与快照 — btrfs 的高级特性 ===\n\n");

printf("btrfs 子卷:\n");
printf("  /\n");
printf("  ├── @ (根子卷)\n");
printf("  ├── @home (home 子卷)\n");
printf("  ├── @snapshots (快照子卷)\n");
printf("  └── @data (数据子卷)\n\n");

printf("--- 子卷操作 ---\n");
printf("创建子卷:\n");
printf("  btrfs subvolume create /mnt/@new\n\n");
printf("列出子卷:\n");
printf("  btrfs subvolume list /mnt\n\n");
printf("删除子卷:\n");
printf("  btrfs subvolume delete /mnt/@old\n\n");

printf("--- 快照操作 ---\n");
printf("创建快照:\n");
printf("  btrfs subvolume snapshot /mnt/@ /mnt/@snap/2024-01-01\n\n");
printf("创建只读快照:\n");
printf("  btrfs subvolume snapshot -r /mnt/@ /mnt/@snap/readonly\n\n");
printf("恢复快照:\n");
printf("  btrfs subvolume delete /mnt/@\n");
printf("  btrfs subvolume snapshot /mnt/@snap/2024-01-01 /mnt/@\n\n");

printf("--- 快照的原理 ---\n");
printf("创建快照时:\n");
printf("  不复制数据\n");
printf("  只复制 B-tree 结构\n");
printf("  共享相同的数据块\n\n");
printf("修改原卷时:\n");
printf("  COW 分配新块\n");
printf("  原始块保留给快照\n");
printf("  快照不受影响\n\n");
printf("快照的空间使用:\n");
printf("  初始: 几乎为零\n");
printf("  随着原卷修改: 逐渐增加\n");
printf("  快照越大,占用越多\n\n");

printf("--- 快照的用途 ---\n");
printf("1. 备份\n");
printf("   创建快照后备份\n");
printf("   不影响正在运行的系统\n\n");
printf("2. 回滚\n");
printf("   系统出问题时恢复\n\n");
printf("3. 测试\n");
printf("   创建快照后测试\n");
printf("   不满意就回滚\n");

道藏笔记

内核启示

子卷和快照是 btrfs 的两大杀手锏,一个管空间,一个管时间。

子卷是逻辑分区——每个子卷有独立的命名空间和 inode 号空间,可以独立挂载,但它们共享同一个底层存储池。跟物理分区不同,子卷不需要提前决定大小,随用随长,灵活得多。

快照更厉害。创建快照时,btrfs 只复制 B-tree 的根节点,共享所有子节点,几乎不占额外空间。快照和原卷共享数据块,只有当原卷修改某个块的时候,COW 才分配新块——原卷用新块,快照继续用旧块。所以快照的空间是随着原卷的修改逐渐增长的。

用途很明显:备份前先创建快照,不影响正在运行的系统;升级搞坏了就从快照回滚;测试新配置前先拍个快照,不满意就回到过去。系统出了问题?恢复快照。几分钟搞定,不需要重装。


破关试炼

快照之试

btrfs 中冻结某一时刻数据、可用于回滚或备份的时间切片叫什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道