Android 系统 Virtual A/B OTA 升级原理

1,535 阅读12分钟

原文链接:mp.weixin.qq.com/s/tMNsKgNfy…

Android在A/B更新机制的基础上演进出了Virtual A/B更新机制,以减少升级过程中的磁盘空间开销。

Virtual A/B 不像 legacy A/B 一样在动态分区上真实地分配了a/b两份空间。而是将升级增量写入快照,然后在确认启动成功后合并到基本分区中。Virtual A/B 使用了 Android 定制的快照格式。这使得快照可以被压缩并最大限度地减少磁盘空间的使用。在全量 OTA 中,通过压缩,快照大小减少了约 45%,而差分 OTA 快照大小减少了约 55%。

接下来从快照如何创建的角度来探索Virtual A/B的升级原理。

快照分区的创建

super分区的数据格式

super分区的layout:

0x0000   +--------------------+
         | reserved           |
0x1000   +--------------------+
         | primary Geometry   |
0x2000   +--------------------+
         | backup  Geometry   |
0x3000   +--------------------+
         | primary Metadata   |
0x13000  +--------------------+
         | backup Metadata    |
0x23000  +--------------------+
         | backup Metadata    |
0x33000  +--------------------+
         | backup Metadata    |
0x43000  +--------------------+
         |                    |
         |                    |
0x100000 +--------------------+
         | system_a           |
         +--------------------+
         | system_ext_a       |
         +--------------------+
         | vendor_a           |
         +--------------------+
         | ...... other       |
         | Logical Partitions |

期中super分区开头预留的4096空间是为了避免创建意外的引导区

system/core/fs_mgr/liblp/include/liblp/metadata_format.h:

/* Amount of space reserved at the start of every super partition to avoid
 * creating an accidental boot sector.
 */
#define LP_PARTITION_RESERVED_BYTES 4096

定义super分区中metadata结构详细信息在 system/core/fs_mgr/liblp/include/liblp/metadata_format.h

我们找个super.img分析一下。

先将稀疏矩阵格式的super.img转化为普通img:

simg2img super.img super_orig.img

然后使用xxd命令, 即可看到super的primary Geometry

$ xxd -l 8192 super_orig.img
0000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
         ........开头预留的4096都是0
         从0x1000开始就是primary Geometry
0001000: 6744 6c61 3400 0000 12ff 55f0 aba7 b506  gDla4.....U.....
0001010: f25c b5da 5dca 0934 4234 e8df 1d9c 93ae  ...]..4B4......
0001020: 82a4 99d9 8019 467e 0000 0100 0300 0000  ......F~........
0001030: 0010 0000 0000 0000 0000 0000 0000 0000  ................
0001040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
         ........都是0

读取primary Metadata数据: GetPrimaryMetadataOffset() = LP_PARTITION_RESERVED_BYTES + (LP_METADATA_GEOMETRY_SIZE * 2) + geometry.metadata_max_size * slot_number = 4096 + 4096 * 2 + 0x10000 * slot_number

Metadata数据格式:

 *  +-----------------------------------------+
 *  | Header data - fixed size                |
 *  +-----------------------------------------+
 *  | Partition table - variable size         |
 *  +-----------------------------------------+
 *  | Partition table extents - variable size |
 *  +-----------------------------------------+
 *  | Partition groups - variable size        | 
 *  +-----------------------------------------+
 *  | Block devices -    variable size        | 
 *  +-----------------------------------------+

期中Header data数据结构为LpMetadataHeader:

typedef struct LpMetadataHeader {
    /*  0: Four bytes equal to LP_METADATA_HEADER_MAGIC. */
    uint32_t magic;

    /*  4: Version number required to read this metadata. If the version is not
     * equal to the library version, the metadata should be considered
     * incompatible.
     */
    uint16_t major_version;

    /*  6: Minor version. A library supporting newer features should be able to
     * read metadata with an older minor version. However, an older library
     * should not support reading metadata if its minor version is higher.
     */
    uint16_t minor_version;

    /*  8: The size of this header struct. */
    uint32_t header_size;

    /* 12: SHA256 checksum of the header, up to |header_size| bytes, computed as
     * if this field were set to 0.
     */
    uint8_t header_checksum[32];

    /* 44: The total size of all tables. This size is contiguous; tables may not
     * have gaps in between, and they immediately follow the header.
     */
    uint32_t tables_size;

    /* 48: SHA256 checksum of all table contents. */
    uint8_t tables_checksum[32];

    /* 80: Partition table descriptor. */
    LpMetadataTableDescriptor partitions;
    /* 92: Extent table descriptor. */
    LpMetadataTableDescriptor extents;
    /* 104: Updateable group descriptor. */
    LpMetadataTableDescriptor groups;
    /* 116: Block device table. */
    LpMetadataTableDescriptor block_devices;

    /* Everything past here is header version 1.2+, and is only included if
     * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must
     * zero these additional fields.
     */

    /* 128: See LP_HEADER_FLAG_ constants for possible values. Header flags are
     * independent of the version number and intended to be informational only.
     * New flags can be added without bumping the version.
     */
    uint32_t flags;

    /* 132: Reserved (zero), pad to 256 bytes. */
    uint8_t reserved[124];
} __attribute__((packed)) LpMetadataHeader;

