23、OTA升级与resize:动态分区resize、逻辑分区扩容
动态分区最让我头疼的地方,就是分区大小怎么动态调整。你想想看,手机用着用着,system分区不够了,vendor分区又空着一大块。要是以前,只能重新烧录整个镜像。现在有了resize机制,这事儿就灵活多了。
我最早接触这个功能,是在做某个项目的OTA升级方案时。客户要求系统分区能自动扩容,因为后续版本会加入大量预装应用。嗯,当时我第一反应是:这玩意儿靠谱吗?后来深入研究才发现,Google在设计动态分区时,就已经把resize作为核心能力了。
resize的本质是什么?
说白了,resize就是调整逻辑分区在超级分区(super)中的占用空间。每个逻辑分区在super里都有一块连续的区域,resize就是改变这块区域的大小。
这里有个关键点:resize只能发生在super分区的空闲空间内。你不能把分区扩得比super还大,也不能缩到比分区最小需求还小。
核心概念:逻辑分区的resize操作,本质上是修改super分区中的LPD(Logical Partition Descriptor)元数据。这个元数据记录了每个逻辑分区的起始偏移和大小。
resize的两种场景
我在项目中遇到过两种典型的resize场景:
- OTA升级时自动resize:新版本镜像比旧版本大,系统在更新过程中自动扩容
- 手动resize:通过fastboot或recovery模式手动调整分区大小
OTA场景下,resize是自动完成的。系统会根据新镜像的大小,计算出需要多少额外空间,然后从super的空闲区域中划拨。
resize的约束条件
你以为resize就是改个数字那么简单?太天真了。我踩过的坑可不少:
| 约束条件 | 说明 |
|---|---|
| 空间连续性 | 逻辑分区在super中必须是连续的,不能碎片化 |
| 对齐要求 | 分区大小必须是逻辑块大小(通常4KB)的整数倍 |
| 最小大小 | 不能小于分区内已有数据的大小 |
| 最大大小 | 不能超过super剩余空闲空间 + 当前分区大小 |
我曾经踩过的坑:有一次OTA升级后,system分区死活扩不了容。查了半天才发现,super分区里的空闲空间被碎片化了——几个小分区占着位置,导致system分区无法连续扩展。后来我学乖了,设计分区布局时就预留了足够的连续空间。
resize的完整流程
OTA升级时,resize的流程是这样的:
- 系统解析新版本的镜像文件,获取每个逻辑分区的目标大小
- 检查super分区的空闲空间是否足够
- 如果需要扩容,计算需要从空闲区域划拨多少空间
- 更新LPD元数据,修改分区大小信息
- 将新数据写入扩容后的分区
- 验证分区完整性
这里有个细节:resize操作是事务性的。如果中途断电或出错,系统会回滚到之前的状态。嗯,这个设计很贴心,至少不会把手机变砖。
代码层面的实现
在Android源码中,resize的核心逻辑在lpdump和lpmake工具里。我给大家看一段关键代码:
// 来自 system/core/fs_mgr/liblp/liblp.cpp
bool ResizeLogicalPartition(BlockDevicePartition* partition, uint64_t new_size) {
// 检查对齐
if (new_size % partition->block_size() != 0) {
return false;
}
// 检查最小大小
uint64_t current_size = partition->size();
if (new_size < partition->minimum_size()) {
return false;
}
// 计算需要增加或减少的空间
int64_t delta = static_cast<int64_t>(new_size) - static_cast<int64_t>(current_size);
// 检查空闲空间是否足够
uint64_t free_space = GetFreeSpace(partition->super());
if (delta > 0 && static_cast<uint64_t>(delta) > free_space) {
return false;
}
// 更新元数据
partition->set_size(new_size);
return true;
}
这段代码看起来简单,但实际要考虑的情况多得多。比如分区在super中的位置、相邻分区的边界、碎片整理等等。
逻辑分区扩容的实战经验
我给大家分享一个实际案例。有一次,我们需要把system分区从2GB扩容到3GB。super分区总大小是8GB,其他分区占了4GB,空闲空间2GB。看起来够用对吧?
但问题来了:system分区在super中的位置是0-2GB,空闲空间在6-8GB。中间隔着vendor和product分区。这种情况下,system分区无法直接扩容,因为空间不连续。
解决方案有两种:
- 方案一:重新排列分区顺序,把system和空闲空间放在一起
- 方案二:在super分区设计时,预留system分区旁边的空间
我个人推荐方案二。在设计分区布局时,就把可能扩容的分区放在super的末尾,这样扩容时只需要向后扩展,不会受其他分区影响。
小技巧:如果你不确定哪些分区会扩容,可以把system和vendor这两个最常变动的分区放在super的末尾。这样即使后续版本变大,也能轻松扩容。
SVG:resize操作的核心流程
resize的注意事项
最后,我总结几个实战中容易忽略的点:
- 备份元数据:resize操作前,一定要备份LPD元数据。万一出问题,还能恢复
- 预留缓冲空间:super分区不要塞得太满,留10%-15%的空闲空间作为缓冲
- 测试覆盖:各种边界情况都要测试,比如空间刚好够、空间差一点点、空间碎片化等
- 日志记录:resize操作的详细日志要保留,方便排查问题
一句话总结:resize是动态分区最灵活的特性,但也是最容易出问题的环节。设计时多留余地,实现时做好回滚,测试时覆盖边界——做到这三点,resize基本不会出大问题。