A/B system<二>

2018/08/07 a/bsystem

A/B system <二> update_engine_client

update_engine 复杂的代码结构让人有点无从下手,本篇简单介绍 update_engine_client 原理,通过 这个 debug 工具来了解 update_engine 的原理

升级执行命令

生成的 update.zip ,使用adb push 到 /data/ota_package/ 下,同时把 update.zip 中 payload_properties.txt 复制出来传给headers参数,执行以下命令

update_engine_client --update --follow --payload=file:///data/ota_package/update.zip --offset=7769 --size=580694079 --headers="FILE_HASH=ZSgofjc05i4zZyA1pjypMF5dIS+vKvuRIXkf6mTkKoo=
FILE_SIZE=580694079
METADATA_HASH=m++g6wd+wyMMZDVQgdmGIC7oqZg9Bpd2xt6TaL5mqr8=
METADATA_SIZE=87192
"

流程分析

update_engine_client 侧

system/update_engine/ 下有 update_engine_client_android.cc update_engine_client.cc两个文件,update_engine_client_android.cc是我们Android设备要使用的文件,另一个应该是 chrome 用的。

看下 main 函数,很简单

int main(int argc, char** argv) {
  chromeos_update_engine::internal::UpdateEngineClientAndroid client(
      argc, argv);
  return client.Run();
}

Run 方法调用 external/libbrillo/brillo/daemons/daemon.cc 下的 Run,OnInit 是一个虚函数,所以又会调用子类的函数

int Daemon::Run() {
  int exit_code = OnInit();//虚函数
  if (exit_code != EX_OK)
    return exit_code;

  message_loop_.Run();

  OnShutdown(&exit_code_);

  // base::RunLoop::QuitClosure() causes the message loop to quit
  // immediately, even if pending tasks are still queued.
  // Run a secondary loop to make sure all those are processed.
  // This becomes important when working with D-Bus since dbus::Bus does
  // a bunch of clean-up tasks asynchronously when shutting down.
  while (message_loop_.RunOnce(false /* may_block */)) {}

  return exit_code_;
}

看下子类的实现 UpdateEngineClientAndroid::OnInit,省略了部分代码,前面都是一些处理命令行,初始话 logger 日志。来看下重点的地方,获取 android.os.UpdateEngineService 服务,这个在哪里注册的呢?其实是在 update_engine 侧进行注册的,咱们后面再说。还记得前面的命令行吗?是有 follow 和 update 参数的。follow 里面注册了回调函数,用于 update_engine 调用。applyPayload 函数是触发整个升级流程的关键函数了,实现是在 update_engine 端实现的。最后注册一个服务用于检测 server(update_client)端是否正常。到这里 update_engien_client 就分析完了,其实就是这么简单。下面是要根据 update_engine_client 来分析 server(update_engine) 端的做的工作了

  android::status_t status = android::getService(
      android::String16("android.os.UpdateEngineService"), &service_);
