相关文章:
Android A/B System 分析系列
Android A/B System OTA 分析(一)概览Android A/B System OTA 分析(二)系统image的生成Android A/B System OTA 分析(三)主系统和bootloader的通信Android A/B System OTA 分析(四)系统的启动和升级Android A/B System OTA 分析(五)客户端参数Android A/B System OTA 分析(六)如何获取 payload 的 offset 和 size
Android Update Engine 分析系列
Android Update Engine 分析(一)MakefileAndroid Update Engine 分析(二)Protobuf和AIDL文件Android Update Engine 分析(三)客户端进程Android Update Engine 分析(四)服务端进程Android Update Engine 分析(五)服务端核心之Action机制Android Update Engine 分析(六)服务端核心之Action详解Android Update Engine 分析(七)DownloadAction之FileWriterAndroid Update Engine 分析(八)升级包制作脚本分析Android Update Engine 分析(九)delta_generator 工具的 6 种操作Android Update Engine 分析(十)生成 payload 和 metadata 的哈希Android Update Engine 分析(十一) 更新 payload 签名Android Update Engine 分析(十二) 验证 payload 签名Android Update Engine 分析(十三) 提取 payload 的 property 数据Android Update Engine 分析(十四) 生成 payload 数据
上一篇《Android Update Engine 分析(十四) 生成 payload 数据》中从 payload 文件的层次分析了如何解析参数,如何设置 payload_config,然后遍历分区生成 AnnotatedOperation 数据,最后如何将 AnnotatedOperation 数据写入 payload 文件。
其中提到,根据不同的参数会选择不同的策略去遍历每一个分区的每一个 block 生成相应的 AnnotatedOperation 数据:
- 差分升级的情况下,如果 version.minor = 1(kInPlaceMinorPayloadVersion), 则使用 InplaceGenerator 策略差分升级的情况下,其它情况使用 ABGenerator 策略全量升级情况, 使用 FullUpdateGenerator 策略
原计划是这里一篇就把 3 种策略全部说完,但写到一半的时候发现内容还是太多,所以本篇主要分析最简单的 FullUpdateGenerator 策略,该策略在制作全量包时,或者升级单分区时适用。
本文第 1 节详细分析 FullUpdateGenerator 操作流程,如果觉得繁琐或只关心结论,请直接跳转到第 2 节查看总结,包括函数调用流程和数据转换总结。
1. FullUpdateGenerator 策略源码分析本文涉及的Android代码版本:android‐7.1.1_r23 (NMF27D)
上一篇《Android Update Engine 分析(十四) 生成 payload 数据》中说了,生成 payload 需要遍历所有的分区去生成一大堆的 AnnotatedOperation。在遍历分区时,会调用不同策略的 GenerateUpdatePayloadFile 函数去处理 image 文件的每一个 block,对于 FullUpdateGenerator,我们这里就直接分析 FullUpdateGenerator::GenerateOperations 函数。
1.1 FullUpdateGenerator::GenerateOperations 函数GenerateOperations 中将 image 文件分成很多个数据块(chunk),每个 chunk 打包到一个 ChunkProcessor 中,然后使用多线程并发对每个 chunk 单独进行处理, 每个 chunk 处理后会输出一个 AnnotatedOperation。
函数源码注释如下:
bool FullUpdateGenerator::GenerateOperations(
const PayloadGenerationConfig& config,
const PartitionConfig& old_part,
const PartitionConfig& new_part,
BlobFileWriter* blob_file,
vector* aops) {
TEST_AND_RETURN_FALSE(new_part.ValidateExists());
// FullUpdateGenerator requires a positive chunk_size, otherwise there will
// be only one operation with the whole partition which should not be allowed.
// For performance reasons, we force a small default hard limit of 1 MiB. This
// limit can be changed in the config, and we will use the smaller of the two
// soft/hard limits.
size_t full_chunk_size;
if (config.hard_chunk_size >= 0) {
full_chunk_size = std::min(static_cast(config.hard_chunk_size),
config.soft_chunk_size);
} else {
full_chunk_size = std::min(kDefaultFullChunkSize, config.soft_chunk_size);
LOG(INFO) << "No chunk_size provided, using the default chunk_size for the "
<< "full operations: " << full_chunk_size << " bytes.";
}
TEST_AND_RETURN_FALSE(full_chunk_size > 0);
TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0);
// 将 full_chunk_size 转换成 chunk_blocks
size_t chunk_blocks = full_chunk_size / config.block_size;
// sysconf(_SC_NPROCESSORS_CONF)返回系统可以使用的核数, 所以这里设置的 max_threads 不少于 4 个
size_t max_threads = std::max(sysconf(_SC_NPROCESSORS_ONLN), 4L);
LOG(INFO) << "Compressing partition " << new_part.name
<< " from " << new_part.path << " splitting in chunks of "
<< chunk_blocks << " blocks (" << config.block_size
<< " bytes each) using " << max_threads << " threads";
// 打开 new_part 对应分区的 image 文件,如: /tmp/system.raw.new
int in_fd = open(new_part.path.c_str(), O_RDONLY, 0);
TEST_AND_RETURN_FALSE(in_fd >= 0);
ScopedFdCloser in_fd_closer(&in_fd);
// We potentially have all the ChunkProcessors in memory but only
// |max_threads| will actually hold a block in memory while we process.
// 计算分区的总 block 数 partition_blocks
size_t partition_blocks = new_part.size / config.block_size;
// 分区总块数 partition_blocks, 每次操作的块数 chunk_blocks
// 因此这里计算每个分区操作的 chunk 数量
size_t num_chunks = (partition_blocks + chunk_blocks - 1) / chunk_blocks;
// 调整 aops 向量的大小为 num_chunks
aops->resize(num_chunks);
vector chunk_processors;
// 调整 chunk_processors 向量大小为 num_chunks, 每个 chunk_processors 处理一个 chunk
chunk_processors.reserve(num_chunks);
// 调整 blob_file 的数据块数量为 num_chunks
blob_file->SetTotalBlobs(num_chunks);
// 遍历所有的 chunks,每个 chunk 放到一个独立的 ChunkProcessor 中处理
// 为什么叫 ChunkProcessor 啊?因为每次处理一个 chunk 数据
for (size_t i = 0; i < num_chunks; ++i) {
// 计算第 i 个 chunk 操作的起始地址
size_t start_block = i * chunk_blocks;
// 获取第 i 个 chunk 操作的 block 数量, 最后一个操作可能小于一个完整的 chunk
// The last chunk could be smaller.
size_t num_blocks = std::min(chunk_blocks,
partition_blocks - i * chunk_blocks);
// 获取第 i 个 chunk 对应的 AnnotatedOperation 指针
// Preset all the static information about the operations. The
// ChunkProcessor will set the rest.
AnnotatedOperation* aop = aops->data() + i;
// 设置第 i 个 chunk 对应的 AnnotatedOperation 名字:
aop->name = base::StringPrintf("<%s-operation-%" PRIuS ">",
new_part.name.c_str(), i);
// 用第 i 个 chunk 操作的起始地址 start_block 和 num_blocks 设置对应 InstallOperation 的 dst_extent
Extent* dst_extent = aop->op.add_dst_extents();
dst_extent->set_start_block(start_block);
dst_extent->set_num_blocks(num_blocks);
// 往 chunk_processors 写入一个 chunk 操作的任务
// offset: start_block * config.block_size
// size: num_blocks * config.block_size
chunk_processors.emplace_back(
config.version,
in_fd,
static_cast(start_block) * config.block_size,
num_blocks * config.block_size,
blob_file,
aop);
}
// Thread pool used for worker threads.
base::DelegateSimpleThreadPool thread_pool("full-update-generator",
max_threads);
thread_pool.Start();
for (ChunkProcessor& processor : chunk_processors)
thread_pool.AddWork(&processor);
thread_pool.JoinAll();
// All the work done, disable logging.
blob_file->SetTotalBlobs(0);
// 如果某个 Chunk Processor 处理失败了,则对应 AnnotatedOperation 的 type 为空,如果出现这种情况则说明操作失败了
// All the operations must have a type set at this point. Otherwise, a
// ChunkProcessor failed to complete.
for (const AnnotatedOperation& aop : *aops) {
if (!aop.op.has_type())
return false;
}
return true;
}
提取上面注释中的重点,主要做了以下事情:
- 设置 full_chunk_size,即每个 chunk 的大小,默认情况下计算的结果为 2M根据 image 大小和单次处理 chunk 的大小得到需要操作的次数,用于设置 ChunkProcessor 和 AnnotatedOperation 向量数组将每个 chunk 的操作打包到一个 ChunkProcessor 中使用多线程队列对向量数组中的 ChunkProcessor 进行并行处理,生成 AnnotatedOperation检查生成的 AnnotatedOperation 中是否存在异常的结果
不过,这个函数只是将待处理的 image 划分成很多 chunk 数据块进行处理,但还没有涉及每个 chunk 操作的细节。
1.2 ChunkProcessor::ProcessChunk 函数每个 chunk 数据块都打包到一个 ChunkProcessor 中,因此数据块处理的细节就需要去分析 ChunkProcessor。
这里执行并行任务时操作 thread_pool,但最终其实调用的是 ChunkProcessor 的 Run 函数,在 Run 函数中进一步调用了 ProcessChunk,所以这里的 ProcessChunk 才是真正干活的那个人。
函数注释如下:
// 文件: system/update_engine/payload_generator/full_update_generator.cc
bool ChunkProcessor::ProcessChunk() {
brillo::Blob buffer_in_(size_);
brillo::Blob op_blob;
ssize_t bytes_read = -1;
// 调用 PReadAll 从指定的句柄 fd_ 的 offset_ 开始处读取数据到 buffer_in_ 缓冲区
// buffer_in_ 缓冲大小为 size_, 因此最多读取 size_ 字节,实际读取数量存放在 bytes_read 中
TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
buffer_in_.data(),
buffer_in_.size(),
offset_,
&bytes_read));
TEST_AND_RETURN_FALSE(bytes_read == static_cast(size_));
// 调用 GenerateBestFullOperation 处理读取到的 chunk 数据
// - 处理后的数据内容保存在 op_blob 中
// - 处理后的数据信息保存在 op_type 中
InstallOperation_Type op_type;
TEST_AND_RETURN_FALSE(diff_utils::GenerateBestFullOperation(
buffer_in_, version_, &op_blob, &op_type));
// 将处理后的 op_blob 和 op_type 打包成一个 AnnotatedOperation aop_
aop_->op.set_type(op_type);
TEST_AND_RETURN_FALSE(aop_->SetOperationBlob(op_blob, blob_file_));
return true;
}
ProcessChunk() 主要干了两件事:
- 读取相应 Chunk 的数据将读取的 Chunk 数据交由 GenerateBestFullOperation 函数处理用处理返回的结果,包括处理后数据的内容以及相关信息,生成 AnnotatedOperation
所以最终这一个 chunk 的数据读取出来后交由 GenerateBestFullOperation 函数处理生成相应的 AnnotatedOperation。
1.3 GenerateBestFullOperation 函数GenerateBestFullOperation 函数就是最底层处理每一个 byte 数据的那个码农了~
函数根据传入的版本 Version 参数对数据 new_data 进行处理,将处理后的实际数据存放在 out_blob 中,处理后的数据信息(InstallOperation_Type) 存放在 out_type 中。
函数注释如下:
// 文件: system/update_engine/payload_generator/delta_diff_utils.cc
bool GenerateBestFullOperation(const brillo::Blob& new_data,
const PayloadVersion& version,
brillo::Blob* out_blob,
InstallOperation_Type* out_type) {
if (new_data.empty())
return false;
if (version.OperationAllowed(InstallOperation::ZERO) &&
std::all_of(
new_data.begin(), new_data.end(), [](uint8_t x) { return x == 0; })) {
// The read buffer is all zeros, so produce a ZERO operation. No need to
// check other types of operations in this case.
*out_blob = brillo::Blob();
*out_type = InstallOperation::ZERO;
return true;
}
bool out_blob_set = false;
// Try compressing |new_data| with xz first.
if (version.OperationAllowed(InstallOperation::REPLACE_XZ)) {
brillo::Blob new_data_xz;
if (XzCompress(new_data, &new_data_xz) && !new_data_xz.empty()) {
*out_type = InstallOperation::REPLACE_XZ;
*out_blob = std::move(new_data_xz);
out_blob_set = true;
}
}
// Try compressing it with bzip2.
if (version.OperationAllowed(InstallOperation::REPLACE_BZ)) {
brillo::Blob new_data_bz;
// TODO(deymo): Implement some heuristic to determine if it is worth trying
// to compress the blob with bzip2 if we already have a good REPLACE_XZ.
if (BzipCompress(new_data, &new_data_bz) && !new_data_bz.empty() &&
(!out_blob_set || out_blob->size() > new_data_bz.size())) {
// A REPLACE_BZ is better or nothing else was set.
*out_type = InstallOperation::REPLACE_BZ;
*out_blob = std::move(new_data_bz);
out_blob_set = true;
}
}
// If nothing else worked or it was badly compressed we try a REPLACE.
if (!out_blob_set || out_blob->size() >= new_data.size()) {
*out_type = InstallOperation::REPLACE;
// This needs to make a copy of the data in the case bzip or xz didn't
// compress well, which is not the common case so the performance hit is
// low.
*out_blob = new_data;
}
return true;
}
总结函数的重点:
- 处理全 0 数据,生成一个 InstallOperation::ZERO 操作使用 XZ 的方式压缩数据,生成一个 InstallOperation::REPLACE_XZ 操作使用 bzip2 方式压缩数据,生成一个 InstallOperation::REPLACE_BZ 操作其余情况则不做处理,直接生成一个 InstallOperation::REPLACE 操作
换句话说,按照数据压缩的大小,GenerateBestFullOperation 函数将当个数据块 chunk 的内容转换成以下 4 种操作之一:
全 0 数据生成空包: InstallOperation::ZERO使用 XZ 压缩: InstallOperation::REPLACE_XZ使用 BZIP2 压缩: InstallOperation::REPLACE_BZ保留原有数据不压缩: InstallOperation::REPLACE
文字理解不太直观,我画了一张图,可以对照着理解:
2. FullUpdateGenerator 策略总结FullUpdateGenerator 将待处理的分区文件(如 system.img )划分成大小为 2M 的若干数据块,然后将每个数据块及其起始地址和大小打包到一个 ChunkProcessor 中,交由多个线程进行并行处理,最终每个 ChunkProcessor 得到一个 AnnotatedOperation,待所有操作执行完后,检查所有生成的 AnnotatedOperation,返回给上一层的函数。
2.1 函数调用流程FullUpdateGenerator 处理时函数的调用流程如下:
2.2 数据转换FullUpdateGenerator 处理时每一个数据块的转换操作如下:
3. 问题:thread_pool 是如何启动 ChunkProcessor 去干活的呢?问题来了,thread_pool 是如何启动 ChunkProcessor 去干活的呢?因为这个代码流转不是这里分析的重点,所以略去了。
如果你喜欢弄清楚来龙去脉,建议深入研究下。
4. 其它洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。
所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:
一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。一个Android OTA的讨论组,请说明加Android OTA群。一个git和repo的讨论组,请说明加git和repo群。
在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:
洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:



