update_engine简介

发布时间 2023-04-18 17:13:11作者: xiaowang_lj

AB升级(谷歌官网叫 无缝更新)是自android7.0开始新增的一种android设备升级方式,这种方式对设备存储要求高

简而言之:系统同时存在两套system分区,一套处于休眠状态不可使用,一套处于使用状态,两者通过slot的概念来做区分,在设备启动引导阶段通过特殊标记位确定启动哪个system,当有可用升级版本时候,客户端将升级包下载下来,或者将下载地址请求下来,然后通过update_engine将当前没有在使用的一套system升级到最新版本,然后修改启动标志位,在下次启动的时候,就进入升级到最新版本的那套system系统,这样做的好处就是省去了recovery升级过程的耗时,规避了recovery升级过程中发生意外中断导致设备无法开机的风险。现将其主要逻辑做简单整理归纳

AB升级的主要实现过程在update_engine中,主要代码集中在/system/update_engine路径下

主要分为两部分:

1.update_engine的初始化过程

2.java层调用升级接口执行升级过程

1.update_engine的初始化过程

首先查看目录下的Android.bp

cc_binary {
    name: "update_engine",
    defaults: [
        "ue_defaults",
        "libupdate_engine_android_exports",
    ],

    static_libs: ["libupdate_engine_android"],
    required: [
        "cacerts_google",
        "otacerts",
    ],

    srcs: ["main.cc", "aosp/metrics_reporter_android.cc"],
    init_rc: ["update_engine.rc"],
}

update_engine.rc内容如下:update_engine服务在init进程中启动,启动后运行main.cc中的main函数:

update_engine 默认是不启动的,当设置了属性ro.boot.slot_suffix时,就enable update_engine了,也就是启动Update_eingine了。如果使用AB分区的模式的,这个属性是一定要设置的。此属性一般也是bootloader启动时,从misc分区中读取了哪个Slot是当前可以启动的,然后设置此属性。

service update_engine /system/bin/update_engine --logtostderr --logtofile --foreground
    class late_start
    user root
    group root system wakelock inet cache media_rw
    writepid /dev/cpuset/system-background/tasks /dev/blkio/background/tasks
    disabled

on property:ro.boot.slot_suffix=*
    enable update_engine

main.cc   在Main.cc的main函数里主要做了两个事,一个是启动logging, 第二个就是运行update_engine_daemon。

