update_engine-FilesystemVerifierAction和PostinstallRunnerAction
在介绍完了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的更多相关文章
- update_engine-DownloadAction(二)
		在update_engine-DownloadAction(一)中对DownloadAction介绍到了DeltaPerformer的Write方法.下面开始介绍Write方法. src/system ... 
- update_engine-整体结构(三)
		在update_engine-整体结构(二)中分析到了Action,那么我们接着继续分析. 首先来看一下BuildUpdateActons(...)这个方法. src/system/update_en ... 
- update_engine-整体结构(二)
		在update_engine-整体结构(一)中分析UpdateEngineDaemon::OnInit()的整体情况.下面先分析在该方法中涉及的DaemonStateAndroid和BinderUpd ... 
- update_engine-DownloadAction(一)
		通过update_engine-整体结构(一),(二),(三)对update_engine整体的运行机制有了一定的认识之后.开始逐个分析重要的Action.先从DownloadAction开始分析. ... 
- update_engine-整体结构(一)
		update_engine简介 update_engine是A/B升级的核心逻辑.理解了update_engine就理解了在Android系统中A/B升级是如何运行的.它的代码放在源码目录下syste ... 
- Android init介绍(下)
		上一篇请参考<Android init介绍(上)> 5. AIL 在init启动过程中,系统服务等均是通过解析rc文件来启动,而rc文件则是由Android初始化语言(Android In ... 
- Android A/B System OTA分析(一)概览【转】
		本文转载自:https://blog.csdn.net/guyongqiangx/article/details/71334889 Android从7.0开始引入新的OTA升级方式,A/B Syste ... 
- Linux学习:使用 procrank 测量系统内存使用情况
		程序员应该了解一个基本问题:我的程序使用了多少内存?这可能是一个简单的问题,但是对于像Linux这样的虚拟内存操作系统,答案是相当复杂的,因为top和ps给出的数字不能简单相加.进程中两个最常见的内存 ... 
随机推荐
- sklearn-adaboost
			sklearn中实现了adaboost分类和回归,即AdaBoostClassifier和AdaBoostRegressor, AdaBoostClassifier 实现了两种方法,即 SAMME 和 ... 
- js /Date(1550273700000)/ 格式转换
			self.FormatJsonDate = function (jsonStr) { var tmp = ""; if (jsonStr == null || jsonStr == ... 
- PCA降维—降维后样本维度大小
			之前对PCA的原理挺熟悉,但一直没有真正使用过.最近在做降维,实际用到了PCA方法对样本特征进行降维,但在实践过程中遇到了降维后样本维数大小限制问题. MATLAB自带PCA函数:[coeff, sc ... 
- php的array数组 -------方法foreach循环时候,利用数组里值的引用地址(& )从而改变数组里的值
			/* * 把每个数组值后面都加个SQL然后返回数组 * foreach循环时候,直接用引用(&)的方式就能改变之前的数组 */public function array_foreach(){ ... 
- task打印执行结果
			使用debug输出task执行的register: - name: check extract session # script: /app/ansiblecfg/XXX/roles/test/tas ... 
- Openflow协议详解
			http://www.h3c.com/cn/d_201811/1131080_30005_0.htm# 1 OpenFlow背景 转发和控制分离是SDN网络的本质特点之一 .在SDN网络架构中,控制平 ... 
- [Hive]新增字段(column)后,旧分区无法更新数据问题
			问题描述: 实际应用中,常常存在修改数据表结构的需求,比如:增加一个新字段. 如果使用如下语句新增列,可以成功添加列col1.但如果数据表tb已经有旧的分区(例如:dt=20190101),则该旧分区 ... 
- arch xfce快捷键
			<Alt>F1:xfce4-popup-applicationsmenu 打开右键菜单 <Alt>F2:xfrun4 打开应用程序运行窗口,同<Alt>F3差不多 ... 
- 浅谈jquery事件命名空间
			什么是jquery的事件命名空间? 先看如下简单代码: $("#btn").on("click.name1.name2",function(){ console ... 
- React中使用echarts
			1.安装相关的依赖: cnpm i react-for-echarts -S cnpm i echarts -S 2.使用方法: 页面引入: import ReactEcharts from 'ech ... 
