在介绍完了DownloadAction之后,还剩下FilesystemVerifierAction和PostinstallRunnerAction,下面开始对其进行分析。

FilesystemVerifierAction

在数据下载完成后,在DownloadAction中会切换到FilesystemVerifierAction

 void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
if (writer_) {
........
// Write the path to the output pipe if we're successful.
if (code == ErrorCode::kSuccess && HasOutputPipe())
SetOutputObject(install_plan_);
processor_->ActionComplete(this, code);
}

最后的ActionComplete会开始执行FilesystemVerifierAction。

src/system/update_engine/payload_consumer/filesystem_verifer_action.cc

  void FilesystemVerifierAction::PerformAction() {
// Will tell the ActionProcessor we've failed if we return.
ScopedActionCompleter abort_action_completer(processor_, this); if (!HasInputObject()) {
LOG(ERROR) << "FilesystemVerifierAction missing input object.";
return;
}
install_plan_ = GetInputObject(); //获取上一个Action传过来的install_plan_ if (install_plan_.partitions.empty()) {
LOG(INFO) << "No partitions to verify.";
if (HasOutputPipe())
SetOutputObject(install_plan_);
abort_action_completer.set_code(ErrorCode::kSuccess);
return;
} StartPartitionHashing(); //开始计算分区的hash
abort_action_completer.set_should_complete(false);
}

接着看StartPartitionHashing

  void FilesystemVerifierAction::StartPartitionHashing() {
if (partition_index_ == install_plan_.partitions.size()) { //判断是否验证到了最后一个分区
Cleanup(ErrorCode::kSuccess);
return;
}
InstallPlan::Partition& partition =
install_plan_.partitions[partition_index_]; string part_path;
switch (verifier_step_) { //默认值是KVerifyTargetHash
case VerifierStep::kVerifySourceHash:
part_path = partition.source_path;
remaining_size_ = partition.source_size;
break;
case VerifierStep::kVerifyTargetHash:
part_path = partition.target_path; //分区的路径
remaining_size_ = partition.target_size; //大小
break;
}
LOG(INFO) << "Hashing partition " << partition_index_ << " ("
<< partition.name << ") on device " << part_path;
if (part_path.empty())
return Cleanup(ErrorCode::kFilesystemVerifierError); brillo::ErrorPtr error;
src_stream_ = brillo::FileStream::Open( //打开对应的分区文件
base::FilePath(part_path),
brillo::Stream::AccessMode::READ,
brillo::FileStream::Disposition::OPEN_EXISTING,
&error); if (!src_stream_) {
LOG(ERROR) << "Unable to open " << part_path << " for reading";
return Cleanup(ErrorCode::kFilesystemVerifierError);
} buffer_.resize(kReadFileBufferSize); //重置缓存区的大小
read_done_ = false; //未被读取完成
hasher_.reset(new HashCalculator()); //设置HashCalculator // Start the first read.
ScheduleRead(); //开始读取
}

首先判断是否验证的分区的所有hash,如果验证完成了,调用CleanUp做最后的工作。

CleanUp

  void FilesystemVerifierAction::Cleanup(ErrorCode code) {
src_stream_.reset();
// This memory is not used anymore.
buffer_.clear(); if (cancelled_)
return;
if (code == ErrorCode::kSuccess && HasOutputPipe())
SetOutputObject(install_plan_);
processor_->ActionComplete(this, code);
}

可以看到主要就是清空缓存区,设置install_plan_,切换到下一个Action。如果没有验证完成,就获取要验证的分区路径和大小,这个大小只是要验证的大小,不一定是分区的真正大小。对于镜像文件而言1G的大小能被安装在2G的分区上。接下来调用ScheduleRead()开始进行验证。

ScheduleRead()

  void FilesystemVerifierAction::ScheduleRead() {
size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
remaining_size_); //获取要读取数据的大小
if (!bytes_to_read) { //读取完成
OnReadDoneCallback();
return;
} bool read_async_ok = src_stream_->ReadAsync(
buffer_.data(),
bytes_to_read,
base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
base::Unretained(this)),
base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
base::Unretained(this)),
nullptr); //开始读取 if (!read_async_ok) {
LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
Cleanup(ErrorCode::kError);
}
}