int main(int argc, char** argv) {
  DEFINE_bool(logtofile, false, "Write logs to a file in log_dir.");
  DEFINE_bool(logtostderr,
              false,
              "Write logs to stderr instead of to a file in log_dir.");
  DEFINE_bool(foreground, false, "Don't daemon()ize; run in foreground.");
  终端初始化
  chromeos_update_engine::Terminator::Init();
  brillo::FlagHelper::Init(argc, argv, "A/B Update Engine");

  // We have two logging flags "--logtostderr" and "--logtofile"; and the logic
  // to choose the logging destination is:
  // 1. --logtostderr --logtofile -> logs to both
  // 2. --logtostderr             -> logs to system debug
  // 3. --logtofile or no flags   -> logs to file
  bool log_to_system = FLAGS_logtostderr;
  bool log_to_file = FLAGS_logtofile || !FLAGS_logtostderr;
  设置log 在update_engine.rc里,启动项里有带–logtostderr --logtofile,所以当前log_to_system 和 log_to_file 都是true。 chromeos_update_engine::SetupLogging(log_to_system, log_to_file);
if (!FLAGS_foreground) PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed"; LOG(INFO) << "A/B Update Engine starting"; // xz-embedded requires to initialize its CRC-32 table once on startup. xz_crc32_init(); // Ensure that all written files have safe permissions. // This is a mask, so we _block_ all permissions for the group owner and other // users but allow all permissions for the user owner. We allow execution // for the owner so we can create directories. // Done _after_ log file creation. umask(S_IRWXG | S_IRWXO);   初始化并实例化 更新引擎 auto daemon = chromeos_update_engine::DaemonBase::CreateInstance(); int exit_code = daemon->Run(); chromeos_update_engine::Subprocess::Get().FlushBufferedLogsAtExit(); LOG(INFO) << "A/B Update Engine terminating with exit code " << exit_code; return exit_code; }

启动logging

void SetupLogging(bool log_to_system, bool log_to_file) {
  logging::LoggingSettings log_settings;
  log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
  log_settings.logging_dest = static_cast<logging::LoggingDestination>(
      (log_to_system ? logging::LOG_TO_SYSTEM_DEBUG_LOG : 0) |
      (log_to_file ? logging::LOG_TO_FILE : 0));
  log_settings.log_file = nullptr;

  string log_file;
  if (log_to_file) {
    log_file = SetupLogFile(kSystemLogsRoot);
    //先删除此目录下的log文件
    log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
#if BASE_VER < 780000
    log_settings.log_file = log_file.c_str();
#else
    log_settings.log_file_path = log_file.c_str();
#endif
  }
  //调用libchrome里的logging
  logging::InitLogging(log_settings);
}

logging::InitLogging(log_settings) 调用的并不是Android 自带的logger机制里的方法,而是libchrome里的logging。因为本身update_engine 这个工程就是最早用于chrome里的,后面才移植到Android里来升级AB分区的。这里说明下保存log的文件路径kSystemLogsRoot为/data/misc/update_engine_log/下。
external\libchrome\base\logging.h     external\libchrome\base\logging.cc

inline bool InitLogging(const LoggingSettings& settings) {
  return BaseInitLoggingImpl(settings);
}
bool BaseInitLoggingImpl(const LoggingSettings& settings) {
#if defined(OS_NACL)
  // Can log only to the system debug log.
  CHECK_EQ(settings.logging_dest & ~LOG_TO_SYSTEM_DEBUG_LOG, 0);
#endif
  if (base::CommandLine::InitializedForCurrentProcess()) {
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    // Don't bother initializing |g_vlog_info| unless we use one of the
    // vlog switches.
    if (command_line->HasSwitch(switches::kV) ||
        command_line->HasSwitch(switches::kVModule)) {
      // NOTE: If |g_vlog_info| has already been initialized, it might be in use
      // by another thread. Don't delete the old VLogInfo, just create a second
      // one. We keep track of both to avoid memory leak warnings.
      CHECK(!g_vlog_info_prev);
      g_vlog_info_prev = g_vlog_info;

      g_vlog_info =
          new VlogInfo(command_line->GetSwitchValueASCII(switches::kV),
                       command_line->GetSwitchValueASCII(switches::kVModule),
                       &g_min_log_level);
    }
  }

  g_logging_destination = settings.logging_dest;

  // ignore file options unless logging to file is set.
  if ((g_logging_destination & LOG_TO_FILE) == 0)
    return true;

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  LoggingLock::Init(settings.lock_log, settings.log_file);
  LoggingLock logging_lock;
#endif

  // Calling InitLogging twice or after some log call has already opened the
  // default log file will re-initialize to the new options.
  CloseLogFileUnlocked();

  if (!g_log_file_name)
    g_log_file_name = new PathString();
  *g_log_file_name = settings.log_file;
  if (settings.delete_old == DELETE_OLD_LOG_FILE)
    DeleteFilePath(*g_log_file_name);
  //初始化log文件句柄
  return InitializeLogFileHandle();
}

在InitLogging的函数的系列调用 中,最后通过fopen的方式打开g_log_file_name, 后面再通过追加写入的方式,去保存update的log。

运行update_engine_daemon

DaemonBase继承自 brillo::Daemon (external\libbrillo\brillo\daemons\daemon.cc)会运行到DaemonChromeOS::OnInit中:

这里需要注意Android.bp文件中 引用的源文件,因为实现DaemonBase有两个地方,而Android.bp 使用的是其中一个

cc_library_static {
    name: "libupdate_engine_android",
    defaults: [
        "ue_defaults",
        "libupdate_engine_android_exports",
    ],

    // TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
    // out of the DBus interface.
    include_dirs: ["external/cros/system_api/dbus"],

    aidl: {
        local_include_dirs: ["binder_bindings"],
        export_aidl_headers: true,
    },

    srcs: [
        ":libupdate_engine_aidl",
        "common/system_state.cc",
        "aosp/apex_handler_android.cc",
        "aosp/binder_service_android.cc",
        "aosp/binder_service_stable_android.cc",
        "aosp/daemon_android.cc",   重点关注
        "aosp/daemon_state_android.cc",
        "aosp/hardware_android.cc",
        "aosp/logging_android.cc",
        "aosp/network_selector_android.cc",
        "aosp/update_attempter_android.cc",
        "certificate_checker.cc",
        "download_action.cc",
        "libcurl_http_fetcher.cc",
        "metrics_utils.cc",
        "update_boot_flags_action.cc",
        "update_status_utils.cc",
    ],
}

class DaemonBase : public brillo::Daemon {
 public:
  DaemonBase() = default;
  virtual ~DaemonBase() = default;

  // Creates an instance of the daemon.
  static std::unique_ptr<DaemonBase> CreateInstance();

 private:
  DISALLOW_COPY_AND_ASSIGN(DaemonBase);
};
unique_ptr<DaemonBase> DaemonBase::CreateInstance() {
  LOG(INFO) << "daemon_android.cc:DaemonBase::CreateInstance()";
  return std::make_unique<DaemonAndroid>();
}
OnInit方法里 初始化了update_engine的全局状态 并创建了DBUS
int DaemonAndroid::OnInit() {
  // Register the |subprocess_| singleton with this Daemon as the signal
  // handler.
  subprocess_.Init(this);
  //父类初始化
  int exit_code = brillo::Daemon::OnInit();
  if (exit_code != EX_OK)
    return exit_code;
  //Binder初始化
  android::BinderWrapper::Create();
  binder_watcher_.Init();
  //DaemonStateAndroid的初始化
  DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid();
  daemon_state_.reset(daemon_state_android);
  LOG_IF(ERROR, !daemon_state_android->Initialize())
      << "Failed to initialize system state.";

  auto binder_wrapper = android::BinderWrapper::Get();
  //初始化BinderUpdateEngineAndroidService
  // Create the Binder Service.
  binder_service_ = new BinderUpdateEngineAndroidService{
      daemon_state_android->service_delegate()};
  if (!binder_wrapper->RegisterService(binder_service_->ServiceName(),
                                       binder_service_)) {
    LOG(ERROR) << "Failed to register binder service.";
  }
  daemon_state_->AddObserver(binder_service_.get());

  // Create the stable binder service.
  stable_binder_service_ = new BinderUpdateEngineAndroidStableService{
      daemon_state_android->service_delegate()};
  if (!binder_wrapper->RegisterService(stable_binder_service_->ServiceName(),
                                       stable_binder_service_)) {
    LOG(ERROR) << "Failed to register stable binder service.";
  }
  daemon_state_->AddObserver(stable_binder_service_.get());
  //启动updater
  daemon_state_->StartUpdater();
  return EX_OK;
}

updateEngineDaemon的onInit()里大致做了三件事情:1. 初始化DaemonStateAndroid    2. 初始化BinderUpdateEngineAndroidService 和 BinderUpdateEngineAndroidStableService     3.启动startUpdater.

1. 初始化DaemonStateAndroid

bool DaemonStateAndroid::Initialize() {
  //初始化boot_control boot_control_
= boot_control::CreateBootControl(); if (!boot_control_) { LOG(WARNING) << "Unable to create BootControl instance, using stub " << "instead. All update attempts will fail."; boot_control_.reset(new BootControlStub()); } //创建硬件相关接口,主要以属性操作为主 hardware_ = hardware::CreateHardware(); if (!hardware_) { LOG(ERROR) << "Error initializing the HardwareInterface."; return false; } LOG_IF(INFO, !hardware_->IsNormalBootMode()) << "Booted in dev mode."; LOG_IF(INFO, !hardware_->IsOfficialBuild()) << "Booted non-official build."; //初始化存取key的存储接口 // Initialize prefs. base::FilePath non_volatile_path; if (!hardware_->GetNonVolatileDirectory(&non_volatile_path)) { prefs_.reset(new MemoryPrefs()); LOG(WARNING) << "Could not get a non-volatile directory, fall back to memory prefs"; } else { Prefs* prefs = new Prefs(); prefs_.reset(prefs); if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) { LOG(ERROR) << "Failed to initialize preferences."; return false; } } //校验类的初始化 // The CertificateChecker singleton is used by the update attempter. certificate_checker_.reset( new CertificateChecker(prefs_.get(), &openssl_wrapper_)); certificate_checker_->Init(); //update_attempter的实始化 // Initialize the UpdateAttempter before the UpdateManager. update_attempter_.reset(new UpdateAttempterAndroid(this, prefs_.get(), boot_control_.get(), hardware_.get(), CreateApexHandler())); return true; }

DaemonStateAndroid的初始化主要做了如下事件

boot_control的初始化,为后面切换A/B slot做准备。
hardware的初始化,为了后续获取版本信息,OOB的状态,编译时间等信息
prefs的初始化,主要为了从文件中读写key
certificateChecker的初始化,主要为后面验证升级包
updateAttempterAndroid的初始化, 基于前面获取的boot_control, perfs, hardware的实类初始化

初始化BinderUpdateEngineAndroidService

BinderUpdateEngineAndroidService::BinderUpdateEngineAndroidService(
    ServiceDelegateAndroidInterface* service_delegate)
    : service_delegate_(service_delegate) {}

从上面代码看,初始化BinderUpdateEngineAndroidService时,会把DaemonStateAndroid 的实例指针传给BinderUpdateEngineAndroidService。然后DaemonStateAndroid 会调用 AddObserver,将这个加入到service_observers_的列表中,后续通过调用service_observers()来获取这个列表,从而获取这个函数指针来调用其函数。

启动startUpdater

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;
}
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); UpdatePrefsAndReportUpdateMetricsOnReboot(); #ifdef _UE_SIDELOAD LOG(INFO)
<< "Skip ScheduleCleanupPreviousUpdate in sideload because " << "ApplyPayload will call it later."; #else ScheduleCleanupPreviousUpdate(); #endif } }
bool UpdateAttempterAndroid::UpdateCompletedOnThisBoot() {
  // In case of an update_engine restart without a reboot, we stored the boot_id
  // when the update was completed by setting a pref, so we can check whether
  // the last update was on this boot or a previous one.
  string boot_id;
  TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));

  string update_completed_on_boot_id;
