zoukankan      html  css  js  c++  java
  • OTA升级详解(三)

    君子知夫不全不粹之不足以为美也, 

    故诵数以贯之,

    思索以通之,

    为其人以处之,

    除其害者以持养之;

              出自荀子《劝学篇》


    终于OTA的升级过程的详解来了,之前的两篇文章OTA升级详解(一)OTA升级详解(二)主要是铺垫,

    OTA升级的一些基础知识,那这边文章就开始揭开OTA-recovery模式升级过程的神秘面纱,需要说明的是

    以下重点梳理了本人认为的关键、核心的流程,其他如ui部分、签名校验部分我并未花笔墨去描述,主要

    还是讲升级的核心,其他都是枝枝叶叶。Android 10 recovery源码分析,代码来源路径:

    https://www.androidos.net.cn/android/10.0.0_r6/xref

    本文所讲的流程代码路径为:bootable/recovery/

    首先从文件层面说下升级功能的调用流程,说明如下:

    recovery-main.cpp      升级的主入口

    recovery.cpp                开始recovery升级的处理流程

    install/install.cpp         执行升级的处理流程(调用updater)

    updater/updater.cpp  完成升级的核心流程

    1 主入口代码为:recovery-main.cpp,main入口

    1.1 日志相关的工作准备

     1 // We don't have logcat yet under recovery; so we'll print error on screen and log to stdout
     2 // (which is redirected to recovery.log) as we used to do.
     3 android::base::InitLogging(argv, &UiLogger);
     4 
     5 // Take last pmsg contents and rewrite it to the current pmsg session.
     6 static constexpr const char filter[] = "recovery/";
     7 // Do we need to rotate?
     8 bool do_rotate = false;
     9 
    10 __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate);
    11 // Take action to refresh pmsg contents
    12 __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate);
    13 
    14 time_t start = time(nullptr);
    15 
    16 // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger
    17 // instances with different timestamps.
    18 redirect_stdio(Paths::Get().temporary_log_file().c_str());

    1.2 load_volume_table(); 加载系统分区信息,注意这里并明白挂载分区

    .mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0 

    mount_point -- 挂载点    fs_type -- 分区类型 

    blk_device     -- 设备块名 length  -- 分区大小

    1.3 挂载/cache分区,我们的升级命令都放在这个分区下

    1 has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;

    1.4 获取升级的参数并写BCB块信息

    std::vector<std::string> args = get_args(argc, argv);
    
    if (!update_bootloader_message(options, &err)) {
        LOG(ERROR) << "Failed to set BCB message: " << err;
    }

    a、读取misc分区分区,并将recovery模式升级的标记写到misc分区中,这样做的目的是断电续升,

    升级中掉电之后,如果下次开机重启,在bootloader中会读取此标记,并重新进入到recovery模式中

    update_bootloader_message函数完成此功能。

    b、从/cache/recovery/command 中读取升级参数,这里recovery启动进程是未带入参数时,command

    文件的接口其实有很详细的解释

     * The arguments which may be supplied in the recovery.command file:
     *   --update_package=path - verify install an OTA package file
     *   --wipe_data - erase user data (and cache), then reboot
     *   --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
     *       data (and cache), then reboot
     *   --wipe_cache - wipe cache (but not user data), then reboot
     *   --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).
     *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
     *   --just_exit - do nothing; exit and reboot

    1.5 加载recovery_ui_ext.so,完成升级中与屏幕信息的显示,升级进度,升级结果等。这里就不多说了。

    static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
      // Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have
      // handed out pointers to code or static [or thread-local] data and doesn't collect them all back
      // in on dlclose).
      void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);
    
      using MakeDeviceType = decltype(&make_device);
      MakeDeviceType make_device_func = nullptr;
      if (librecovery_ui_ext == nullptr) {
        printf("Failed to dlopen %s: %s
    ", kDefaultLibRecoveryUIExt, dlerror());
      } else {
        reinterpret_cast<void*&>(make_device_func) = dlsym(librecovery_ui_ext, "make_device");
        if (make_device_func == nullptr) {
          printf("Failed to dlsym make_device: %s
    ", dlerror());
        }
      }

    1.6 非fastboot模式升级就开始了recovery模式升级,start_recovery

    ret = fastboot ? StartFastboot(device, args) : start_recovery(device, args);

    2 进入 recovery.cpp 

    2.1 参数解析,这些参数其实就是来源于/cache/recovery/command, 上面已经通过get_arg,

    读取到了args中

    2.2 界面的各种ui信息显示,点事电量的检查等待辅助动作。

    2.3 函数名为安装升级包,其实还未真正开始进行升级包的安装

    1 status = install_package(update_package, should_wipe_cache, true, retry_count, ui);

    2.4 安装结束之后由finish_recovery()完成收尾工作,保存日志、清除BCB中的标记,设备重启。

     1 static void finish_recovery() {
     2   std::string locale = ui->GetLocale();
     3   // Save the locale to cache, so if recovery is next started up without a '--locale' argument
     4   // (e.g., directly from the bootloader) it will use the last-known locale.
     5   if (!locale.empty() && has_cache) {
     6     LOG(INFO) << "Saving locale "" << locale << """;
     7     if (ensure_path_mounted(LOCALE_FILE) != 0) {
     8       LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
     9     } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
    10       PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
    11     }
    12   }
    13 
    14   copy_logs(save_current_log, has_cache, sehandle);
    15 
    16   // Reset to normal system boot so recovery won't cycle indefinitely.
    17   std::string err;
    18   if (!clear_bootloader_message(&err)) {
    19     LOG(ERROR) << "Failed to clear BCB message: " << err;
    20   }
    21 
    22   // Remove the command file, so recovery won't repeat indefinitely.
    23   if (has_cache) {
    24     if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
    25       LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
    26     }
    27     ensure_path_unmounted(CACHE_ROOT);
    28   }
    29 
    30   sync();  // For good measure.
    31 }

    3 install/install.cpp

    3.1 install.cpp其实就进入了安装升级包的准备动作,刚上的install_package,是假的,这里才是

    really_install_package

    1 really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
    2                                     retry_count, &max_temperature, ui);

    3.2 really_install_package 关键地方已加注释

     1 static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
     2                                   std::vector<std::string>* log_buffer, int retry_count,
     3                                   int* max_temperature, RecoveryUI* ui) {
     4   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
     5   ui->Print("Finding update package...
    ");
     6   // Give verification half the progress bar...
     7   ui->SetProgressType(RecoveryUI::DETERMINATE);
     8   ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
     9   LOG(INFO) << "Update location: " << path;
    10 
    11   // Map the update package into memory.
    12   ui->Print("Opening update package...
    ");
    13 
    14   if (needs_mount) {
    15     if (path[0] == '@') {
    16       ensure_path_mounted(path.substr(1));
    17     } else {
    18       ensure_path_mounted(path);
    19     }
    20   }
    21 
    22   /* 将zip映射到内存中 */
    23   auto package = Package::CreateMemoryPackage(
    24       path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
    25   if (!package) {
    26     log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
    27     return INSTALL_CORRUPT;
    28   }
    29 
    30   // Verify package.进行zip包进行签名校验
    31   if (!verify_package(package.get(), ui)) {
    32     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
    33     return INSTALL_CORRUPT;
    34   }
    35 
    36   // Try to open the package.打开zip包
    37   ZipArchiveHandle zip = package->GetZipArchiveHandle();
    38   if (!zip) {
    39     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
    40     return INSTALL_CORRUPT;
    41   }
    42 
    43   // Additionally verify the compatibility of the package if it's a fresh install.
    44   if (retry_count == 0 && !verify_package_compatibility(zip)) {
    45     log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
    46     return INSTALL_CORRUPT;
    47   }
    48 
    49   // Verify and install the contents of the package.
    50   ui->Print("Installing update...
    ");
    51   if (retry_count > 0) {
    52     ui->Print("Retry attempt: %d
    ", retry_count);
    53   }
    54   ui->SetEnableReboot(false);
    55   int result =
    56       /* 执行升级updater进程进行升级 */
    57       try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
    58   ui->SetEnableReboot(true);
    59   ui->Print("
    ");
    60 
    61   return result;
    62 }

    3.3 try_update_binary

    从升级包中读取元数据信息

    1 ReadMetadataFromPackage(zip, &metadata)

    3.4 从升级包中读取updater进程

     1 int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
     2                              int status_fd, std::vector<std::string>* cmd) {
     3   CHECK(cmd != nullptr);
     4 
     5   // In non-A/B updates we extract the update binary from the package.
     6   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
     7   ZipString binary_name(UPDATE_BINARY_NAME);
     8   ZipEntry binary_entry;
     9   if (FindEntry(zip, binary_name, &binary_entry) != 0) {
    10     LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
    11     return INSTALL_CORRUPT;
    12   }
    13 
    14   const std::string binary_path = Paths::Get().temporary_update_binary();
    15   unlink(binary_path.c_str());
    16   android::base::unique_fd fd(
    17       open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
    18   if (fd == -1) {
    19     PLOG(ERROR) << "Failed to create " << binary_path;
    20     return INSTALL_ERROR;
    21   }
    22 
    23   int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
    24   if (error != 0) {
    25     LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
    26     return INSTALL_ERROR;
    27   }
    28 
    29   // When executing the update binary contained in the package, the arguments passed are:
    30   //   - the version number for this interface
    31   //   - an FD to which the program can write in order to update the progress bar.
    32   //   - the name of the package zip file.
    33   //   - an optional argument "retry" if this update is a retry of a failed update attempt.
    34   *cmd = {
    35     binary_path,
    36     std::to_string(kRecoveryApiVersion),
    37     std::to_string(status_fd),
    38     package,
    39   };
    40   if (retry_count > 0) {
    41     cmd->push_back("retry");
    42   }
    43   return 0;
    44 }

    3.5 创建管道,这里子进程关闭了读端,父进程关闭了写端,这样就是保证从单向的信息通信,从

    子进程传入信息到父进程中。

    1 android::base::Pipe(&pipe_read, &pipe_write, 0)

    3.6 创建子进程,在子进程中运行update-binary进程

     1 if (pid == 0) {
     2     umask(022);
     3     pipe_read.reset();
     4 
     5     // Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
     6     auto chr_args = StringVectorToNullTerminatedArray(args);
     7     /* chr_args[0] 其实就是升级包中的 META-INF/com/google/android/update-binary */
     8     execv(chr_args[0], chr_args.data());
     9     // We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to
    10     // hang. This deadlock results from an improperly copied mutex in the ui functions.
    11     // (Bug: 34769056)
    12     fprintf(stdout, "E:Can't run %s (%s)
    ", chr_args[0], strerror(errno));
    13     _exit(EXIT_FAILURE);
    14   }

    3.7 recovery获取子进程的信息并显示,进度、ui_print 等等。

    1 FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
    2 while (fgets(buffer, sizeof(buffer), from_child) != nullptr)

    4 execv执行升级进程之后,工作在updater/updater.cpp中完成。

    4.1 这里的主要核心就是构造脚本解析器对updater-script中的命令进行执行,至于这个脚本解析器

    是如何构造的,如何执行的, 其实我也搞的不是很清楚。

    4.2 安装升级包的核心程序就是Configure edify's functions. 中的那些注册回调函数

      1 int main(int argc, char** argv) {
      2 // Various things log information to stdout or stderr more or less
      3 // at random (though we've tried to standardize on stdout).  The
      4 // log file makes more sense if buffering is turned off so things
      5 // appear in the right order.
      6   setbuf(stdout, nullptr);
      7   setbuf(stderr, nullptr);
      8 // We don't have logcat yet under recovery. Update logs will always be written to stdout
      9 // (which is redirected to recovery.log).
     10   android::base::InitLogging(argv, &UpdaterLogger);
     11 if (argc != 4 && argc != 5) {
     12     LOG(ERROR) << "unexpected number of arguments: " << argc;
     13 return 1;
     14   }
     15 /* 支持的版本检查 */
     16 char* version = argv[1];
     17 if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '') {
     18 // We support version 1, 2, or 3.
     19     LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
     20 return 2;
     21   }
     22 // Set up the pipe for sending commands back to the parent process.
     23 int fd = atoi(argv[2]);
     24   FILE* cmd_pipe = fdopen(fd, "wb");
     25   setlinebuf(cmd_pipe);
     26 // Extract the script from the package.
     27 /* 从包中提取脚本 */
     28 const char* package_filename = argv[3];
     29   MemMapping map;
     30 if (!map.MapFile(package_filename)) {
     31     LOG(ERROR) << "failed to map package " << argv[3];
     32 return 3;
     33   }
     34   ZipArchiveHandle za;
     35 int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
     36 if (open_err != 0) {
     37     LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
     38     CloseArchive(za);
     39 return 3;
     40   }
     41 ZipString script_name(SCRIPT_NAME);
     42   ZipEntry script_entry;
     43 int find_err = FindEntry(za, script_name, &script_entry);
     44 if (find_err != 0) {
     45     LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
     46                << ErrorCodeString(find_err);
     47     CloseArchive(za);
     48 return 4;
     49   }
     50 std::string script;
     51   script.resize(script_entry.uncompressed_length);
     52 int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
     53                                     script_entry.uncompressed_length);
     54 if (extract_err != 0) {
     55     LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
     56     CloseArchive(za);
     57 return 5;
     58   }
     59 // Configure edify's functions.
     60 /* 注册updater-script中的回调函数 这里主要是一些断言函数 abort assert*/
     61   RegisterBuiltins();
     62 /* 这里主要是一些安装升级包的函数 主要是对有文件系统的分区来说*/
     63   RegisterInstallFunctions();
     64 /* 这里主要注册对裸分区进行升级的函数 */
     65   RegisterBlockImageFunctions();
     66   RegisterDynamicPartitionsFunctions();
     67   RegisterDeviceExtensions();
     68 // Parse the script.
     69 std::unique_ptr<Expr> root;
     70 int error_count = 0;
     71 int error = ParseString(script, &root, &error_count);
     72 if (error != 0 || error_count > 0) {
     73     LOG(ERROR) << error_count << " parse errors";
     74     CloseArchive(za);
     75 return 6;
     76   }
     77   sehandle = selinux_android_file_context_handle();
     78   selinux_android_set_sehandle(sehandle);
     79 if (!sehandle) {
     80 fprintf(cmd_pipe, "ui_print Warning: No file_contexts
    ");
     81   }
     82 // Evaluate the parsed script.
     83   UpdaterInfo updater_info;
     84   updater_info.cmd_pipe = cmd_pipe;
     85   updater_info.package_zip = za;
     86   updater_info.version = atoi(version);
     87   updater_info.package_zip_addr = map.addr;
     88   updater_info.package_zip_len = map.length;
     89 State state(script, &updater_info);
     90 if (argc == 5) {
     91 if (strcmp(argv[4], "retry") == 0) {
     92       state.is_retry = true;
     93     } else {
     94 printf("unexpected argument: %s", argv[4]);
     95     }
     96   }
     97 std::string result;
     98 bool status = Evaluate(&state, root, &result);
     99 if (!status) {
    100 if (state.errmsg.empty()) {
    101       LOG(ERROR) << "script aborted (no error message)";
    102 fprintf(cmd_pipe, "ui_print script aborted (no error message)
    ");
    103     } else {
    104       LOG(ERROR) << "script aborted: " << state.errmsg;
    105 const std::vector<std::string> lines = android::base::Split(state.errmsg, "
    ");
    106 for (const std::string& line : lines) {
    107 // Parse the error code in abort message.
    108 // Example: "E30: This package is for bullhead devices."
    109 if (!line.empty() && line[0] == 'E') {
    110 if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
    111             LOG(ERROR) << "Failed to parse error code: [" << line << "]";
    112           }
    113         }
    114 fprintf(cmd_pipe, "ui_print %s
    ", line.c_str());
    115       }
    116     }
    117 // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
    118 // a more specific code has been set in errmsg.
    119 if (state.error_code == kNoError) {
    120       state.error_code = kScriptExecutionFailure;
    121     }
    122 fprintf(cmd_pipe, "log error: %d
    ", state.error_code);
    123 // Cause code should provide additional information about the abort.
    124 if (state.cause_code != kNoCause) {
    125 fprintf(cmd_pipe, "log cause: %d
    ", state.cause_code);
    126 if (state.cause_code == kPatchApplicationFailure) {
    127         LOG(INFO) << "Patch application failed, retry update.";
    128 fprintf(cmd_pipe, "retry_update
    ");
    129       } else if (state.cause_code == kEioFailure) {
    130         LOG(INFO) << "Update failed due to EIO, retry update.";
    131 fprintf(cmd_pipe, "retry_update
    ");
    132       }
    133     }
    134 if (updater_info.package_zip) {
    135       CloseArchive(updater_info.package_zip);
    136     }
    137 return 7;
    138   } else {
    139 fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]
    ", result.c_str());
    140   }
    141 if (updater_info.package_zip) {
    142     CloseArchive(updater_info.package_zip);
    143   }
    144 return 0;
    145 }

    以上就是基于Android的OTARecovery模式升级流程。我这里主要是梳理整个升级流程的主要,

    很多地方还是写的不够细,望读者理解,我认为比较核心与关键的地方有以下几点吧

    • 主系统与recovery升级系统,升级消息的传递通过cache;
    • BCB块中写信息来保证断电续升;
    • 主系统中fork子进程进行升级进程的执行,并通过pipe管道进行信息交互;
    • updater中使用命令与执行的分离,命令在updater-script中,执行在update-binary中;
      • 升级程序通过升级包带入的,那么核心升级流程是每次都有机会变更或者优化的,
      • 这样就比那些将升级流程预置在系统中的要灵活的很多;

                                         

            长按二维码关注【嵌入式C部落】,获取更多编程资料及精华文章

  • 相关阅读:
    留言板
    移动端开发
    css3的伪类
    JQuery移除事件
    关于offset,你知道多少?
    关于section-scroll插件:
    jQuery响应式弹出层和对话框插插件magnific-popup.css
    col-lg-8 col-lg-offset-2
    关于渐变属 gradient:
    关于 window.onresize
  • 原文地址:https://www.cnblogs.com/nuoyan/p/11861705.html
Copyright © 2011-2022 走看看