Android 构建系统编译完毕后,在 out/target/product/<product>/ 目录下会生成一系列的 .img 映像文件。这些映像文件包含了 Android 设备运行所需的全部组件,从内核到应用。理解这些映像文件的结构、格式和刷写方式,是深入理解 Android 系统启动、OTA 更新和设备恢复的基础。本文基于 Android 12 (API 31) 的 AOSP 源码深入剖析每个系统映像文件的内部结构。
一、映像文件全景图 1.1 Android 12+ 的镜像体系 ┌─────────────────────────────────────────────────────┐ │ super.img (动态分区) │ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ │ │ system.img │ │ vendor.img │ │ product.img │ │ │ │ (read-only) │ │ (HAL impl.) │ │ (product) │ │ │ │ erofs │ │ erofs │ │ erofs │ │ │ └──────────────┘ └──────────────┘ └─────────────┘ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ system_ext.img│ │ odm.img │ │ │ │ (extended) │ │ (ODM) │ │ │ │ erofs │ │ erofs │ │ │ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ boot.img │ │ ┌─────────────────┐ ┌──────────────────────────┐ │ │ │ kernel │ │ boot ramdisk │ │ │ │ (Image.gz) │ │ ├── init │ │ │ │ │ │ └── first_stage_ramdisk │ │ │ └─────────────────┘ └──────────────────────────┘ │ └─────────────────────────────────────────────────────┘ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ │ vendor_boot.img │ │ vbmeta.img │ │ recovery.img │ │ (vendor ramdisk) │ │ (verified │ │ (recovery │ │ │ │ boot meta) │ │ ramdisk) │ └──────────────────┘ └──────────────┘ └──────────────┘
二、boot.img:启动的核心 2.1 boot.img 的结构 boot.img 是 Android 设备启动的第一个映像文件,由 bootloader (LK/ABL) 加载到内存。它采用 Android Boot Image 格式:
boot.img 布局(Android 12 GKI 2.0): ┌──────────────────────────────┐ │ boot_img_hdr (header) │ <- 文件头,描述镜像结构 │ ├── magic: "ANDROID!" │ │ ├── kernel_size │ │ ├── kernel_addr │ │ ├── ramdisk_size │ │ ├── ramdisk_addr │ │ ├── second_size │ │ ├── second_addr │ │ ├── tags_addr │ │ ├── page_size │ │ ├── header_version │ <- v0~v4 (Android 12 使用 v4) │ ├── os_version │ │ └── name: "boot" │ ├──────────────────────────────┤ │ kernel (Image.gz / Image.lz4)│ <- 压缩的内核映像 ├──────────────────────────────┤ │ ramdisk (gzip/lz4) │ <- GKI 通用 ramdisk │ ├── init │ <- 第一阶段 init 二进制 │ └── first_stage_ramdisk/ │ <- 第一阶段挂载点 ├──────────────────────────────┤ │ (second stage — for v0) │ <- 二级 bootloader(不再使用) ├──────────────────────────────┤ │ device tree blob (DTB/DTBO) │ <- 设备树(v2+) └──────────────────────────────┘
Version
Android 版本
关键变化
v0
Android 6-
原始格式:kernel + ramdisk + second
v1
Android 7+
添加 recovery_dtbo 字段
v2
Android 9+
添加 dtb 字段(设备树 blob)
v3
Android 11+
移除 second stage,ramdisk 变为通用 (GKI 1.0)
v4
Android 12+
vendor_boot 独立分区(GKI 2.0)
2.3 boot.img 的创建 mkbootimg \ --kernel out/target/product/<product>/kernel \ --ramdisk out/target/product/<product>/ramdisk.img \ --header_version 4 \ --os_version 12.0.0 \ --os_patch_level 2022-01-05 \ --pagesize 4096 \ --cmdline "console=ttyMSM0,115200n8 androidboot.hardware=qcom" \ --output out/target/product/<product>/boot.img
C 语言解析 boot.img header:
typedef struct boot_img_hdr_v0 { uint8_t magic[8 ]; uint32_t kernel_size; uint32_t kernel_addr; uint32_t ramdisk_size; uint32_t ramdisk_addr; uint32_t second_size; uint32_t second_addr; uint32_t tags_addr; uint32_t page_size; uint32_t header_version; uint32_t os_version; uint8_t name[16 ]; uint8_t cmdline[512 ]; uint8_t id[32 ]; uint8_t extra_cmdline[1024 ]; } boot_img_hdr_v0;
2.4 vendor_boot.img:Android 12 GKI 2.0 的关键 在 GKI 2.0 架构中,boot.img 中的 ramdisk 变为通用(Generic Ramdisk),由 Google 提供。所有 vendor 特定的 ramdisk 内容(如 init .rc 文件、.so 文件、firmware)移入 vendor_boot.img:
vendor_boot.img 布局(header v4): ┌──────────────────────────┐ │ vendor_boot_img_hdr │ │ ├── magic │ │ ├── header_version (4) │ │ ├── page_size │ │ ├── kernel_addr │ │ ├── ramdisk_size │ │ ├── ramdisk_addr │ │ ├── vendor_ramdisk_size │ <- vendor ramdisk 大小 │ ├── dtb_size │ <- 设备树大小 │ └── vendor_cmdline │ ├──────────────────────────┤ │ vendor ramdisk │ <- vendor .rc 文件、HAL .so │ ├── init.rc │ │ ├── fstab.<hardware> │ <- vendor 特定的 fstab │ └── vendor/ │ ├──────────────────────────┤ │ DTB (Device Tree Blob) │ <- SoC 设备树 └──────────────────────────┘
三、system.img:系统主分区 3.1 system.img 的文件系统格式 Android 系统分区使用只读优化的文件系统:
ext4(传统) :标准的 Linux 文件系统,支持 journal 和权限
erofs(Android 10+ 推荐) :Enhanced Read-Only File System,专为只读分区优化
erofs 相比 ext4 的优势:
镜像体积约小 20%(更高效的压缩和元数据存储)
更好的只读性能(数据结构针对读优化)
支持 lz4 和 lz4hc 压缩
mkfs.erofs \ -zlz4hc \ -T 1230768000 \ --fs-config-file=system_fs_config \ --product-out=out/target/product/xxx \ system.img \ system/
3.2 system.img 的内部结构 system/ ├── bin/ # 系统可执行文件 │ ├── init # init 进程(系统启动第一个用户态进程) │ ├── servicemanager # Binder 服务管理器 │ ├── surfaceflinger # 显示合成引擎 │ ├── app_process64 # Zygote/App 进程入口 │ └── ... ├── lib/ / lib64/ # 系统动态库 │ ├── libandroid_runtime.so # Android Runtime JNI 库 │ ├── libbinder.so # Binder IPC 库 │ ├── libgui.so # GUI 库(Surface/BufferQueue) │ ├── libcutils.so # C 工具库 │ └── ... ├── etc/ │ ├── init/ # init.rc 文件目录 │ │ ├── hw/init.rc # 主 rc 文件 │ │ └── ... # 各种 .rc 文件 │ ├── vintf/ # VINTF 兼容性清单 │ │ └── manifest.xml │ ├── selinux/ # SELinux 策略文件 │ │ └── ... │ └── ... ├── framework/ # Java 框架 │ ├── framework.jar # 框架 API │ ├── services.jar # 系统服务 │ ├── boot-framework.art # ART boot image │ └── ... ├── build.prop # 系统属性文件 ├── apex/ # APEX 模块(Android 10+) │ ├── com.android.art.apex │ ├── com.android.runtime.apex │ └── ... └── fonts/ # 系统字体
四、vendor.img / product.img / system_ext.img 4.1 Android 10+ 的多分区架构 从 Android 10 开始,为提高 Treble 兼容性,系统文件被拆分到多个分区:
Android 9 及以前: /system 包含一切 Android 10+: /system — AOSP 系统核心(Google 提供) /system_ext — 系统扩展(OEM 可使用) /product — 产品层(OEM 定制) /vendor — SoC vendor HAL 实现 /odm — ODM/OEM 设备特定配置
这种拆分的理由:
独立更新 :system 可以独立于 vendor 更新(GSI 镜像)
许可隔离 :不同分区可以使用不同的许可证
VTS 合规 :vendor 实现必须能运行在任意 system 上
构建效率 :修改 system 不需要重新构建 vendor
4.2 vendor.img 内容 vendor/ ├── bin/ │ └── hw/ │ ├── android.hardware.camera.provider@2.4-service │ ├── android.hardware.graphics.composer@2.3-service │ ├── android.hardware.audio@5.0-service │ └── ... (HAL 服务可执行文件) ├── lib/ / lib64/ │ ├── hw/ │ │ ├── camera.<hw>.so # Camera HAL 实现 │ │ ├── gralloc.<hw>.so # Gralloc HAL 实现 │ │ ├── lights.<hw>.so # LED HAL 实现 │ │ └── ... │ └── ... ├── etc/ │ ├── init/ # vendor .rc 文件 │ ├── vintf/manifest.xml # HAL manifest │ └── selinux/ # vendor SELinux 策略 ├── build.prop # vendor 属性 ├── firmware/ # 固件文件 └── overlay/ # RRO 资源覆盖
五、Sparse Image 格式 5.1 稀疏映像 vs 原始映像 Android 的 .img 文件通常采用 sparse image(稀疏映像)格式而非原始 bitwise 映像:
img2simg system.img system_sparse.img simg2img system_sparse.img system.img
5.2 Sparse Image 格式详解 typedef struct sparse_header { uint32_t magic; uint16_t major_version; uint16_t minor_version; uint16_t file_hdr_sz; uint16_t chunk_hdr_sz; uint32_t blk_sz; uint32_t total_blks; uint32_t total_chunks; uint32_t image_checksum; } sparse_header_t ; typedef enum { CHUNK_TYPE_RAW = 0xCAC1 , CHUNK_TYPE_FILL = 0xCAC2 , CHUNK_TYPE_DONT_CARE = 0xCAC3 , CHUNK_TYPE_CRC32 = 0xCAC4 , } chunk_type_t ;
稀疏映像在 fastboot 刷写时的处理流程:
六、super.img:动态分区 6.1 为什么需要动态分区 传统 Android 系统将 system、vendor、product 等分区固定大小划分。这导致:
system 分区过大 → 浪费存储空间
vendor 分区不够 → OTA 更新失败
分区大小在出厂后无法更改
Android 10 引入动态分区(Dynamic Partitions),使用 super.img 统一管理多个只读分区:
super.img (固定大小,如 4GB) ├── metadata (分区元数据) │ ├── system_a — offset 0, size 1.2G │ ├── system_b — offset 1.2G, size 1.2G │ ├── vendor_a — offset 2.4G, size 300M │ ├── vendor_b — offset 2.7G, size 300M │ ├── product_a — offset 3.0G, size 500M │ └── product_b — offset 3.5G, size 500M └── data blocks
6.2 lpmake 创建 super.img lpmake \ --metadata-size 65536 \ --super-name super \ --metadata-slots 2 \ --device super:4294967296 \ --group qti_dynamic_partitions:3221225472 \ --partition system:readonly :1288490188:qti_dynamic_partitions \ --image system=out/target/product/xxx/system.img \ --partition vendor:readonly :314572800:qti_dynamic_partitions \ --image vendor=out/target/product/xxx/vendor.img \ --partition product:readonly :524288000:qti_dynamic_partitions \ --image product=out/target/product/xxx/product.img \ --sparse \ --output out/target/product/xxx/super.img
6.3 动态分区与 Virtual A/B Android 11 引入 Virtual A/B,它结合了动态分区和 device-mapper (dm-linear):
传统 A/B 系统: 两个完整的分区副本(system_a + system_b = 2x 分区大小) Virtual A/B: 只有一个物理 super 分区 ├── system (当前使用的 slot A) ├── system_b (slot B 的目标,仅在 OTA 更新期间存在) └── OTA 更新时使用 snapshot (dm-snapshot) 机制 ├── 写入 COW (Copy-On-Write) 设备 └── 更新完成后合并到主分区
Virtual A/B 的 OTA 更新流程:
1. 正常运行时:只使用 slot A 2. OTA 下载完成 ├── 创建 COW 设备 ├── 映射 slot B (base + snapshot) └── 写入更新数据到 COW 设备 3. 设备重启 ├── bootloader 切换到 slot B ├── dm-snapshot 合并 COW 数据 ├── 如果合并成功 → 新系统运行 └── 如果合并失败 → 回退到 slot A
七、OTA 更新模型 7.1 A/B 无缝更新 (Seamless Updates) A/B 系统维护两个完整的系统分区副本(slot A 和 slot B):
A/B 分区布局: ┌──────────┬──────────┬──────────┬──────────┐ │ slot A │ slot A │ slot B │ slot B │ │ boot │ system │ boot │ system │ └──────────┴──────────┴──────────┴──────────┘ 正常运行时:使用 slot A (active slot) OTA 更新时:写入 slot B (inactive slot) 重启后:切换到 slot B 下次 OTA:写入 slot A
优点:
更新在后台完成,用户无感知
更新失败自动回退到旧 slot
不需要 recovery 分区就可以完成更新
缺点:
7.2 OTA 包结构 update.zip (OTA 包) ├── META-INF/ │ └── com/ │ └── android/ │ ├── metadata # OTA 元数据 │ └── otacert # OTA 签名证书 ├── payload.bin # 差分更新负载 │ └── (由 update_engine 处理) ├── payload_properties.txt # payload 属性 ├── care_map.txt # 需要验证的块列表 └── compatibility.zip # 兼容性检查信息
OTA 引擎 update_engine (system/update_engine/) 使用 bsdiff / puffdiff 算法生成和应用差分包:
bsdiff:通用二进制差分
puffdiff:针对 deflate 压缩文件的优化差分
7.3 OTA 更新流程 enum class UpdateStatus { IDLE, CHECKING_FOR_UPDATE, UPDATE_AVAILABLE, DOWNLOADING, VERIFYING, FINALIZING, UPDATED_NEED_REBOOT, REPORTING_ERROR_EVENT, ATTEMPT_ROLLBACK, };
八、Fastboot 协议 8.1 Fastboot 模式 Fastboot 是 Android 设备的底层刷写协议,通常在 bootloader 阶段运行:
adb reboot bootloader fastboot devices fastboot flash boot boot.img fastboot flash system system.img fastboot flashing unlock fastboot flashing lock fastboot boot recovery.img fastboot erase userdata fastboot reboot fastboot getvar all
8.2 Fastboot 与 Fastbootd Android 10+ 引入了 userspace fastboot(fastbootd),运行在 Android 系统内而非 bootloader 中:
传统 fastboot (bootloader 模式): bootloader 实现 → 只能操作 bootloader 支持的分区 fastbootd (userspace 模式): Android recovery/system 中实现 → 可以操作动态分区 (super.img) 进入方式:adb reboot fastboot
fastbootd 的实现:
class FastbootDevice { };
九、Verified Boot:启动验证链 9.1 Verified Boot 全链路 Android Verified Boot 确保了从硬件到 system 的完整信任链:
Hardware Root of Trust (eFuse/OTP) │ ▼ 验证 Boot ROM (不可更改) │ ▼ 验证 Bootloader (LK/ABL) │ ▼ 验证 (通过 vbmeta.img) boot.img (kernel + ramdisk) │ ▼ dm-verity (通过 vbmeta 中的哈希) system.img / vendor.img
typedef struct AvbVBMetaImageHeader { uint8_t magic[4 ]; uint32_t required_libavb_version_major; uint32_t required_libavb_version_minor; uint64_t authentication_data_block_size; uint64_t auxiliary_data_block_size; uint32_t algorithm_type; uint64_t hash_offset; uint64_t hash_size; uint64_t signature_offset; uint64_t signature_size; } AvbVBMetaImageHeader;
验证链:
Bootloader 读取 vbmeta.img,验证其签名的公钥与硬件中烧录的 key 匹配
vbmeta 中包含 boot.img 的哈希——bootloader 验证 boot.img 未被篡改
boot.img 中的 ramdisk 启动后,dm-verity 验证 system.img 的每个块的哈希
如果任何一步验证失败,根据设备配置:警告、拒绝启动(黄色/红色状态)
十、recovery.img 与救援模式 10.1 recovery.img 的结构 recovery.img 本质上是一个特殊的 boot.img,使用不同的 ramdisk 和内核命令行:
recovery.img = kernel + recovery ramdisk recovery ramdisk: ├── init # init(恢复模式) ├── init.rc # 恢复模式 .rc 文件 ├── sbin/ │ └── recovery # recovery 主二进制 ├── res/ │ └── images/ # recovery UI 资源 └── etc/ └── recovery.fstab # recovery 模式分区表
10.2 Recovery 模式功能 int main (int argc, char ** argv) { }
十一、核心面试题 Q1:sparse image 和 raw image 有什么区别?为什么 Android 使用 sparse 格式进行 fastboot 刷写?
答:Raw image 是一个分区(如 system 的 2GB)的完整字节拷贝,包含所有空块。Sparse image 仅包含非空块及 chunk 索引,跳过空洞区域(CHUNK_TYPE_DONT_CARE)。传输 sparse image 到设备时数据量显著减少(通常减少 50-70%),大幅加快刷写速度。在设备端,fastbootd 解析 sparse header,只恢复存有数据的块,跳过空洞。这在开发过程中(频繁刷机)尤其重要。
Q2:GKI 2.0 (Android 12) 为什么要把 vendor ramdisk 从 boot.img 中拆出来成为 vendor_boot.img?
答:GKI (Generic Kernel Image) 的目标是让 Google 提供一个通用的内核 + ramdisk 基础(boot.img),OEM/SoC 厂商只需提供 vendor 特定的附加组件(vendor_boot.img)。拆分的直接好处:(1) Google 可以独立更新 boot.img(内核安全补丁)而不需要 OEM 变更 vendor 组件;(2) OEM 可以独立更新 HAL 实现(在 vendor_boot 的 ramdisk 中加载 .so 和 .rc 文件)而不影响内核;(3) 简化了构建——Google 只需要构建和维护一个 boot.img(per-arch),而不是数百个 per-device boot.img。
Q3:erofs 相比 ext4 为什么更适合做 system.img?它没有 journal 会不会导致数据完整性风险?
答:erofs 专为只读分区设计,去掉了 ext4 中针对读写场景的开销(journal、block allocation bitmap、支持写入的 inode 结构等),实现了:(1) 约 20% 更小的镜像体积(更好的压缩和更紧凑的元数据);(2) 更快的挂载速度(更少的结构需要初始化);(3) 更好的读性能(数据结构针对顺序读优化)。关于数据完整性:system 分区是只读的(由 dm-verity 保证完整性),不是写入操作后才需要保证一致性的场景,所以 journal 不需要。dm-verity 在每个块被读取时验证其哈希,任何数据损坏都会在发生时被立即检测到。从这个意义上说,erofs + dm-verity 的组合比 ext4 + journal 提供更强(而非更弱)的数据完整性保证。
Q4:A/B 系统更新中如果设备在更新到 slot B 时断电了怎么办?系统还能启动吗?
答:A/B 系统的设计中有一个关键字段 bootable flag 存储在每个 slot 的元数据中。更新流程:(1) 首先标记 slot B 为 unbootable;(2) 然后开始向 slot B 写入数据;(3) 写入完成后,标记 slot B 为 bootable;(4) 尝试启动 slot B;(5) 如果启动成功(到达 userspace),标记 successful;(6) 如果多次启动失败(超过 retry_count),bootloader 自动回退到 slot A。因此如果更新过程中断电,slot B 仍然是 unbootable 状态,bootloader 会直接启动 slot A。如果已标记 bootable 但数据写入不完整(极端情况),启动失败后自动回退。这种机制确保了 “anti-brick” 保护。
Q5:super.img 中的”逻辑分区”与物理分区在 Linux 内核层面是如何区分的?device-mapper 扮演什么角色?
答:物理分区(如 mmcblk0p1)是由 GPT 分区表定义的闪存上的固定区域。逻辑分区(如 system、vendor 在 super 内)并不对应独立的物理分区——它们是由 Android init 通过 device-mapper 的 dm-linear target 创建的虚拟块设备。具体流程:init 在启动时读取 super.img 的 metadata slot(由 liblp 库解析),然后调用 device-mapper 创建 /dev/block/mapper/system_a、vendor_a 等 dm 设备,每个 dm-linear 设备映射到 super 分区内的一个偏移区域。这样内核通过 dm 设备进行 I/O,实际上是在 super 分区内的指定 offset 上读写。这种抽象使得分区大小可以在 OTA 时动态调整——只需更新 metadata 和重建 dm 映射。
AOSP 核心路径参考:
system/tools/mkbootimg/ — boot.img 创建工具
system/tools/mkbootimg/include/bootimg/bootimg.h — boot image header 定义
system/core/libsparse/ — Sparse image 处理库
system/core/fastboot/ — Fastboot 协议实现
system/core/fs_mgr/ — 文件系统管理器(dm-verity、mount 等)
system/core/fs_mgr/libfs_avb/ — AVB (Verified Boot) 实现
system/extras/partition_tools/ — 分区工具(lpmake、lpunpack 等)
system/update_engine/ — OTA 更新引擎
build/make/tools/releasetools/ — OTA 包生成工具
bootable/recovery/ — Recovery 模式
external/avb/ — libavb 库(Android Verified Boot)
system/core/init/ — 第一阶段挂载(first_stage_mount)