获取读取数据的真实大小,开始读取数据。

 void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
if (bytes_read == ) { //读取完成
read_done_ = true;
} else {
remaining_size_ -= bytes_read;
CHECK(!read_done_);
if (!hasher_->Update(buffer_.data(), bytes_read)) { //计算hash
LOG(ERROR) << "Unable to update the hash.";
Cleanup(ErrorCode::kError);
return;
}
} // We either terminate the current partition or have more data to read.
if (cancelled_)
return Cleanup(ErrorCode::kError); if (read_done_ || remaining_size_ == ) {
if (remaining_size_ != ) {
LOG(ERROR) << "Failed to read the remaining " << remaining_size_
<< " bytes from partition "
<< install_plan_.partitions[partition_index_].name;
return Cleanup(ErrorCode::kFilesystemVerifierError);
}
return FinishPartitionHashing(); //计算完成后
}
ScheduleRead(); //如果没有计算完成,继续计读取计算
}

在这个方法中会对读取的数据进行hash计算,每次计算其实都是基于前一次的计算结果来进行的,不然就会有太对的数据加载到内存中,导致内存不足。当计算完成后

  void FilesystemVerifierAction::FinishPartitionHashing() {
if (!hasher_->Finalize()) {
LOG(ERROR) << "Unable to finalize the hash.";
return Cleanup(ErrorCode::kError);
}
InstallPlan::Partition& partition =
install_plan_.partitions[partition_index_];
LOG(INFO) << "Hash of " << partition.name << ": "
<< Base64Encode(hasher_->raw_hash()); switch (verifier_step_) {
case VerifierStep::kVerifyTargetHash:
if (partition.target_hash != hasher_->raw_hash()) { //对保存的targethash和计算得到的hash进行一个比较
LOG(ERROR) << "New '" << partition.name
<< "' partition verification failed.";
if (partition.source_hash.empty()) {
// No need to verify source if it is a full payload.
return Cleanup(ErrorCode::kNewRootfsVerificationError);
}
// If we have not verified source partition yet, now that the target
// partition does not match, and it's not a full payload, we need to
// switch to kVerifySourceHash step to check if it's because the source
// partition does not match either.
verifier_step_ = VerifierStep::kVerifySourceHash; //计算source hash
} else {
partition_index_++; //计算下一个分区
}
break;
case VerifierStep::kVerifySourceHash:
if (partition.source_hash != hasher_->raw_hash()) { //保存的source hash和计算得到的也不相同
LOG(ERROR) << "Old '" << partition.name
<< "' partition verification failed.";
LOG(ERROR) << "This is a server-side error due to mismatched delta"
<< " update image!";
LOG(ERROR) << "The delta I've been given contains a " << partition.name
<< " delta update that must be applied over a "
<< partition.name << " with a specific checksum, but the "
<< partition.name
<< " we're starting with doesn't have that checksum! This"
" means that the delta I've been given doesn't match my"
" existing system. The "
<< partition.name << " partition I have has hash: "
<< Base64Encode(hasher_->raw_hash())
<< " but the update expected me to have "
<< Base64Encode(partition.source_hash) << " .";
LOG(INFO) << "To get the checksum of the " << partition.name
<< " partition run this command: dd if="
<< partition.source_path
<< " bs=1M count=" << partition.source_size
<< " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 "
"-binary | openssl base64";
LOG(INFO) << "To get the checksum of partitions in a bin file, "
<< "run: .../src/scripts/sha256_partitions.sh .../file.bin";
return Cleanup(ErrorCode::kDownloadStateInitializationError);
}
// The action will skip kVerifySourceHash step if target partition hash
// matches, if we are in this step, it means target hash does not match,
// and now that the source partition hash matches, we should set the error
// code to reflect the error in target partition.
// We only need to verify the source partition which the target hash does
// not match, the rest of the partitions don't matter.
return Cleanup(ErrorCode::kNewRootfsVerificationError);
}
// Start hashing the next partition, if any.
hasher_.reset(); //重置hash计算器
buffer_.clear(); //清空缓存
src_stream_->CloseBlocking(nullptr);
StartPartitionHashing(); //接着计算
}

