25、OTA升级与super_empty:空super分区、首次刷写
聊到OTA升级,很多人第一反应就是“差分”、“增量”、“AB分区”。这些当然重要。但有个东西,平时不怎么露面,一旦你第一次刷机、或者设备出厂时,它就会跳出来刷存在感——super_empty。
说白了,super_empty就是一个“空壳子”。它里面没有任何用户数据,也没有系统镜像。它的作用,就是告诉系统:super分区应该长什么样。
我记得刚接触动态分区那会儿,第一次看到super_empty这个镜像文件,我还以为是个bug。心想:这玩意儿才几百KB,能干啥?后来踩了坑才明白——它才是整个动态分区布局的“蓝图”。
为什么需要空super分区?
你想想看,动态分区和传统分区最大的区别是什么?
传统分区:每个分区(system、vendor、product)都是固定大小,写在GPT表里。你没法改,除非重新分区。
动态分区:这些分区都在super这个大容器里。super本身是一个物理分区,但里面的逻辑分区可以动态调整大小。
问题来了——第一次烧录时,super里面是空的。没有任何逻辑分区信息。系统怎么知道该创建哪些分区?每个分区该多大?
嗯,这就是super_empty登场的时候。
核心概念:super_empty 是一个“空”的super分区镜像,它只包含分区表描述(metadata),不包含任何文件系统数据。它的作用是在首次刷写时,告诉系统如何初始化super分区内的逻辑分区布局。
super_empty 的结构
super_empty 本质上就是一个metadata blob。它里面记录了:
- super分区的总大小
- 每个逻辑分区的名称、大小、属性
- 分组信息(group)
- 分区槽位信息(slot)
我习惯把它理解成“分区布局的配置文件”。只不过这个配置文件是二进制的,直接写在super分区的头部。
来看一个实际的例子。假设你的设备有a/b槽位,super分区大小为4GB,里面包含system、vendor、product三个逻辑分区。那么super_empty里记录的metadata大概长这样:
Super分区布局(示例):
- 总大小:4294967296 字节(4GB)
- 槽位数:2(a/b)
- 分组:default(所有分区都在这个组)
逻辑分区列表(槽位a):
- system_a: 1536MB
- vendor_a: 512MB
- product_a: 512MB
逻辑分区列表(槽位b):
- system_b: 1536MB
- vendor_b: 512MB
- product_b: 512MB
剩余空间:约 1GB(用于动态调整或保留)
注意,这里只是metadata。实际的数据(system.img、vendor.img等)是在后续的刷写步骤中才写入的。
首次刷写的完整流程
我第一次做动态分区设备的首次烧录时,犯过一个低级错误:直接往super分区里写system.img,没先写super_empty。结果设备启动后,系统根本认不出super分区里的内容——因为metadata是空的。
正确的流程应该是这样的:
- 烧录super_empty:先把空壳子写进super分区。这一步会初始化metadata,告诉系统“super分区里有哪些逻辑分区、每个多大”。
- 烧录逻辑分区镜像:把system.img、vendor.img等分别写入对应的逻辑分区。注意,这时候逻辑分区已经存在了(因为metadata里定义了),只是里面还没数据。
- 烧录bootloader和其他分区:boot、dtbo、vbmeta等非动态分区照常烧录。
- 重启设备:bootloader读取super分区里的metadata,挂载逻辑分区,启动系统。
小技巧:如果你用的是fastboot刷机,命令顺序一般是:
fastboot flash super super_empty.img
fastboot flash system system.img
fastboot flash vendor vendor.img
fastboot flash product product.img
fastboot reboot
有些工具(比如fastbootd)会自动处理metadata的更新,但手动刷写时一定要先写super_empty。
super_empty 与 OTA 的关系
你可能会问:OTA升级时,super_empty还用得上吗?
答案是:大部分情况下用不上。因为OTA升级时,super分区里已经有完整的metadata了。系统直接读取现有的metadata,然后更新逻辑分区里的内容就行。
但有一种情况例外——跨版本大升级,分区布局变了。
举个例子:Android 11升级到Android 12,Google可能要求新增一个“system_ext”分区。这时候,原有的super分区布局里没有这个分区。怎么办?
OTA包里的super_empty就派上用场了。升级脚本会先读取新的super_empty,更新super分区里的metadata,然后创建新的逻辑分区,最后把数据写进去。
我曾经踩过的坑:有一次做跨版本OTA,我忘了更新super_empty,结果升级后系统找不到新增的system_ext分区,直接卡在bootloop。后来排查了半天,才发现是metadata没更新。从那以后,我每次做OTA都会检查:新版本的super_empty和旧版本的分区布局是否一致。
super_empty 的生成方式
在Android源码中,super_empty是由lpmake工具生成的。这个工具会根据设备的分区配置文件(比如dynamic_partitions_opts)生成一个空的super镜像。
命令大概长这样:
lpmake --metadata-size 65536 \
--super-name super \
--metadata-slots 2 \
--device super:4294967296 \
--group default:4294967296 \
--partition system_a:readonly:1610612736:default \
--partition vendor_a:readonly:536870912:default \
--partition product_a:readonly:536870912:default \
--partition system_b:readonly:1610612736:default \
--partition vendor_b:readonly:536870912:default \
--partition product_b:readonly:536870912:default \
--sparse \
--output super_empty.img
注意这里的--metadata-slots 2,表示保留两份metadata备份。万一第一份损坏了,还有第二份可以救急。这个设计我觉得挺靠谱的——毕竟分区表坏了,整个设备就变砖了。
SVG:首次刷写super_empty的流程
下面这张图,我画的是首次刷写时,super_empty如何引导整个流程。你可以看到,它就像一张“施工图纸”,决定了后续所有逻辑分区的布局。
实际项目中的注意事项
我在几个项目里都遇到过super_empty相关的问题,总结下来有这么几点:
- 大小必须匹配:super_empty里定义的super分区总大小,必须和实际物理分区大小一致。否则系统会报错,拒绝挂载。
- metadata槽位:建议保留至少2个metadata槽位。我曾经见过一个设备只留了1个槽位,结果metadata损坏后直接变砖,连救砖的机会都没有。
- OTA升级时不要乱改:如果OTA包里的super_empty和当前设备的布局不一致,升级脚本会尝试更新metadata。但这个过程有风险——如果更新到一半断电,metadata可能损坏。所以,非必要不更新super_empty。
- 调试时可以用fastbootd:如果你在开发阶段需要手动操作逻辑分区,可以用
fastboot reboot fastboot进入fastbootd模式。在这个模式下,你可以直接操作逻辑分区,而不需要关心super_empty。
我个人习惯:每次发布新版本前,我都会用lpdump工具检查一下super分区里的metadata,确保和预期一致。命令很简单:
lpdump --slot 0 /dev/block/by-name/super
这样能提前发现分区布局有没有被意外修改。
总结
super_empty虽然不起眼,但它是动态分区体系的基石。没有它,首次刷写时系统根本不知道super分区里该放什么。理解了它的作用,你就能更好地把握OTA升级的完整流程——从空壳子到完整系统,每一步都有它的影子。
嗯,下次你看到super_empty.img这个文件,别再把它当成没用的垃圾了。它可是整个动态分区布局的“总设计师”。