//判断配置升级完成的bootid 与当前的bootId,是不是同一个
return (prefs_->Exists(kPrefsUpdateCompletedOnBootId) && prefs_->GetString(kPrefsUpdateCompletedOnBootId, &update_completed_on_boot_id) && update_completed_on_boot_id == boot_id); }
void UpdateAttempterAndroid::SetStatusAndNotify(UpdateStatus status) {
  status_ = status;
  size_t payload_size =
      install_plan_.payloads.empty() ? 0 : install_plan_.payloads[0].size;
  UpdateEngineStatus status_to_send = {.status = status_,
                                       .progress = download_progress_,
                                       .new_size_bytes = payload_size};

  for (auto observer : daemon_state_->service_observers()) {
    observer->SendStatusUpdate(status_to_send);
  }
  last_notify_time_ = TimeTicks::Now();
}

上面注释与逻辑中可以看到,我们需要先去看当前的bootid 是否需要等待重启切换slot, 如果需要,则通知上面的调用方去上报状态。不需要重启,也需要上报空闲状态,同时更新perfs配置。下面来看如何更新配置:

void UpdateAttempterAndroid::UpdatePrefsAndReportUpdateMetricsOnReboot() {
  string current_boot_id;
  TEST_AND_RETURN(utils::GetBootId(&current_boot_id));
  // Example: [ro.build.version.incremental]: [4292972]
  string current_version =
      android::base::GetProperty("ro.build.version.incremental", "");
  TEST_AND_RETURN(!current_version.empty());
  const auto current_slot = boot_control_->GetCurrentSlot();

  // If there's no record of previous version (e.g. due to a data wipe), we
  // save the info of current boot and skip the metrics report.
  if (!prefs_->Exists(kPrefsPreviousVersion)) {
    prefs_->SetString(kPrefsBootId, current_boot_id);
    prefs_->SetString(kPrefsPreviousVersion, current_version);
    prefs_->SetInt64(std::string{kPrefsPreviousSlot},
                     boot_control_->GetCurrentSlot());
    ClearMetricsPrefs();
    return;
  }
  int64_t previous_slot = -1;
  prefs_->GetInt64(kPrefsPreviousSlot, &previous_slot);
  string previous_version;
  // update_engine restarted under the same build and same slot.
  // TODO(xunchang) identify and report rollback by checking UpdateMarker.
  if (prefs_->GetString(kPrefsPreviousVersion, &previous_version) &&
      previous_version == current_version && previous_slot == current_slot) {
    string last_boot_id;
    bool is_reboot = prefs_->Exists(kPrefsBootId) &&
                     (prefs_->GetString(kPrefsBootId, &last_boot_id) &&
                      last_boot_id != current_boot_id);
    // Increment the reboot number if |kPrefsNumReboots| exists. That pref is
    // set when we start a new update.
    if (is_reboot && prefs_->Exists(kPrefsNumReboots)) {
      prefs_->SetString(kPrefsBootId, current_boot_id);
      int64_t reboot_count =
          metrics_utils::GetPersistedValue(kPrefsNumReboots, prefs_);
      metrics_utils::SetNumReboots(reboot_count + 1, prefs_);
    }
    return;
  }

到这里update_engine_daemon的启动信息就升级完了