可见当一个分区的hash被计算出来的时候就会根据保存好的进行比较,如果target的hash不一致就会转向比较该分区的source hash,其实比较source hash主要就是为了确定错误的类型,只要target hash不一致,无论source hash是否一致都不会继续下一个分区的计算了。就这样一直到最后一个分区验证完后,执行最后一个Action,PostinstallRunnerAction。

PostinstallRunnerAction

PostinstallRunnerAction执行每个分区更新完后的postinstall script。但是在高通平台的,android8.0上无论是全包还是差分包升级并没有实质性的postinstall script。在PostinstallRunnerAction中仅仅是将target_slot标记为active状态。目前只分析于执行相关的代码。

src/system/update_engine/payload_consumer/postinstall_runner_action.cc

  void PostinstallRunnerAction::PerformAction() {
CHECK(HasInputObject());
install_plan_ = GetInputObject(); //获取install_plan_ if (install_plan_.powerwash_required) { //是否需要进行数据的擦除
if (hardware_->SchedulePowerwash()) {
powerwash_scheduled_ = true;
} else {
return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
}
} // Initialize all the partition weights.
partition_weight_.resize(install_plan_.partitions.size()); //初始化每个分区的权重
total_weight_ = ;
for (size_t i = ; i < install_plan_.partitions.size(); ++i) {
// TODO(deymo): This code sets the weight to all the postinstall commands,
// but we could remember how long they took in the past and use those
// values.
partition_weight_[i] = install_plan_.partitions[i].run_postinstall;
total_weight_ += partition_weight_[i]; //计算总的权重
}
accumulated_weight_ = ;
ReportProgress(); //更新进度 PerformPartitionPostinstall(); //开始真正的流程
}

来看PerformPartitionPostinstall()

  void PostinstallRunnerAction::PerformPartitionPostinstall() {
if (install_plan_.download_url.empty()) {
LOG(INFO) << "Skipping post-install during rollback";
return CompletePostinstall(ErrorCode::kSuccess);
} // Skip all the partitions that don't have a post-install step.
while (current_partition_ < install_plan_.partitions.size() &&
!install_plan_.partitions[current_partition_].run_postinstall) { //run_postinstall为false
VLOG() << "Skipping post-install on partition "
<< install_plan_.partitions[current_partition_].name;
current_partition_++;
}
if (current_partition_ == install_plan_.partitions.size())
return CompletePostinstall(ErrorCode::kSuccess);
...................
...................
...................
}

在当前分析中run_postinstall为false,会跳过post-install。之后会直接执行CompletePostinstall(ErrorCode::kSuccess)

  void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
// We only attempt to mark the new slot as active if all the postinstall
// steps succeeded.
if (error_code == ErrorCode::kSuccess &&
!boot_control_->SetActiveBootSlot(install_plan_.target_slot)) { //设置target_slot为active
error_code = ErrorCode::kPostinstallRunnerError;
} ScopedActionCompleter completer(processor_, this);
completer.set_code(error_code); if (error_code != ErrorCode::kSuccess) {
LOG(ERROR) << "Postinstall action failed."; // Undo any changes done to trigger Powerwash.
if (powerwash_scheduled_)
hardware_->CancelPowerwash(); return;
} LOG(INFO) << "All post-install commands succeeded";
if (HasOutputPipe()) { //设置输出的install_plan
SetOutputObject(install_plan_);
}
}

最终将target_slot设置为active在重启之后就会从target_slot开始启动了。

分析到这里就算是对update_engine的核心过程有了个大概的了解,除了对升级的知识点的认识,还体会到了它的架构。不足之处就是还有很多的细节未涉及。

