第九十章:子卷与快照
斩灵期涉及内核源码:
一
林小源在 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 的节点在树冠中闪烁,像满天星辰。
他琢磨了一会儿——快照这东西,不就是把时间切成薄片存起来吗?随时能回去,随时能重来。
/*
* 子卷 (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");#include <stdio.h>
/*
* 子卷 (Subvolume):
* btrfs 可以有多个子卷
* 每个子卷有独立的 inode 号空间
* 子卷可以独立挂载
* 子卷之间共享底层存储
*
* 快照 (Snapshot):
* 子卷的瞬时副本
* 基于 COW,几乎不占用额外空间
* 创建后独立于原卷
* 可以是只读或读写
*
* 创建快照:
* btrfs subvolume snapshot /src /dst
* btrfs subvolume snapshot -r /src /dst_readonly
*
* 快照的用途:
* - 备份:创建快照后备份
* - 回滚:从快照恢复
* - 测试:创建快照后测试,不满意就回滚
*/
int main() {
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");
return 0;
}道藏笔记
内核启示
子卷和快照是 btrfs 的两大杀手锏,一个管空间,一个管时间。
子卷是逻辑分区——每个子卷有独立的命名空间和 inode 号空间,可以独立挂载,但它们共享同一个底层存储池。跟物理分区不同,子卷不需要提前决定大小,随用随长,灵活得多。
快照更厉害。创建快照时,btrfs 只复制 B-tree 的根节点,共享所有子节点,几乎不占额外空间。快照和原卷共享数据块,只有当原卷修改某个块的时候,COW 才分配新块——原卷用新块,快照继续用旧块。所以快照的空间是随着原卷的修改逐渐增长的。
用途很明显:备份前先创建快照,不影响正在运行的系统;升级搞坏了就从快照回滚;测试新配置前先拍个快照,不满意就回到过去。系统出了问题?恢复快照。几分钟搞定,不需要重装。
快照之试
btrfs 中冻结某一时刻数据、可用于回滚或备份的时间切片叫什么?