.......
  if (FLAGS_follow) {
    // Register a callback object with the service.
    callback_ = new UECallback(this);
    bool bound;
    if (!service_->bind(callback_, &bound).isOk() || !bound) {
      LOG(ERROR) << "Failed to bind() the UpdateEngine daemon.";
      return 1;
    }
    keep_running = true;
  }

  if (FLAGS_update) {
    std::vector<std::string> headers = base::SplitString(
        FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
    std::vector<android::String16> and_headers;
    for (const auto& header : headers) {
      and_headers.push_back(android::String16{header.data(), header.size()});
    }
    Status status = service_->applyPayload(
        android::String16{FLAGS_payload.data(), FLAGS_payload.size()},
        FLAGS_offset,
        FLAGS_size,
        and_headers);
    if (!status.isOk())
      return ExitWhenIdle(status);
  }

  if (!keep_running)
    return ExitWhenIdle(EX_OK);

  // When following updates status changes, exit if the update_engine daemon
  // dies.
  android::BinderWrapper::Create();
  android::BinderWrapper::Get()->RegisterForDeathNotifications(
      android::os::IUpdateEngine::asBinder(service_),
      base::Bind(&UpdateEngineClientAndroid::UpdateEngineServiceDied,
                 base::Unretained(this)));

update_engine 侧

注册服务,可以看到 BinderUpdateEngineAndroidService 是继承自 android::brillo::BnUpdateEngine ,而 BnUpdateEngine 刚好是 binder_bindings/android/os/IUpdateEngine.aidl,通过 aidl 文件,就实现了跨进程的多线程通信。

UpdateEngineDaemon::OnInit(){

  binder_service_ = new BinderUpdateEngineAndroidService{
      daemon_state_android->service_delegate()};
......
  auto binder_wrapper = android::BinderWrapper::Get();
  if (!binder_wrapper->RegisterService(binder_service_->ServiceName(),
                                       binder_service_)) {
    LOG(ERROR) << "Failed to register binder service.";
  }
......
}

再来看一下 StartUpdater 函数,看名称有点像和更新相关的功能了,代码在 daemon_state_android.cc

bool DaemonStateAndroid::StartUpdater() {
  // The DaemonState in Android is a passive daemon. It will only start applying
  // an update when instructed to do so from the exposed binder API.
  update_attempter_->Init();
  return true;
}

最终调用 UpdateCompletedOnThisBoot 函数检测是否升级成功了,如果升级成功,则发出重启命令,若未升级则将系统更新状态设置为空闲状态。代码路径在 update_attempter_android.cc

void UpdateAttempterAndroid::Init() {
  // In case of update_engine restart without a reboot we need to restore the
  // reboot needed state.
  if (UpdateCompletedOnThisBoot())
    SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
  else
    SetStatusAndNotify(UpdateStatus::IDLE);
}

到这里 update_engine 就分析完了,怎么觉得太简单了一点,/system/update_engine 这么多的代码啊,其实别忘了我们前面分析的 applypayload。

执行升级

首先调用 binder_service_android.cc 里面的 applyPayload ,其实 binder_service_android.cc 不仅仅提供了 applypayload,像之前的 reset,suspend 等函数都是在这里面调用的。

bool UpdateAttempterAndroid::ApplyPayload(
    const string& payload_url,
    int64_t payload_offset,
    int64_t payload_size,
    const vector<string>& key_value_pair_headers,
    brillo::ErrorPtr* error) {
    .......
  BuildUpdateActions(payload_url);
  // Setup extra headers.
  HttpFetcher* fetcher = download_action_->http_fetcher();
  if (!headers[kPayloadPropertyAuthorization].empty())
    fetcher->SetHeader("Authorization", headers[kPayloadPropertyAuthorization]);
  if (!headers[kPayloadPropertyUserAgent].empty())
    fetcher->SetHeader("User-Agent", headers[kPayloadPropertyUserAgent]);

  SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
  ongoing_update_ = true;

  // Just in case we didn't update boot flags yet, make sure they're updated
  // before any update processing starts. This will start the update process.
  UpdateBootFlags();
  LOG(INFO) << "chenan UpdateBootFlags end";
  return true;
  }

void DownloadAction::PerformAction() {
  LOG(INFO) << "chenan DownloadAction::PerformAction"
  http_fetcher_->set_delegate(this);

  // Get the InstallPlan and read it
  CHECK(HasInputObject());
  install_plan_ = GetInputObject();
  install_plan_.Dump();

  bytes_received_ = 0;
  bytes_total_ = 0;
  for (const auto& payload : install_plan_.payloads)
    bytes_total_ += payload.size;

  if (install_plan_.is_resume) {
    int64_t payload_index = 0;
    if (prefs_->GetInt64(kPrefsUpdateStatePayloadIndex, &payload_index) &&
        static_cast<size_t>(payload_index) < install_plan_.payloads.size()) {
      // Save the index for the resume payload before downloading any previous
      // payload, otherwise it will be overwritten.
      resume_payload_index_ = payload_index;
      for (int i = 0; i < payload_index; i++)
        install_plan_.payloads[i].already_applied = true;
    }
  }
  // TODO(senj): check that install plan has at least one payload.
  if (!payload_)
    payload_ = &install_plan_.payloads[0];

  LOG(INFO) << "Marking new slot as unbootable";
  if (!boot_control_->MarkSlotUnbootable(install_plan_.target_slot)) {
    LOG(WARNING) << "Unable to mark new slot "
                 << BootControlInterface::SlotName(install_plan_.target_slot)
                 << ". Proceeding with the update anyway.";
  }

  StartDownloading();
}

小结:

  • update_engine_client 调用关系 update_engine_client_android.cc (applyPayload)-> binder_service_android.cc -> update_attempter_android.cc (UpdateAttempterAndroid::ApplyPayload)
  • 服务注册 main.cc (update_engine_daemon.Run();)-> daemon.cc (UpdateEngineDaemon::OnInit)->

Search

    Table of Contents