update_engine-FilesystemVerifierAction和PostinstallRunnerAction的更多相关文章

  1. update_engine-DownloadAction(二)

    在update_engine-DownloadAction(一)中对DownloadAction介绍到了DeltaPerformer的Write方法.下面开始介绍Write方法. src/system ...

  2. update_engine-整体结构(三)

    在update_engine-整体结构(二)中分析到了Action,那么我们接着继续分析. 首先来看一下BuildUpdateActons(...)这个方法. src/system/update_en ...

  3. update_engine-整体结构(二)

    在update_engine-整体结构(一)中分析UpdateEngineDaemon::OnInit()的整体情况.下面先分析在该方法中涉及的DaemonStateAndroid和BinderUpd ...

  4. update_engine-DownloadAction(一)

    通过update_engine-整体结构(一),(二),(三)对update_engine整体的运行机制有了一定的认识之后.开始逐个分析重要的Action.先从DownloadAction开始分析. ...

  5. update_engine-整体结构(一)

    update_engine简介 update_engine是A/B升级的核心逻辑.理解了update_engine就理解了在Android系统中A/B升级是如何运行的.它的代码放在源码目录下syste ...

  6. Android init介绍(下)

    上一篇请参考<Android init介绍(上)> 5. AIL 在init启动过程中,系统服务等均是通过解析rc文件来启动,而rc文件则是由Android初始化语言(Android In ...

  7. Android A/B System OTA分析(一)概览【转】

    本文转载自:https://blog.csdn.net/guyongqiangx/article/details/71334889 Android从7.0开始引入新的OTA升级方式,A/B Syste ...

  8. Linux学习:使用 procrank 测量系统内存使用情况

    程序员应该了解一个基本问题:我的程序使用了多少内存?这可能是一个简单的问题,但是对于像Linux这样的虚拟内存操作系统,答案是相当复杂的,因为top和ps给出的数字不能简单相加.进程中两个最常见的内存 ...

随机推荐

  1. vue-resource 和 axios的区别

    vue-resource Vue.js是数据驱动的,这使得我们并不需要直接操作DOM,如果我们不需要使用jQuery的DOM选择器,就没有必要引入jQuery.vue-resource是Vue.js的 ...

  2. .Net Core+Angular6 学习 第一部分(创建web api)

    . 创建.net core web api 1.1 选择一个empty 模式,里面只有简单的2个class 1.2 配置web api 的路由. 1.2.1 打开Startup.cs,首先引用conf ...

  3. 简单gitblit与Jenkins结合,持续集成

    gitblit是当作git服务器,也就是作为私有的代码仓库,用法类似于Github Jenkins 是自动构建工具,帮忙将仓库中的代码更新到服务器上.可以设置为定时自动构建. 详细摸索了我现在公司的用 ...

  4. IP通信基础课堂笔记----第一章(重点)

    七层数据传输:应用层.表示层.会话层.传输层.网络层.数据链路层.物理层 1.物理层:设备----集线器.Hub 两台主机的最大跨度----2500m 在物理层工作----(1)所有设备都处于同一冲突 ...

  5. RabbitMQ集群出现过机器故障,网络异常等故障后,重启无法重新建立集群的终极解决方案

    由于机器掉电,网络故障等原因,RabbitMQ整个集群出现问题.重启RabbitMQ时,发现某些机器始终无法重新加入到集群中,而且还可能出现网络分区. 针对不同情况,可能选择 rabbitmqctl ...

  6. spring cloud 工程构建

    https://blog.csdn.net/zhou199252/article/details/80745151 https://blog.csdn.net/forezp/article/detai ...

  7. maven 常用备忘录

    1.maven 国内的常用中央仓库地址配置: <mirror> <id>alimaven</id> <name>aliyun maven</nam ...

  8. swift4.0 数据转模型

    swift 4.0时代的到来,说明了swift已经趋于稳定了,已经完全可以入坑了. 下面就拿最简单的数据转模型来说说,实战一下. 接口使用:  http://116.211.167.106/api/l ...

  9. 按照不规则多边形shp文件分割底层栅格文件tif,统计不同栅格的属性值

    我想做到,按照voronoi多边形分割地图土地利用类型文件,统计每个多边形内不同地物的种类和数量.-----如图: 我的第一个想法是:首先用上层多边形将下层栅格图切割开来,然后就可以分别统计栅格内的地 ...

  10. gggg

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...