4、Android分区与镜像:各分区的作用、镜像格式、分区表与动态分区
Android系统的OTA升级本质上是针对设备存储中特定分区的数据更新操作。要深入理解OTA升级的机制、脚本编写和问题排查,首先必须对Android设备的存储布局、各分区的功能定位以及镜像文件的组织格式有清晰的认知。本章将系统梳理Android系统中关键分区的作用、raw与sparse镜像格式的区别、分区表的发展演变,以及动态分区(Dynamic Partition)技术对OTA升级流程带来的深刻影响。
4.1 核心分区的作用与职责
Android设备在出厂时,其eMMC或UFS存储芯片会被划分为多个独立的分区。每个分区承载特定的功能,OTA升级时,系统根据升级包中的脚本指令,精准地擦写目标分区。理解每个分区的职责,是判断升级失败后哪个环节出错的基础。
4.1.1 关键分区功能详解
以下是Android设备中与系统启动和OTA升级直接相关的核心分区及其作用:
| 分区名称 | 挂载点/位置 | 核心作用 | OTA升级关注点 |
|---|---|---|---|
| boot | 无(由Bootloader加载) | 存放Linux内核(zImage/Image)和ramdisk(根文件系统)。负责初始化硬件、挂载system分区。 | 升级内核时需整体替换;若boot损坏,设备将无法启动到Android系统。 |
| system | /system | 存放Android操作系统核心框架、系统应用、库文件(.so)等只读系统文件。 | OTA升级的主要目标分区,通常以只读方式挂载,升级时需先卸载或remount。 |
| vendor | /vendor | 存放硬件相关库、HAL(硬件抽象层)、固件、厂商专属配置。用于将硬件驱动与AOSP框架解耦。 | 升级硬件驱动或HAL时更新;vendor与system版本需匹配,否则可能导致系统服务崩溃。 |
| recovery | 无(独立启动) | 存放恢复模式的内核和ramdisk。用于执行OTA升级、恢复出厂设置、日志抓取等。 | 升级recovery分区本身需谨慎;A/B设备通常将recovery作为boot的一部分。 |
| misc | /misc | 存储启动控制信息,如bootloader message、OTA升级状态标志、恢复模式指令等。 | OTA升级前会写入升级指令;若misc分区损坏,可能导致设备无法进入recovery或误启动。 |
| cache | /cache | 存放临时文件、OTA升级包(非A/B设备)、系统日志。用于恢复模式下的数据缓存。 | 非A/B设备升级时,OTA包先下载至cache;空间不足会导致升级失败。 |
| userdata | /data | 存放用户安装的应用、用户数据、设置等。加密存储。 | OTA升级通常不擦除userdata,但升级失败回滚时可能触发数据清除。 |
| dtbo | 无 | 存放设备树覆盖(Device Tree Blob Overlay),用于硬件配置的动态调整。 | 升级dtbo需与boot中的内核匹配,否则可能导致设备无法识别外设。 |
| vbmeta | 无 | 存放Verified Boot元数据,用于校验boot、system、vendor等分区的完整性。 | OTA升级后需更新vbmeta哈希;若校验失败,设备将进入红色警告状态。 |
核心原理:OTA升级的本质是“分区级”的增量或全量更新。系统升级服务(update_engine或recovery)根据升级脚本(updater-script)中的指令,对上述分区进行精确的读写操作。例如,增量升级时,系统会读取旧分区中的特定数据块,与升级包中的patch数据进行合并,生成新数据后写回分区。
4.1.2 分区与启动流程的关联
分区的设计直接决定了设备的启动链路。典型的启动流程为:Bootloader → boot分区(内核+ramdisk)→ system分区(init进程)→ vendor分区(HAL服务)→ zygote → SystemServer。OTA升级时,如果boot或system分区更新失败,设备将无法完成启动,此时recovery分区或misc分区中的标志位将引导设备进入恢复模式进行修复。
4.2 镜像格式:raw与sparse
在生成OTA升级包或刷机包时,分区内容会被打包成特定格式的镜像文件。理解raw和sparse格式的区别,对于控制升级包大小、优化写入速度至关重要。
4.2.1 Raw镜像格式
Raw镜像是对分区数据的逐字节拷贝,不包含任何压缩或空洞优化。例如,一个1GB的system分区,即使实际只使用了600MB,其raw镜像大小也是1GB。这种格式的优点是结构简单,写入时可直接使用dd命令进行块设备拷贝;缺点是文件体积大,不适合网络传输。
# 生成raw镜像示例
dd if=/dev/block/bootdevice/by-name/system of=/tmp/system_raw.img bs=4096
# 查看镜像大小(与分区大小一致)
ls -lh /tmp/system_raw.img
4.2.2 Sparse镜像格式
Sparse镜像(稀疏镜像)是Android系统中广泛使用的格式,其核心思想是跳过分区中的“空洞”(即全零或未使用的数据块),只记录有效数据块的位置和内容。Sparse镜像文件头部包含一个描述符,用于记录原始分区的总大小、块大小以及有效块的映射表。
| 对比维度 | Raw 镜像 | Sparse 镜像 |
|---|---|---|
| 文件大小 | 与分区大小完全一致 | 通常远小于分区大小(取决于数据密度) |
| 写入方式 | 直接逐块写入 | 需解析头部描述符,按映射表写入有效块 |
| 适用场景 | 本地快速刷机(fastboot) | OTA网络传输、recovery模式写入 |
| 生成工具 | dd、cat | img2simg、simg2img |
| 校验方式 | 直接计算整个文件哈希 | 需先转换为raw格式或按块计算哈希 |
实战经验:在制作OTA增量包时,服务器端会将新旧system分区分别转换为sparse格式,然后逐块对比,生成差分补丁(bsdiff/imgdiff)。由于sparse格式跳过了空洞,差分算法只需处理实际有数据的块,显著减少了补丁包的大小。例如,一个1.5GB的system分区,raw格式的增量包可能达到800MB,而sparse格式的增量包可能仅需200MB。
4.2.3 镜像格式转换与校验
在OTA升级脚本中,经常需要将sparse镜像转换为raw格式后再写入分区,或者反之。Android提供了simg2img和img2simg工具。
# 将sparse镜像转换为raw镜像
simg2img system_sparse.img system_raw.img
# 将raw镜像转换为sparse镜像
img2simg system_raw.img system_sparse.img 4096
# 校验镜像完整性(使用sha1sum)
sha1sum system_sparse.img
4.3 分区表与动态分区技术
随着Android系统功能的增加,system、vendor等分区的大小需求不断变化。传统的固定分区表()在出厂时即确定各分区大小,无法灵活调整。动态分区(Dynamic Partition)技术应运而生,它允许在设备运行时动态调整逻辑分区的大小。
4.3.1 传统分区表的局限性
在Android 10之前的设备上,分区表在工厂烧录时即被固化。例如,某设备出厂时system分区为2GB,vendor分区为1GB。当后续OTA升级需要更大的system分区时,由于相邻分区(如vendor或cache)的大小无法自动缩减,升级将面临空间不足的风险。此外,不同厂商的硬件配置差异导致分区表碎片化严重,增加了OTA兼容性维护成本。
4.3.2 动态分区的架构与优势
动态分区将多个物理分区(如system、vendor、product、odm)合并到一个名为super的物理分区中。super分区内部通过逻辑卷管理器(类似LVM)创建多个逻辑分区。这些逻辑分区的大小可以在OTA升级时通过update_engine或recovery中的resize2fs命令动态调整。
# 查看动态分区信息(在设备上执行)
lsblk /dev/block/by-name/super
# 输出示例:
# super 259:0 0 4G 0 disk
# ├─system 259:1 0 2.5G 0 part /system
# ├─vendor 259:2 0 1G 0 part /vendor
# └─product 259:3 0 0.5G 0 part /product
动态分区的核心优势在于:
- 空间利用率高:逻辑分区大小可随OTA包需求动态伸缩,无需预留大量空闲空间。
- 升级更可靠:A/B无缝升级与动态分区结合,可在后台完成分区调整和新系统写入,降低升级失败风险。
- 简化分区表:物理分区数量减少,分区表更简洁,降低Bootloader复杂度。
4.3.3 动态分区对OTA升级流程的影响
在动态分区设备上,OTA升级脚本不再直接操作/dev/block/bootdevice/by-name/system,而是通过update_engine的DynamicPartitionControl接口来管理逻辑分区。升级流程变为:
- 解析OTA包中的
dynamic_partition_info文件,获取目标逻辑分区的大小和哈希值。 - 通过
dmctl或liblp库调整super分区内的逻辑分区大小。 - 将新镜像写入对应的逻辑分区(通常为sparse格式)。
- 更新super分区头部的元数据,使新分区布局生效。
风险提示:动态分区虽然灵活,但引入了额外的抽象层。若super分区的元数据损坏,或逻辑分区调整过程中断电,可能导致所有逻辑分区无法访问,设备变砖。因此,OTA升级时必须确保电池电量充足,且升级包中的update_engine版本与设备固件兼容。
4.4 案例拆解:一次system分区扩容的OTA升级
以下是一个典型的动态分区设备OTA升级案例,展示了从问题发现到成功升级的完整闭环。
背景与约束:某款Android 11设备,出厂时super分区为6GB,其中system逻辑分区为2.5GB,vendor为1.5GB,product为1GB,剩余1GB为free空间。新版本系统因增加GMS套件,system分区需要额外800MB空间。
初始现象:用户收到OTA推送后,下载完成进入升级阶段,但升级进度条在约60%时停滞,随后设备重启并提示“系统更新失败,无法启动”。
关键日志分析:抓取/cache/recovery/log,发现如下关键错误:
I:UpdateEngine: Attempting to resize partition "system" from 2621440 KB to 3440640 KB
E:UpdateEngine: Not enough free space in super partition. Required: 819200 KB, Available: 1048576 KB
E:UpdateEngine: Dynamic partition resize failed for "system"
分析判断过程:日志显示,虽然super分区有1GB空闲空间,但系统要求system分区增加800MB,而当前空闲空间为1GB,理论上足够。但进一步检查发现,product分区和vendor分区之间存在碎片化,且super分区头部元数据中预留的group(组)大小限制了system所在组的最大容量。该设备的super分区被划分为两个group:default组(包含system和product)和vendor组。default组的最大容量被设定为3.5GB,而system扩容后需要3.44GB,加上product的1GB,已超过组上限。
实际操作步骤:
- 联系厂商获取新的OTA包,该包中修改了
dynamic_partition_info,将default组的最大容量调整为4.5GB。 - 在升级脚本中增加了
resize_group命令,在调整system分区前先扩展default组。 - 重新生成OTA包并推送给用户。
结果对比:升级成功后,执行df -h /system显示system分区大小为3.3GB,可用空间充足。设备启动正常,GMS套件运行流畅。
风险与代价:由于调整了组大小,升级过程中super分区元数据被重写。若在写入元数据时断电,可能导致分区表损坏。因此,该OTA包强制要求电池电量高于50%,且升级过程中禁止强制关机。
复盘经验:动态分区设备的OTA升级,不仅要关注单个分区的大小,还要关注组(group)的容量限制。在制作OTA包时,应使用lpdump工具检查当前设备的super分区布局,确保新分区大小在组限制内。
总结复盘:本章节深入剖析了Android分区的职责、镜像格式的差异以及动态分区的原理。对于OTA开发工程师而言,掌握这些知识意味着:
- 能够快速定位升级失败是分区空间不足、镜像格式错误还是分区表不匹配。
- 在编写升级脚本时,能正确选择raw或sparse格式,并合理使用
resize2fs、simg2img等工具。 - 面对动态分区设备,能理解
super分区的逻辑结构,避免因组容量限制导致升级失败。