10、系统镜像与分区:system.img, vendor.img, product.img 的生成、分区大小配置
说到 Android 系统镜像,我刚开始接触时也觉得挺神秘的。说白了,你手机里跑的那些系统文件,最后都要打包成一个个镜像文件,烧录到对应的分区里。今天我们就来聊聊 system.img、vendor.img、product.img 这几个核心镜像,它们是怎么生成的,分区大小又该怎么配。
10.1 镜像文件到底是什么?
镜像文件,你可以理解成是一个「硬盘的完整快照」。Android 编译完成后,会把所有属于某个分区的文件,按照特定的文件系统格式打包成一个 .img 文件。这个文件里包含了目录结构、文件权限、SELinux 标签等等所有信息。
举个例子,system.img 里装的就是 Android 框架层的东西——系统应用、库文件、服务等等。vendor.img 装的是硬件相关的驱动和库,product.img 则是厂商自定义的产品特性。
核心要点:每个镜像对应一个分区,分区就是存储设备上的一块独立区域。系统启动时,会把这些镜像挂载到对应的挂载点。
10.2 镜像的生成流程
编译系统是怎么把一堆文件变成 .img 的呢?我画了个流程图,你看一眼就明白了。
流程其实不复杂。编译系统先把源码编译成目标文件,然后调用文件系统工具(比如 make_ext4fs 或 mke2fs),把这些文件按照 ext4 格式打包成镜像。最后生成的镜像文件,就放在 out/target/product/<设备名>/ 目录下。
小技巧:我个人习惯在编译完成后,先检查一下 out/target/product/<设备名>/ 目录下的镜像文件大小。如果发现某个镜像异常大,多半是配置文件里多了不该有的东西。
10.3 分区大小配置
分区大小配置,这是很多新手容易踩坑的地方。我记得刚带团队时,有个同事把 system 分区配小了,结果编译出来的镜像塞不进去,折腾了大半天。
分区大小配置主要在 BoardConfig.mk 或 BoardConfigCommon.mk 里定义。常见的配置项有这些:
| 配置项 | 说明 | 示例值 |
|---|---|---|
BOARD_SYSTEMIMAGE_PARTITION_SIZE |
system 分区大小 | 2147483648 (2GB) |
BOARD_VENDORIMAGE_PARTITION_SIZE |
vendor 分区大小 | 1073741824 (1GB) |
BOARD_PRODUCTIMAGE_PARTITION_SIZE |
product 分区大小 | 536870912 (512MB) |
BOARD_SUPER_PARTITION_SIZE |
动态分区总大小(Android 10+) | 4294967296 (4GB) |
配置示例:
# BoardConfig.mk
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 2147483648
BOARD_VENDORIMAGE_PARTITION_SIZE := 1073741824
BOARD_PRODUCTIMAGE_PARTITION_SIZE := 536870912
# 如果是动态分区(Android 10 以后)
BOARD_SUPER_PARTITION_SIZE := 4294967296
BOARD_SUPER_PARTITION_GROUPS := group_1
BOARD_SUPER_PARTITION_GROUP_group_1_SIZE := 4294967296
注意:分区大小单位是字节。2GB 就是 2 * 1024 * 1024 * 1024 = 2147483648。我曾经见过有人直接写 2048,结果分区只有 2KB,编译出来的镜像根本写不进去。
10.4 动态分区与静态分区
这里有个重要的概念要区分——动态分区和静态分区。
静态分区:每个分区在烧录时就固定了大小,比如 system 分区 2GB,vendor 分区 1GB。优点是简单直接,缺点是空间利用率低——万一 system 只用了 1.5GB,剩下的 0.5GB 就浪费了。
动态分区:Android 10 引入的新机制。所有分区都放在一个「超级分区」(super partition) 里,空间可以灵活分配。system 需要 1.8GB,vendor 需要 800MB,那就各取所需,剩下的空间还能给 product 用。
我个人更推荐动态分区,尤其是现在设备存储空间越来越大的情况下。不过要注意,动态分区需要 bootloader 和内核的支持,老设备可能用不了。
10.5 如何确定分区大小?
这个问题没有标准答案,但有个经验法则:
- 先编译一次,看看生成的镜像文件实际有多大。
- 留出 20%-30% 的余量,给后续 OTA 升级和功能扩展。
- 考虑文件系统开销,ext4 文件系统本身会占用一些空间(比如 inode 表、日志等)。
举个例子,我做过的一个项目:
# 编译后实际大小
system.img: 1.2GB
vendor.img: 600MB
product.img: 300MB
# 配置的分区大小(留了 30% 余量)
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 1610612736 # 1.5GB
BOARD_VENDORIMAGE_PARTITION_SIZE := 805306368 # 768MB
BOARD_PRODUCTIMAGE_PARTITION_SIZE := 402653184 # 384MB
避坑指南:我曾经遇到过一个问题——编译出来的 system.img 只有 1.8GB,但分区配了 2GB,烧录时却提示空间不足。后来发现是 ext4 文件系统的保留块(reserved blocks)占用了 5% 的空间。解决办法是在 make_ext4fs 时加上 -l 参数指定实际可用大小,或者在分区配置里多留一些余量。
10.6 镜像生成的关键脚本
如果你想知道镜像到底是怎么生成的,可以看看 build/make/core/Makefile 里的相关规则。核心逻辑在 build_image.py 这个脚本里:
# 简化的调用过程
build_image.py \
--target-files out/target/product/xxx/obj/PACKAGING/target_files_intermediates/xxx-target_files-eng.xxx.zip \
--system-image-size 2147483648 \
--vendor-image-size 1073741824 \
--product-image-size 536870912
这个脚本会读取分区大小配置,然后调用 make_ext4fs 或 mke2fs 生成 ext4 格式的镜像文件。如果你用的是 Android 10 以上的动态分区,还会调用 lpmake 工具来生成超级分区镜像。
10.7 验证镜像是否正确
镜像生成后,我建议你做两件事:
- 检查镜像大小:用
ls -lh看看镜像文件是否接近你配置的分区大小。如果差太多,说明配置可能有问题。 - 挂载检查内容:用
mount -o loop把镜像挂载到临时目录,看看里面的文件结构是否完整。
# 挂载 system.img 检查
mkdir /tmp/system_mount
sudo mount -o loop out/target/product/xxx/system.img /tmp/system_mount
ls /tmp/system_mount/
# 检查关键目录是否存在
ls /tmp/system_mount/app/
ls /tmp/system_mount/framework/
sudo umount /tmp/system_mount
嗯,这一步虽然简单,但能帮你发现很多问题。比如我之前就遇到过 vendor.img 里少了某个 HAL 库,导致设备无法启动。如果提前挂载检查,就能在烧录前发现问题。
10.8 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 镜像大小超过分区大小 | 分区配置太小,或编译产物过多 | 增大分区配置,或清理不必要的文件 |
| 烧录后设备无法启动 | 镜像内容不完整,或文件权限错误 | 检查 SELinux 标签和文件权限 |
| 动态分区无法挂载 | lpmake 参数错误,或分区表损坏 | 重新生成超级分区镜像 |
| vendor 分区缺少驱动 | 编译配置遗漏了某个模块 | 检查 PRODUCT_PACKAGES 是否包含所需模块 |
总的来说,系统镜像和分区配置是 Android 构建系统里比较基础但也很关键的一环。你只要理解了镜像的生成流程,掌握了分区大小的配置方法,再注意一下动态分区和静态分区的区别,这块基本就没问题了。
公众号:蓝海资料掘金营,微信deep3321