分区和映像

分区

Android 设备包含若干个分区,这些分区在启动过程中发挥不同的作用。为了支持 A/B 更新,设备需要为 bootsystemvendorradio 分区分别单独配置一个槽位。

  • bootboot 分区包含通过 mkbootimg 组合在一起的内核映像和 RAM 磁盘。为了直接刷写内核而不刷写新的 boot 分区,可以使用虚拟分区:
    • kernelkernel 虚拟分区仅覆盖内核(zImage、zImage-dtb、Image.gz-dtb),方法是写入新的映像来覆盖旧的映像。为此,它会确定 eMMC 中现有内核映像的起始位置并将新内核映像复制到该位置。请记住,新内核映像可能会大于现有内核映像。引导加载程序可以通过移动其后的任何数据来腾出空间或放弃出错的操作。如果提供的开发内核不兼容,则可能需要使用相关的内核模块更新 dtb 分区(如果存在)、vendor 分区或 system 分区。
    • ramdiskramdisk 虚拟分区通过将新映像写入旧磁盘来仅覆盖 RAM 磁盘。为此,它会确定 eMMC 中现有 ramdisk.img 的起始位置并将新 RAM 磁盘映像复制到该位置。请记住,新 RAM 磁盘映像可能会大于现有 RAM 磁盘映像。引导加载程序可以通过移动其后的任何数据来腾出空间或放弃出错的操作。
  • systemsystem 分区主要包含 Android 框架。
  • recoveryrecovery 分区用于存储在 OTA 过程中启动的恢复映像。如果设备支持 A/B 更新,则恢复映像可以是启动映像中包含的 RAM 磁盘,而不是单独的映像。
  • cachecache 分区用于存储临时数据,如果设备使用 A/B 更新,则可以不要此分区。cache 分区不需要可从引导加载程序写入,而只需要可清空。大小取决于设备类型和 userdata 分区的可用空间。目前,50MB 至 100MB 应该没问题。
  • miscmisc 分区供恢复映像使用,存储空间不能小于 4KB。
  • userdatauserdata 分区包含用户安装的应用和数据,包括自定义数据。
  • metadata:如果设备被加密,则需要使用 metadata 分区,该分区的存储空间不能小于 16MB。
  • vendorvendor 分区包含所有不可分发给 Android 开源项目 (AOSP) 的二进制文件。如果没有专有信息,则可以省略此分区。
  • radioradio 分区包含无线装置映像。只有包含无线装置且在专用分区中包含无线装置专用软件的设备才需要此分区。
  • tostos 分区用于存储 Trusty 操作系统的二进制映像文件,仅在设备包含 Trusty 时使用。

流程

引导加载程序的运作方式如下:

  1. 首先加载引导加载程序。
  2. 引导加载程序初始化内存。
  3. 如果使用 A/B 更新,则确定要启动的当前槽位。
  4. 确定是否应按照支持更新中所述改为启动恢复模式。
  5. 引导加载程序加载映像,其中包含内核和 RAM 磁盘(在 Treble 中,包含更多部分)。
  6. 引导加载程序开始将内核作为可自行执行的压缩二进制文件加载到内存中。
  7. 内核将自身解压缩并开始执行到内存中。
  8. 自此,旧版设备从 RAM 磁盘加载 init,而新版设备从 /system 分区加载它。
  9. init/system 分区中启动并开始装载其他所有分区,如 /vendor/oem/odm,然后开始执行代码以启动设备

映像

引导加载程序依赖于下面这些映像。

内核映像

内核映像以标准 Linux 格式创建,如 zImage、Image 或 Image.gz。内核映像可以单独刷写,也可以与 RAM 磁盘映像相结合,可以刷写到 boot 分区,也可以从内存启动。在创建内核映像时,建议使用连接的设备树二进制文件,而不是为设备树使用一个单独的分区。为不同的电路板修订版本使用多个设备树 Blob (DTB) 时,应按电路板修订版本降序连接多个 DTB。

RAM 磁盘映像

RAM 磁盘应包含适合作为 rootfs 装载的根文件系统。RAM 磁盘映像先通过 mkbootfs 与内核映像组合在一起,然后再刷写到 boot 分区中。

启动映像