Header data的二进制数据如下:

$ xxd -s 12288 -l 65536 super_orig.img
0003000: 3050 4c41 0a00 0200 0001 0000 4bda 5dea  0PLA........K.].
0003010: ca67 6075 27fe fa53 8940 1311 afe8 58da  .g`u'..S.@....X.
0003020: ec28 ca64 5f4a 4f87 5148 de07 d003 0000  .(.d_JO.QH......
0003030: b11f 2677 b804 208b 59e0 d78a 6bd2 65aa  ..&w.. .Y...k.e.
0003040: 4ee2 d8d5 4d64 768e 0e34 63e3 0d03 a8f6  N...Mdv..4c.....

0003050: 0000 0000 0c00 0000 3400 0000 7002 0000  ........4...p...
0003060: 0600 0000 1800 0000 0003 0000 0300 0000  ................
0003070: 3000 0000 9003 0000 0100 0000 4000 0000  0...........@...
0003080: 0100 0000 0000 0000 0000 0000 0000 0000  ................

0x414C5030为LpMetadataHeader.magic
从0x3050开始,是
    /* 80: Partition table descriptor. */
    LpMetadataTableDescriptor partitions;
    /* 92: Extent table descriptor. */
    LpMetadataTableDescriptor extents;
    /* 104: Updateable group descriptor. */
    LpMetadataTableDescriptor groups;
    /* 116: Block device table. */
    LpMetadataTableDescriptor block_devices;
的数据。

typedef struct LpMetadataTableDescriptor {
    /*  0: Location of the table, relative to end of the metadata header. */
    uint32_t offset;
    /*  4: Number of entries in the table. */
    uint32_t num_entries;
    /*  8: Size of each entry in the table, in bytes. */
    uint32_t entry_size;
} __attribute__((packed)) LpMetadataTableDescriptor;

解析得:
LpMetadataTableDescriptor partitions:
uint32_t offset = 0 // LpMetadataHeader 大小为256, 所以分区表从0x3100开始
uint32_t num_entries = 0x0c = 12 //12个分区
uint32_t entry_size = 0x34

LpMetadataTableDescriptor extents
uint32_t offset = 0x270 // LpMetadataHeader 大小为256, 所以extents表从0x3370开始
uint32_t num_entries = 0x06 = 6 //6个extent
uint32_t entry_size = 0x18 //每个extent条目的大小

LpMetadataTableDescriptor groups
uint32_t offset = 0x300 // LpMetadataHeader 大小为256, 所以groups表从0x3400开始
uint32_t num_entries = 0x03 = 3 //3个group
uint32_t entry_size = 0x30 //每个group条目的大小

LpMetadataTableDescriptor block_devices
uint32_t offset = 0x390 // LpMetadataHeader 大小为256, 所以block_devices表从0x3490开始
uint32_t num_entries = 0x01 = 1 //1个block_device
uint32_t entry_size = 0x40 //每个block_device条目的大小

接着从0x3100开始解析分区表:

分区表 LpMetadataPartition, 大小0x34:
0003100: 7379 7374 656d 5f61 0000 0000 0000 0000  system_a........
0003110: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0003120: 0000 0000 0100 0000 0000 0000 0100 0000  ................
0003130: 0100 0000 7379 7374 656d 5f62 0000 0000  ....system_b....
0003140: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0003150: 0000 0000 0000 0000 0100 0000 0100 0000  ................
0003160: 0000 0000 0200 0000 7379 7374 656d 5f65  ........system_e
name[36]     attributes    first_extent_index    num_extents    group_index
system_a     01            0                     01             01
system_b     01            01                    0              02
system_ext_a 01            01                    01             01
system_ext_b 01            02                    0              02
vendor_a     01            02                    01             01
......

从0x3370开始解析extents表:

0003370: e8e0 1100 0000 0000 0000 0000 0008 0000
0003380: 0000 0000 0000 0000 d0d2 0a00 0000 0000
0003390: 0000 0000 00f0 1100 0000 0000 0000 0000
00033a0: 3057 1600 0000 0000 0000 0000 00c8 1c00
00033b0: 0000 0000 0000 0000 788a 0400 0000 0000
00033c0: 0000 0000 0020 3300 0000 0000 0000 0000
00033d0: c04c 0700 0000 0000 0000 0000 00b0 3700
00033e0: 0000 0000 0000 0000 d043 0000 0000 0000
00033f0: 0000 0000 0000 3f00 0000 0000 0000 0000

LpMetadataExtent:

num_sectors   target_type  target_data  target_source
0x11e0e8      0 - LINEAR   0x800        0
0x0ad2d0      0 - LINEAR   0x11f000     0
0x165730      0 - LINEAR   0x1cc800     0
0x048a78      0 - LINEAR   0x332000     0
0x074cc0      0 - LINEAR   0x37b000     0
0x0043d0      0 - LINEAR   0x3f0000     0

0x3400开始解析groups表:

0003400: 6465 6661 756c 7400 0000 0000 0000 0000  default.........
0003410: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0003420: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0003430: 6772 6f75 705f 756e 6973 6f63 5f61 0000  group_unisoc_a..
0003440: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0003450: 0000 0000 0000 0000 0000 c05d 0100 0000  ...........]....
0003460: 6772 6f75 705f 756e 6973 6f63 5f62 0000  group_unisoc_b..
0003470: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0003480: 0000 0000 0000 0000 0000 c05d 0100 0000  ...........]....

LpMetadataPartitionGroup:

char name[36]   flags   maximum_size
default         0       0
group_unisoc_a  0       0x015dc00000
group_unisoc_b  0       0x015dc00000

0x3490开始解析block_devices表:

0003490: 0008 0000 0000 0000 0000 1000 0000 0000  ................
00034a0: 0000 005e 0100 0000 7375 7065 7200 0000  ...^....super...
00034b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00034c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

LpMetadataBlockDevice:

first_logical_sector = 0x0000000000000800
alignment = 0x00100000
alignment_offset = 0x0
size = 0x015e000000
partition_name[36] = super
flags = 0

由LpMetadataPartition.first_extent_index从extents表找到对应的extent, 然后根据找到的LpMetadataExtent.target_data和LpMetadataExtent.num_sectors即可从super分区中找到对应的分区数据。

如system_a的first_extent_index = 0, num_extents = 1 所以对应的extent是

num_sectors   target_type  target_data  target_source
0x11e0e8      0 - LINEAR   0x800        0

0x800 = 2048 0x11e0e8 = 1171688 通过命令

$ dd if=super_orig.img of=system_from_super.img skip=2048 ibs=512 obs=512 count=1171688

即可导出system.img

COW(copy-on-write)分区的创建

COW分区用于存储新系统和老系统的差分数据(新系统-老系统=COW)。COW内容默认会在super分区中创建临时的逻辑分区来进行存储, 而在super分区空间不够用时,就会在/data下创建COW文件。

所以可以根据需求适当减小super分区的大小,以增大date分区的大小,但需要确保在更新系统时,data分区有足够的空间用于存储COW文件。或者,设备只需确保 super 足够大,即可保证在系统升级时绝不需要 /data。参考 Size the super partition

现以都在super分区上创建COW为例分析COW的创建过程。

计算super的空闲空间,并分配给COW

system/core/fs_mgr/libsnapshot/snapshot.cpp:

Return SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) {

    // 系统当前用的metadata
    auto current_metadata = MetadataBuilder::New(opener, current_super, current_slot);

    // 新系统的metadata
    //将current_metadata中的_a分区名字改为'_b',并赋值给target_metadata里的partitions, 原有的_b分区信息舍弃掉
    auto target_metadata =
            MetadataBuilder::NewForUpdate(opener, current_super, current_slot, target_slot);

    // Delete partitions with target suffix in |current_metadata|. Otherwise,
    // partition_cow_creator recognizes these left-over partitions as used space.
    // 从current_metadata中删掉'_b'的grop和partitions
    for (const auto& group_name : current_metadata->ListGroups()) {
        if (android::base::EndsWith(group_name, target_suffix)) {
            current_metadata->RemoveGroupAndPartitions(group_name);
        }
    }

    // 用来构建COW分区的参数
    PartitionCowCreator cow_creator{
            .target_metadata = target_metadata.get(),
            .target_suffix = target_suffix,
            .target_partition = nullptr,
            .current_metadata = current_metadata.get(),
            .current_suffix = current_suffix,
            .update = nullptr,
            .extra_extents = {},
            .compression_enabled = use_compression,
            .compression_algorithm = compression_algorithm,
    };

    auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
                                             &all_snapshot_status);
    ret = InitializeUpdateSnapshots(lock.get(), target_metadata.get(),
                                    exported_target_metadata.get(), target_suffix,
                                    all_snapshot_status);
    //更新super上的分区表
    // [liblp]Updated logical partition table at slot 1 on device super
    if (!UpdatePartitionTable(opener, device_->GetSuperDevice(target_slot),
                              *exported_target_metadata, target_slot)) {
        LOG(ERROR) << "Cannot write target metadata";
        return Return::Error();
    }
}

CreateUpdateSnapshots里有2个关键参数current_metadatatarget_metadata, 它们是LpMetadata类型的数据,分别存储了当前系统和新系统的分区表信息。

Return SnapshotManager::CreateUpdateSnapshotsInternal(
        LockedFile* lock, const DeltaArchiveManifest& manifest, PartitionCowCreator* cow_creator,
        AutoDeviceList* created_devices,
        std::map<std::string, SnapshotStatus>* all_snapshot_status) {

    for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
        // Compute the device sizes for the partition.
        // 计算super上的剩余空间
        auto cow_creator_ret = cow_creator->Run();

        LOG(INFO) << "For partition " << target_partition->name()
                  << ", device size = " << cow_creator_ret->snapshot_status.device_size()
                  << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size()
                  << ", cow partition size = "
                  << cow_creator_ret->snapshot_status.cow_partition_size()
                  << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size();

        // Store these device sizes to snapshot status file.
        if (!CreateSnapshot(lock, cow_creator, &cow_creator_ret->snapshot_status)) {
            return Return::Error();
        }


        // Create the COW partition. That is, use any remaining free space in super partition before
        // creating the COW images.
        if (cow_creator_ret->snapshot_status.cow_partition_size() > 0) {
            if (!target_metadata->ResizePartition(
                        cow_partition, cow_creator_ret->snapshot_status.cow_partition_size(),
                        cow_creator_ret->cow_partition_usable_regions)) {
                LOG(ERROR) << "Cannot create COW partition on metadata with size "
                           << cow_creator_ret->snapshot_status.cow_partition_size();
                return Return::Error();
            }
            // Only the in-memory target_metadata is modified; nothing to clean up if there is an
            // error in the future.
        }
    }

    LOG(INFO) << "Allocating CoW images.";

    for (auto&& [name, snapshot_status] : *all_snapshot_status) {
        // Create the backing COW image if necessary.
        if (snapshot_status.cow_file_size() > 0) {
            auto ret = CreateCowImage(lock, name);
            if (!ret.is_ok()) return AddRequiredSpace(ret, *all_snapshot_status);
        }

        LOG(INFO) << "Successfully created snapshot for " << name;
    }
}

期中CreateUpdateSnapshotsInternal函数里先执行cow_creator->Run()使用current_metadatatarget_metadata里的分区表信息计算出super上的空闲空间。

system/core/fs_mgr/libsnapshot/partition_cow_creator.cpp

std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
    // Being the COW partition virtual, its size doesn't affect the storage
    // memory that will be occupied by the target.
    // The actual storage space is affected by the COW file, whose size depends
    // on the chunks that diverged between |current| and |target|.
    // If the |target| partition is bigger than |current|, the data that is
    // modified outside of |current| can be written directly to |current|.
    // This because the data that will be written outside of |current| would
    // not invalidate any useful information of |current|, thus:
    // - if the snapshot is accepted for merge, this data would be already at
    // the right place and should not be copied;
    // - in the unfortunate case of the snapshot to be discarded, the regions
    // modified by this data can be set as free regions and reused.
    // Compute regions that are free in both current and target metadata. These are the regions
    // we can use for COW partition.
    auto target_free_regions = target_metadata->GetFreeRegions();

    auto current_free_regions = current_metadata->GetFreeRegions();

    auto free_regions = Interval::Intersect(target_free_regions, current_free_regions);
    uint64_t free_region_length = 0;
    for (const auto& interval : free_regions) {
        free_region_length += interval.length();
    }
    free_region_length *= kSectorSize;

    LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes";
}

这里需要注意的是,GetFreeRegions在计算空闲extent时,地址对齐用的是524288,而不是super分区里block_devices表里的alignment = 0x00100000。为啥?因为从kernel去获取alignment了,代码如下:

system/core/fs_mgr/liblp/builder.cpp

std::unique_ptr<MetadataBuilder> MetadataBuilder::New(const LpMetadata& metadata,
                                                      const IPartitionOpener* opener) {
            BlockDeviceInfo device_info;
            if (opener->GetInfo(partition_name, &device_info)) {
                builder->UpdateBlockDeviceInfo(i, device_info);
            }
}

bool MetadataBuilder::UpdateBlockDeviceInfo(size_t index, const BlockDeviceInfo& device_info) {
    LpMetadataBlockDevice& block_device = block_devices_[index];
    // The kernel does not guarantee these values are present, so we only
    // replace existing values if the new values are non-zero.
    if (device_info.alignment) {
        block_device.alignment = device_info.alignment;
    }
}

system/core/fs_mgr/liblp/partition_opener.cpp

bool PartitionOpener::GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const {
    std::string path = GetPartitionAbsolutePath(partition_name);
    return GetBlockDeviceInfo(path, info);
}

bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info) {
#if defined(__linux__)
    unique_fd fd = GetControlFileOrOpen(block_device.c_str(), O_RDONLY);
    ioctl(fd, BLKIOMIN, &device_info->alignment)
}

current_metadatatarget_metadata里的分区表如下, 图中地址是以sector为单位:

current_target_metadata.jpg

经过cow_creator->Runtarget_metadata->ResizePartition后, 在super上分配了COW分区的空间如下:

COW分布图.jpg

创建好的COW设备会map到dm-user, 如:

Mapped COW device for system_b at /dev/block/dm-12

但在升级过程中这些COW设备会被多次unmap和重新map

使用快照映射分区

OTA升级流程来到了往分区写升级数据的步骤,启用了Virtual A/B Compression功能的分区会走以下代码:

VABCPartitionWriter::Init()
  DynamicPartitionControlAndroid::OpenCowWriter()
    SnapshotManager::OpenSnapshotWriter()
      //unmap 快照分区
      UnmapPartitionWithSnapshot()
      MapPartitionWithSnapshot(SnapshotContext::Update)
      //创建base设备: odm_b-base
      CreateLogicalPartition()
      //创建cow设备: odm_b-cow
      MapCowDevices()
      if (context == SnapshotContext::Update && live_snapshot_status->compression_enabled()) {
        // 还没到启动snapuserd的时候,在这里退出
        // Stop here, we can't run dm-user yet, the COW isn't built.
        LOG(ERROR) << "Stop here, we can't run dm-user yet, the COW isn't built.";
        created_devices.Release();
        return true;
      }

以odm_b为例,MapPartitionWithSnapshot()后,odm_b-base, odm_b-cow 2个分区对应的sector地址如下:

odm_b-base:  0x37B000 --- 0x3EFCC0
odm_b-cow:   0x6FF400 --- 0x766D88

odm_b-base,odm_b-cow分配的空间与之前的保持不变

odm_b-base, odm_b-cow的块设备路径分别如下:

odm_b-base: /dev/block/dm-12
odm_b-cow:  /dev/block/dm-13

vabc_partition_writer中, 会往/dev/block/dm-13也就是COW设备写压缩过的升级数据,就因为在vabc_partition_writer中对数据做了压缩处理,所以减慢了OTA升级速度,制作OTA包时加入--disable_vabc参数可以不对升级数据做压缩,加过升级速度。 耗时的DownloadAction执行完后,接着就是做hash校验的FilesystemVerifierAction了,在FilesystemVerifierAction又对快照分区做了多次的unmap和map操作:

FilesystemVerifierAction:
  PerformAction()
    StartPartitionHashing()
      InitializeFdVABC()
        dynamic_control_->UnmapAllPartitions();
        dynamic_control_->MapAllPartitions();

DynamicPartitionControlAndroid:
  MapAllPartitions()
    snapshot_->MapAllSnapshots()

SnapshotManager:
  MapAllSnapshots()
    UnmapPartitionWithSnapshot()
    MapPartitionWithSnapshot(SnapshotContext::Mount)
      //创建base设备: odm_b-base
      CreateLogicalPartition()
      //创建cow设备: odm_b-cow
      MapCowDevices()
      //创建source设备: odm_b-src
      MapSourceDevice()
      //将odm_b-base, odm_b-cow, odm_b-src三个设备传给snapuserd
      //以odm_b为名,odm_b-base的device size为参数创建dm-user设备
      MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time,
            &new_cow_device))
        // Snapuserd还没启动的话,就启动Snapuserd
        EnsureSnapuserdConnected()
        // 往Snapuserd发送'init'命令,创建dm-user '路由表', 对odm_b的I/O操作,将通过Snapuserd路由到odm_b-base, odm_b-cow, odm_b-src
        snapuserd_client_->InitDmUserCow
        // 往Snapuserd发送'start'命令
        snapuserd_client_->AttachDmUser

odm_b为例,MapPartitionWithSnapshot后,odm_b-base, odm_b-cow, odm_b-src 3个分区对应的sector地址如下:

odm_b-base:  0x37B000 --- 0x3EFCC0
odm_b-cow:   0x6FF400 --- 0x766D88
odm_b-src:   0x37B000 --- 0x3EFCC0

odm_b-baseodm_b-cow分配的空间与之前的保持不变, odm_b-src = odm_b-base

odm_b-base, odm_b-cow, dm_b-src, odm_b的块设备路径分别如下:

odm_b-base: /dev/block/dm-47
odm_b-cow:  /dev/block/dm-48
odm_b-src:  /dev/block/dm-49
odm_b:      /dev/block/dm-50

FilesystemVerifierAction中校验odm的参数如下:

update_engine: Partition: odm
update_engine:   source_size: 0
update_engine:   source_path: 
update_engine:   source_hash: 
update_engine:   target_size: 244940800
update_engine:   target_path: 
update_engine:   target_hash: 41DAEED1141E64AB01324FFAD46C5266A35EFCC256C24C0EA64E2EF3CF643202
update_engine:   run_postinstall: false
update_engine:   postinstall_path: 
update_engine:   readonly_target_path: /dev/block/mapper/odm_b

其中,readonly_target_path: /dev/block/mapper/odm_b的真实路径就是/dev/block/dm-50:

# ls -l /dev/block/mapper/
lrwxrwxrwx 1 root root 16 2025-01-21 09:10 odm_b -> /dev/block/dm-50

对/dev/block/mapper/odm_b做hash校验就是对/dev/block/dm-50做hash校验。 做hash校验时会产生读取块设备的I/O请求,然后被dm-user转发到snapuserd处理:

snapuserd: odm_b: Daemon: msg->seq: 0
snapuserd: odm_b: Daemon: msg->len: 131072
snapuserd: odm_b: Daemon: msg->sector: 0
snapuserd: odm_b: Daemon: msg->type: 0
snapuserd: odm_b: Daemon: msg->flags: 2048

snapuserd收到的操作请求: msg->type: 0是#define DM_USER_REQ_MAP_READ 0读的意思。

代码在 system/core/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp

bool Worker::RunThread() {
    while (true) {
        if (!ProcessIORequest()) {
            break;
        }
    }
}

bool Worker::ProcessIORequest() {
    struct dm_user_header* header = bufsink_.GetHeaderPtr();
    switch (header->type) {
        case DM_USER_REQ_MAP_READ: {
            if (!DmuserReadRequest()) {
                return false;
            }
            break;
        }
    }
}

bool Worker::DmuserReadRequest() {
    return ReadAlignedSector(header->sector, header->len, true);
}

bool Worker::ReadAlignedSector() {
    if (not_found) {
        // Block not found in map - which means this block was not
        // changed as per the OTA. Just route the I/O to the base
        // device.
        // 直接从Base设备读取
        if (!ReadDataFromBaseDevice(sector, size)) {
    } else {
        // We found the sector in mapping. Check the type of COW OP and
        // process it.
        // 从COW OP获取
        if (!ProcessCowOp(it->second)) {
            SNAP_LOG(ERROR) << "ProcessCowOp failed";
            header->type = DM_USER_RESP_ERROR;
        }
    }
}

快照合并

升级完成后, 新系统的每个动态分区都由两部分组成——老系统的分区(-base)和新旧系统的差分内容(-cow)。系统在升级完成后重启时需要将分区的原始内容(-base)与差分内容(-cow)进行动态的合并,并使用这合并后的内容启动系统 (此时仅仅只是动态的合并并加载到内存中,并没有物理上的合并)。

如果开机失败,那很简单,分区切回去,再次开机时不加载差分内容(-cow),也就是使用老系统开机。

如果开机成功,会在后台默默将差分内容(-cow)物理的合并进老系统的分区(-base)中,形成真正的新系统。