20、OTA升级与VABC:Virtual AB Compress,压缩快照技术

聊到OTA升级,大家最熟悉的可能是传统的A/B分区方案。但今天我要讲的,是Google在Android 11里引入的一个“狠角色”——Virtual AB Compress,简称VABC。说白了,它就是在虚拟A/B的基础上,加了一层压缩快照技术。

我最早接触VABC是在一个存储空间吃紧的项目上。当时设备只有64GB,传统A/B分区要预留两套系统,用户能用的空间少得可怜。VABC的出现,算是把这个问题给解了。

为什么需要VABC?

先回顾一下痛点。传统A/B分区,你得有两套system、vendor、product。一套在用的叫slot A,另一套待命的叫slot B。升级时往B写,写完了切过去。好处是升级失败还能回滚,坏处是——空间浪费太严重。

虚拟A/B(Virtual AB)稍微好点。它不保留完整的slot B,而是用COW(写时复制)快照来记录差异。但问题来了:COW快照在升级过程中会越来越大,如果升级包很大,COW可能膨胀到跟完整分区差不多。我见过一个项目,OTA包2GB,COW最后涨到了3.5GB,差点把用户数据分区撑爆。

VABC就是来解决这个问题的。它的核心思路是:把快照数据压缩存储。你想想看,升级过程中写入的差异数据,很多都是重复的或者可以压缩的。压缩之后,COW占用的空间能减少50%到70%。

核心结论:VABC = 虚拟A/B + 压缩快照。它用CPU时间换存储空间,适合存储紧张的设备。

VABC的工作原理

VABC的工作流程,我习惯把它拆成三个阶段:

  1. 快照创建阶段:系统在升级开始时,为每个目标分区创建一个压缩快照设备(snapshot device)。这个设备背后是一个压缩的COW文件。
  2. 数据写入阶段:OTA服务把新数据写入快照设备。写入时,数据先被压缩,然后存储到COW文件中。注意,这里用的是在线压缩,边写边压。
  3. 快照合并阶段:升级完成后,系统重启。在第一次启动时,内核把压缩快照合并到目标分区。合并过程中,数据被解压并写入最终位置。

嗯,这里要注意:合并阶段是在重启后进行的。如果合并过程中断电或者崩溃,系统会回滚到升级前的状态。这就是VABC的“原子性”保证。

压缩算法与性能权衡

VABC默认使用lz4压缩算法。为什么选lz4?因为它快。OTA升级对延迟很敏感,你不能让用户等太久。lz4的压缩速度是gzip的3-5倍,解压速度更是快到飞起。

我曾经在项目上测试过不同算法的表现:

算法 压缩比 压缩速度 解压速度 适用场景
lz4 2:1 ~ 3:1 极快 极快 默认推荐
gzip 3:1 ~ 5:1 中等 存储极度紧张
zstd 3:1 ~ 6:1 中等 需要高压缩比

我个人习惯用lz4。除非你的设备存储真的捉襟见肘,否则没必要为了多压10%的空间去牺牲升级速度。你想想看,用户正在用手机,突然OTA下载完了要重启,结果合并花了5分钟——这体验能好吗?

VABC的架构图

下面这张图展示了VABC的核心架构。我画的是简化版,但关键组件都在了。

VABC 核心架构图 用户空间 (Userspace) OTA 更新服务 (update_engine) 快照管理服务 (snapshotctl) 压缩库 (liblz4) 内核空间 (Kernel) dm-snapshot (快照设备映射器) dm-user (用户空间设备映射) 压缩COW文件 (/data/gsi/) 存储层:/data 分区 (F2FS / ext4)

从图里可以看到,VABC涉及用户空间的OTA服务和快照管理,以及内核的dm-snapshot和dm-user。压缩COW文件存储在/data分区下。这个设计有个好处:压缩快照不占用额外的物理分区,全部挤在用户数据区里。

关键代码片段

VABC的配置在BoardConfig.mk里。我贴一段实际项目中的配置:

# 启用虚拟A/B
AB_OTA_UPDATER := true
# 启用压缩快照
BOARD_VIRTUAL_AB_COMPRESS := true
# 压缩算法:lz4, gzip, zstd 可选
BOARD_VIRTUAL_AB_COMPRESS_METHOD := lz4
# 压缩级别:1-9,默认3
BOARD_VIRTUAL_AB_COMPRESS_LEVEL := 3
# 快照存储路径
BOARD_VIRTUAL_AB_COMPRESS_PATH := /data/gsi/

嗯,这里有个坑。我曾经把BOARD_VIRTUAL_AB_COMPRESS_LEVEL设成了9,想着压缩率高一点。结果升级时间从2分钟变成了8分钟。后来查资料才发现,lz4的级别9和级别3,压缩比只差了不到5%,但时间多了4倍。得不偿失。

合并阶段的细节

合并阶段是VABC最容易被忽视的地方。系统重启后,init进程会检查是否有待合并的快照。如果有,它会启动一个叫snapshotctl的服务来执行合并。

合并过程是增量式的。每次合并一个块,然后更新快照的元数据。如果合并过程中断电,下次启动时会从上次中断的地方继续。这就是所谓的“断点续传”。

我记得有一次,测试同事反馈说设备升级后重启卡在开机动画。查了半天,发现是合并过程中/data分区空间不足。原来COW文件虽然压缩了,但合并时需要临时解压空间。如果/data剩余空间小于COW文件大小的两倍,合并就会失败。

避坑指南:我曾经因为/data分区剩余空间不足,导致VABC合并失败,设备卡在recovery模式。建议在OTA升级前,检查/data剩余空间是否大于COW文件预估大小的2倍。可以在update_engine的预检查阶段加一个空间校验。

VABC的优缺点总结

说了这么多,我总结一下VABC的优缺点:

  • 优点:
    • 节省存储空间,压缩比可达2:1到4:1
    • 支持回滚,升级失败可恢复
    • 合并过程支持断点续传
    • 兼容现有A/B升级流程
  • 缺点:
    • 增加CPU开销,升级时间变长
    • 合并阶段依赖/data分区空间
    • 调试难度大,压缩快照出问题时不好定位
    • 对低端设备不友好,CPU太弱会导致升级卡顿

个人建议:如果你的设备存储大于128GB,其实没必要上VABC。传统虚拟A/B就够用了。VABC最适合64GB及以下的设备。另外,如果设备CPU是Cortex-A53级别的,建议慎用zstd,lz4是更稳妥的选择。

最后说一句,VABC不是银弹。它用CPU时间换存储空间,这个trade-off你要想清楚。我在项目上见过为了省500MB空间,结果升级时间翻倍的案例。嗯,设计系统就是这样,没有完美的方案,只有合适的取舍。


公众号:蓝海资料掘金营,微信deep3321