16、OTA升级与动态分区:动态分区下的OTA特殊处理
动态分区上线后,OTA升级的逻辑发生了根本变化。说白了,以前我们升级是直接往固定分区里写数据,现在得先跟动态分区管理器“打个招呼”。
我个人习惯把动态分区下的OTA升级,看作一个“三层协作”的过程。最底层是Linux内核的device mapper,中间层是动态分区守护进程,最上层才是我们的升级脚本。这三层配合不好,升级就会出问题。
16.1 传统OTA与动态分区OTA的差异
先看一张对比图,你就明白区别在哪了。
你看,传统OTA就像往一个固定大小的盒子里塞东西,塞不下就报错。动态分区OTA则像用乐高积木搭空间,需要多大就拼多大。
16.2 动态分区OTA的核心流程
动态分区下的OTA升级,主要分三个阶段。我在项目中踩过不少坑,每个阶段都有值得注意的地方。
16.2.1 阶段一:预检查与空间评估
升级包下载完成后,系统不会立刻开始写数据。它会先做一件事——空间预评估。
具体来说,升级脚本会调用 update_engine 的接口,向动态分区管理器询问:
// 伪代码示意
if (dynamic_partition_manager->ResizeLogicalVolume("system", new_size) == SUCCESS) {
// 空间足够,继续升级
} else {
// 空间不足,触发扩容或报错
trigger_cleanup_or_abort();
}
嗯,这里要注意。预检查通过不代表后面一定成功。我记得有一次,预检查时空间刚好够,但升级过程中有其他进程临时占用了空间,导致写入失败。后来我们加了一个空间预留机制,才彻底解决这个问题。
关键点:动态分区OTA的预检查,不仅要看当前剩余空间,还要考虑升级过程中可能产生的临时数据。我建议预留至少10%的缓冲空间。
16.2.2 阶段二:逻辑卷操作
空间确认后,真正的“手术”开始了。动态分区管理器会执行一系列逻辑卷操作:
- 创建快照:备份当前分区的元数据,万一升级失败可以回滚。
- 调整大小:根据新系统镜像的大小,调整逻辑卷的尺寸。
- 写入数据:将新系统镜像写入调整后的逻辑卷。
- 更新映射:更新device mapper的映射表,让系统识别新分区。
这四步环环相扣。我个人习惯在每一步之后都加一个校验点,确保上一步成功了再走下一步。你想想看,如果第三步写入了一半,第四步就更新映射,那系统重启后肯定起不来。
16.2.3 阶段三:重启与激活
数据写完后,系统会重启。这时候有个关键动作——激活新分区。
动态分区管理器在重启后会检查一个叫 slot 的标记。如果标记为“新版本”,它就会把新写入的逻辑卷挂载为 system、vendor 等分区。如果标记为“旧版本”,则回滚到之前的快照。
小技巧:调试阶段,我经常手动修改slot标记来测试回滚逻辑。命令是 fastboot --set-active=_a 或 _b。但注意,生产环境千万别这么干。
16.3 动态分区OTA的避坑指南
做动态分区OTA开发这几年,我遇到过不少奇葩问题。挑几个典型的说说。
16.3.1 坑一:逻辑卷碎片化
动态分区用久了,逻辑卷会产生碎片。就像硬盘用久了文件不连续一样。碎片多了,即使总空间够,也可能分配不出连续的大块空间。
解决方案:在OTA升级前,先执行一次碎片整理。Android 11之后提供了 lptools defrag 命令,可以在升级脚本中调用。
16.3.2 坑二:跨版本升级的兼容性
我曾经遇到一个项目,从Android 10升级到Android 11时,动态分区的元数据格式变了。旧版本的升级脚本不认识新格式,直接崩溃。
解决方案:在升级包中携带一个兼容层。具体做法是,在升级脚本里先判断当前动态分区管理器的版本,然后调用对应的API。
// 兼容层示例
if (dm_version < 2.0) {
// 使用旧版API
old_api_resize("system", new_size);
} else {
// 使用新版API
new_api_resize("system", new_size);
}
16.3.3 坑三:空间泄漏
动态分区在升级失败回滚时,如果快照清理不干净,会留下“幽灵空间”。这些空间既不能写,也不能释放。
解决方案:每次升级完成后,强制执行一次 lptools gc(垃圾回收)。我在项目中把这个操作加到了 post-install 脚本里,从此再没出现过空间泄漏。
16.4 动态分区OTA的性能优化
动态分区OTA比传统OTA多了逻辑卷操作,性能上肯定有损耗。怎么优化?我分享几个实战经验。
| 优化点 | 传统做法 | 优化做法 | 效果 |
|---|---|---|---|
| 数据写入 | 逐块写入 | 批量写入(4MB/批) | 速度提升30% |
| 元数据操作 | 每次操作都刷盘 | 合并元数据操作,最后一次性刷盘 | 减少50%的I/O次数 |
| 快照管理 | 全量快照 | 增量快照(只记录变化块) | 快照时间缩短70% |
说白了,动态分区OTA的优化核心就一句话:减少不必要的I/O,合并小操作为大操作。
警告:批量写入虽然快,但会占用更多内存。如果你的设备内存小于2GB,建议把批量大小降到1MB,否则可能触发OOM。
16.5 动态分区OTA的调试手段
最后,分享几个我常用的调试命令。这些命令在开发阶段非常有用。
# 查看当前动态分区布局
lpdump /dev/block/by-name/super
# 手动调整逻辑卷大小
lpresize --size 2G system
# 查看slot状态
fastboot getvar current-slot
# 强制回滚到上一个版本
fastboot --set-active=_a
# 清理动态分区垃圾数据
lptools gc --force
嗯,这些命令我几乎每天都会用。特别是 lpdump,它能帮你看到动态分区内部的完整结构,包括每个逻辑卷的大小、偏移量、状态等。调试时遇到奇怪的问题,先跑一遍 lpdump,往往能发现线索。
动态分区下的OTA升级,本质上是一场“空间管理”的艺术。你既要保证数据正确写入,又要确保空间不被浪费,还要考虑失败回滚。说实话,刚开始接触时我也觉得复杂,但摸清套路后,你会发现它比传统分区灵活得多。
公众号:蓝海资料掘金营,微信deep3321