启动映像应包含通过未经修改的 mkbootimg 组合在一起的内核和 RAM 磁盘。

您可以在以下位置找到 mkbootimg 实现:system/core/mkbootimg

引导加载程序会读取由 mkbootimg 生成的 bootimg.h 头文件,并更新内核标头,使其包含被刷写的 RAM 磁盘的正确位置和大小、内核基址、命令行参数以及其他内容。然后,引导加载程序会将启动映像中指定的命令行附加到引导加载程序生成的命令行的末尾。

文件系统映像(系统、用户数据和恢复映像)

YAFFS2 映像格式

如果使用原始 NAND 存储空间,则这些映像必须是由未经修改的 mkyaffs2image 生成的 YAFFS2(可以在 Android 开源项目 (AOSP) 的 external/yaffs2/yaffs2/utils 中找到)。它们采用以下格式:


| 2k bytes of data| yaffs extra data | padding | | 0  2048 | 0 64 | variable|

引导加载程序负责使用这些映像,并将 YAFFS 额外数据转移到给定 NAND 硬件的带外区域中的适当位置。如果需要软件 ECC,则引导加载程序还应在此时执行相应的计算。

稀疏映像格式

应支持稀疏映像格式。“ext4 压缩映像”文档以及 system/core/libsparse/sparse_format.h 中都对这种格式进行了说明,该格式的实现位置为:system/core/libsparse/sparse_read.cpp

如果使用基于块的存储设备,则应支持 ext4 或 f2fs。要快速传输并刷写大型空 ext4 文件系统 (userdata),应以稀疏格式存储映像,映像中包含有关文件系统的哪些区域可以保留不写的信息。文件格式由 mke2fs 实用程序编写,该实用程序还用于创建文件格式由引导加载程序读取和刷写的映像。有关属性,请参见下面几部分:

文件格式
  • 所有字段都采用无符号的小端字节序
  • 文件包含一个文件标头,后跟一系列区块
  • 文件标头、区块标头和区块数据全部都是 4 个字节长的倍数
  • 32 位魔数:0xed26ff3a
  • 16 位 Major 版本 (0x1) - 拒绝 Major 版本更高的映像
  • 16 位 Minor 版本 (0x0) - 允许 Minor 版本更高的映像
  • 以字节为单位的 16 位文件标头大小(在 v1.0 中为 28 位)
  • 以字节为单位的 16 位区块标头大小(在 v1.0 中为 12 位)
  • 以字节为单位的 32 位块大小,必须是 4 的倍数
  • 输出文件中的 32 位块总大小
  • 输入文件中的 32 位区块总大小

原始数据的 32 位 CRC32 校验和(将“随意”算作 0 标准 802.3 多项式)使用公开域表格实现

区块
  • 16 位区块类型:
    • 0xCAC1 原始
    • 0xCAC2 填充
    • 0xCAC3 随意
  • 16 位保留(写入为 0,读取时忽略)
  • 输出映像中以块为单位的 32 位区块大小
  • 区块输入文件中以字节为单位的 32 位总大小(包括区块标头和数据)
数据
  • 对于原始类型,表示原始数据,即以块为单位的大小 * 以字节为单位的块大小
  • 对于填充类型,表示 4 字节的填充数据
实现写入程序

mke2fs 实用程序已经知道需要写入映像的哪些区域,并且会在它们之间编码“随意”区块。另一个工具 img2simg 会将常规(非稀疏)映像转换为稀疏映像。常规映像没有关于“随意”区域的信息,转换过程至多能做到的是查找重复数据块,以减小所生成映像的大小。

实现读取程序

读取程序应拒绝 Major 版本未知的映像,但应接受 Minor 版本未知的映像。读取程序可能会拒绝区块大小不受其支持的映像。

Major 版本经过验证后,读取程序应忽略具有未知类型字段的区块。它应使用“文件中的区块大小”跳过文件中的区块,并跳过输出中的“以块为单位的区块大小”块。

应针对将要写入磁盘的数据计算循环冗余校验 802.3 CRC32。任何未写入的区域(随意区域或跳过的区块)都应在 CRC 中算作 0。应将写入或跳过的总块数与标头中的“总块数”字段进行比较。simg2img 工具会将稀疏映像格式转换为标准映像,这样会丢失稀疏信息。