update_engine简介

简介: update_engine简介

update_engine简介


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.

初始化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的启动信息就升级完了


目录
相关文章
|
SQL 关系型数据库 PostgreSQL
|
2月前
|
编译器 API UED
Galacean Engine 1.3 发布
经历了三个月的研发,我们激动地向您介绍全新的 Galacean Engine 1.3 版本 🎉 此次更新不仅包含后处理、Prefab、动画状态机、高级材质等多项新功能,更在稳定性、性能和用户体验上进行了一大波优化。我们还在编辑器中内置了大量的案例模板,方便用户快速学习和预览功能效果。此外,Galacean Engine 官网设计也进行了全面更新,为您带来更棒的浏览体验!
|
6月前
|
存储 关系型数据库 OLTP
X-Engine
阿里云数据库产品事业部自主研发的OLTP数据库存储引擎X-Engine,已经成功应用在阿里集团内部的核心业务系统,包括交易历史库、钉钉历史库等,不仅大幅降低了业务成本,同时作为双十一大促的关键数据库技术,成功承受了数百倍平时流量的冲击。
68 2
|
6月前
|
SQL Java 关系型数据库
jdbc(insert,update,,create,以及各个类详解)
jdbc(insert,update,,create,以及各个类详解)
|
SQL 关系型数据库 MySQL
Mysql统计技巧:ON DUPLICATE KEY UPDATE用法
Mysql统计技巧:ON DUPLICATE KEY UPDATE用法
147 0
|
关系型数据库 MySQL
MySQL:自动维护create_time和update_time字段
通过建表语句设置,让mysql自动维护这两个字段,那么编程的时候也能少写一部分代码
89 0
|
SQL 关系型数据库 MySQL
《零基础》MySQL UPDATE 更新(十四)
法 以下是 UPDATE 命令修改 MySQL 数据表数据的通用 SQL 语法: UPDATE table_name SET field1=new-value1, field2=new-value2
133 0
|
存储 SQL MySQL
MySQL8.0 - 新特性 - CHECK CONSTRAINT
即使MySQL8.0已经GA了,官方仍然在向其中增加新的功能,比如在最新的MySQL8.0.16版本中,增加了一个众望所归的功能:CHECK CONSTRAINT,也就是说可以自动对写入的数据进行约束检查。
3836 0
|
存储 SQL 自然语言处理
MySQL8.0 - 新特性 - Instant Add Column
MySQL8.0开始对一些DDL操作做了大量的优化,例如原子DDL, 快速DDL(只修改元数据),前者解决了长期以来mysql的一大诟病,后者则提升了dba同学的生活品质 官方文档列出了一些可以快速ddl的操作,大体包括: 修改索引类型 Add column (limited) 当一条alt.
5462 0