A/B(两套系统槽位。一套是当前在用的,另一套留着上一份或下一份,坏了还能从剩下的那套启动)在桌面系统上常被说成「两个系统镜像分区」。Deepin 20 这一代全盘安装确实是这样摆的。它和后来 deepin 25 的磐石、也和手机上那种无缝升级,步骤不一样。下面按 20.x 全盘安装来写。
1. 磁盘里那两块是谁
全盘安装时,安装器自己切分区,用户不用指定 Rootb。分区工具里能看到一对大小相同的 ext4(第四代扩展文件系统,Linux 上常见的那种磁盘格式)分区,卷标写成 Roota 和 Rootb。正在用的系统挂在其中一块上,另一块平时不出现在文件管理器里。
其余空间大多给 data。用户目录、大量可变数据放在这块上,通过挂载进系统。EFI(可扩展固件接口用的系统分区,里面放引导程序,格式是 FAT(文件分配表,一种简单的磁盘格式,固件规定 EFI 分区用它))单独一块。有的机器还有 swap(把内存换出到磁盘的分区)。
20 这一代早期的全盘安装,两块系统分区经常各只有大约 15GB,磁盘剩下的都进 data。系统分区装软件一多就会满。想保持 A/B 备份,扩容时 Roota 和 Rootb 要一起加大,只扩正在用的那一块,另一块就装不下同样一份系统。
手动分区可以自己划 EFI、根目录和 home。安装器没有一项叫 Rootb。只加一个名叫 recovery 的分区,也接不上控制中心里的备份还原。recovery 和 Rootb 是两件事。
2. 业界其实是两条路
两套槽位都是为了「升级搞坏了,机器还能回到上一份完整系统」。写入的方向相反。
| 做法 | 升级时写哪一块 | 正在运行的那一块 | 谁在用 |
|---|---|---|---|
| 先备份,再原地升级 | 把当前系统复制到空槽 | 复制完成后,软件包装在这块上 | Deepin 20、同时代的统信桌面 |
| 先写新系统,再切换 | 新系统写进空槽,写完才重启进去 | 升级过程中保持原样 | 安卓 A/B、ChromeOS、SteamOS |
第二条路里,空槽在重启前已经是完整的新系统。引导程序把下一次启动指过去。新系统自己报健康之后,这块才算提交。启动失败就仍从旧槽起来。旧槽在整个写入过程中没有被 apt 改过。
Deepin 20 走第一条。空槽里放的是升级开始之前的系统副本。新软件包装在你正在用的那块根分区上。副本的用途是:这次更新若把机器带到起不来,引导菜单还能进升级前的那一份。
两条路都把用户数据放在槽位外面。系统分区可以整块换、整块回,照片和家目录留在 data 或 home 上。
3. Deepin 20 一次更新的顺序
控制中心点系统更新时,备份服务 deepin-ab-recovery 先跑。它在仓库 linuxdeepin/deepin-ab-recovery 里,20 这一代和统信桌面专业版用的是同一套。
- 找到卷标为 Rootb 的分区。配置里用 UUID(分区的唯一编号,换盘符也不会变)记下哪一块是当前系统、哪一块是备份。
- 把备份分区挂到
/deepin-ab-recovery-backup。 - 用 rsync(远程同步工具,这里在本机把一个目录树镜像到另一个分区)把根目录同步过去。
- 把当前正在用的内核和 initrd(内核启动后最先用的内存盘,里面是驱动和挂载根分区所需的程序)拷到
/boot/deepin-ab-recovery。 - 改备份分区里的 fstab(开机时按这个表挂载磁盘),让它若被启动,根目录指向它自己。
- 写 GRUB(Grand Unified Bootloader,Linux 常用的引导菜单)配置,多一条从备份分区启动的菜单,然后更新引导。
- 备份成功,控制中心才继续装更新。备份失败,更新停在「系统备份失败」,正在用的系统还没开始换包。
所以升级的后半段仍是 Debian 系的 apt(Advanced Package Tool,用软件源安装、升级软件包的工具。装上去的是 deb(Debian 软件包格式)包)在当前根分区上改文件。A/B 保证的是有一份升级前的整系统可以启动,保证不了 apt 写到一半时当前这块已经完整。
控制中心点更新
→ 挂载 Rootb
→ rsync 当前根目录到 Rootb(升级前的副本)
→ 内核副本放进 /boot/deepin-ab-recovery
→ GRUB 增加「从备份分区启动」
→ 备份成功
→ apt 在 Roota 上安装更新
→ 重启后仍从 Roota 进入新系统
起不来时,菜单里选备份分区,进的是 Rootb
4. 复制过去的是什么
同步命令带 -a(保留权限、时间和符号链接)、-X(保留扩展属性)、-x(不跨到别的文件系统)、--delete-after(目标上多出来的文件在复制结束后删掉)。效果是把备份分区收成当前根分区的一份镜像,旧备份里多余的文件不会留下来。
排除列表里有这些目录:
/media /tmp /proc /sys /dev /run /mnt
/boot /data /lost+found /recovery /opt
/proc、/sys、/dev、/run 是内核运行时造出来的,不能当成系统文件拷走。/data 是用户数据盘,和这次系统备份无关。/boot 不进这次 rsync,因为内核文件要按下一节单独留。/opt 常被第三方软件用来放大包,20 这一代的备份默认不带它。
-x 使单独挂载的 home 也不会跟着根分区走。家目录在 data 上时,还原系统不会把备份时刻之后新存的文件退回去,也不会用旧家目录覆盖现在的家目录。
备份分区上会放一个空标记文件 .deepin-ab-recovery-backup。服务靠它认出「这块是备份,不要再拿去当备份的备份」。
5. 内核和引导要单独留
apt 升级经常会换内核。新的 vmlinuz 和 initrd 写在当前系统的 /boot 里。若备份系统开机仍去读这份 /boot,它会拿到升级之后的内核,和 Rootb 里升级前的模块对不上。
所以备份时先按当前启动项找到正在用的内核文件,复制到 /boot/deepin-ab-recovery/。GRUB 里那条备份菜单使用这组内核,根分区参数写成 Rootb 的 UUID。
备份分区自己的 /etc/fstab 也会改:里面的根分区从「当前这块」改成「备份这块」。从菜单进入备份系统时,它把自己当成根,而不是再去挂 Roota。
udev(内核通知用户态「设备插上了」的那套规则)里还会藏起备份分区,避免文件管理器把它当成一块多余的盘挂出来,人随手往里写。
6. 还原是把两块身份对调
从备份启动成功之后,若在控制中心做还原,服务做的是换身份:
- 把
/boot/deepin-ab-recovery/里留着的内核移回/boot,让日常启动项重新使用升级前的内核。 - 重写 GRUB。
- 配置文件里的当前分区和备份分区对调。原来的 Rootb 变成以后每天启动的系统,原来的 Roota 变成新的备份分区,并继续从文件管理器里隐藏。
还原不必再 rsync 一次。升级前的文件已经在另一块分区上,差的是下一次从哪一块 UUID 启动。对调之后,被 apt 改过的那一块留在备份位,下次更新会用新的当前系统把它盖掉。
7. 手动分区通常没有这一对
这套备份绑在安装器造好的卷标和配置上。全盘安装才会出现 Roota、Rootb 和 ab-recovery 的配置。手动分区的系统,控制中心的备份还原入口会缺,系统更新仍可能去跑备份,然后报备份失败。
论坛里有人在手动方案里加了 /recovery,安装器并没有把 Rootb 交给这项功能。/recovery 可以拿来自己放镜像,它不会在点「系统更新」时自动收到一份 rsync。
想要同样的回退,就要自己留一份升级前的根分区副本,并在 GRUB 里写一条能从那份副本启动的菜单。Deepin 20 把这件事做进了全盘安装。
8. 空间和备份失败
Rootb 必须装得下 Roota 上要备份的那些文件,所以两块划成一样大。根分区只有十几 GB 时,装完系统和几个大软件,备份阶段就会因为空间失败,更新被拦住。这是 20 这一代全盘安装最常见的卡点。扩容要同时加大两块,data 上让出的空间要分给 Roota 和 Rootb。
控制中心报「系统备份失败」时,更新还没有换包。常见原因有三类,都出在备份这一步:
- Rootb 的文件系统需要清理,rsync 写到一半退出。
- EFI 分区变成只读,
update-grub写不进引导配置。 ab-recovery进程已经占着服务名,第二次启动直接失败。
备份成功之后的风险在 apt。装包过程中断电,当前根分区可能停在一半新、一半旧的状态。这时应走引导菜单里的备份项,从 Rootb 启动升级前的系统,再按上一节把身份对调回来。
9. 23 以后换了存法
deepin 23 的原子更新说明写过:版本管理不再依赖一块和根分区一样大的 Roota/Rootb。备份改成快照,相同文件在磁盘上只存一份,手动分区也能用备份还原。deepin 25 的磐石在这之上把 /usr、/bin、/lib 设成只读,更新前做快照,并保留当前系统和上一份备份。
25 的全盘安装仍会分配它所谓的 A/B 镜像空间,用来在控制中心更新之后回退。那是后一代的布局。20.x 磁盘上那两块一样大的 Roota、Rootb,对应的是本文这条路径:先 rsync 出升级前的系统,再在当前分区上 apt。
| Deepin 20 全盘安装 | 先写新系统再切换 | |
|---|---|---|
| 两块分区里有什么 | 一块是今天的系统,一块是更新前的副本 | 一块是旧系统,一块是已经写好的新系统 |
| 更新时改哪块 | 备份完成后改正在运行的那块 | 只改没在运行的那块 |
| 用户数据 | 在 data,rsync 不跨过去 | 在单独的数据分区 |
| 回退 | 从 GRUB 进备份分区,再把两块身份对调 | 引导切回旧槽,旧槽没被这次安装碰过 |
| 磁盘成本 | 系统分区几乎要两份一样大 | 也要两份系统区;后来的虚拟 A/B 用快照把空槽变小 |