Android Recovery流程分析 OTA安装基本架构

24 篇文章 4 订阅

本人博客已经迁移到nasdaqgodzilla.github.io

Recovery升级准备流程

  1. OTA应用调用Framework内提供的系统接口RecoverySystem.installPackage()后,在/cache/recovery/block.map中写入升级信息。该文件将会在设备重启时,由启动引导程序读取内容,并根据内容启动为recovery模式,调起recovery进行OTA包的升级。
  2. Recovery启动后,首先挂载分区,读取系统属性,然后通过block.map指定的路径尝试读取升级文件并进行校验。
  3. 取出OTA包内的升级文件和列表system.patch.datsystem.new.datsystem.transfer.listboot.imguboot.imgmetadata用于校验版本。若update-binary有,则取出,用于执行升级脚本定义的升级命令。updater-script定义了用于执行升级的脚本。
  4. 根据OTA包内metadata定义的post-build和pre-build的匹配来确定当前OTA是否是本设备对应Android版本的合适差分包。确认版本号匹配后,才会进行校验。若版本号不匹配,则在校验进行之前就会退出安装。

OTA安装流程

/* OTA INSTALL
 * 1. main system downloads OTA package to /cache/some-filename.zip
 * 2. main system writes "--update_package=/cache/some-filename.zip"
 * 3. main system reboots into recovery
 * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
 *    -- after this, rebooting will attempt to reinstall the update --
 * 5. install_package() attempts to install the update
 *    NOTE: the package install must itself be restartable from any point
 * 6. finish_recovery() erases BCB
 *    -- after this, rebooting will (try to) restart the main system --
 * 7. ** if install failed **
 *    7a. prompt_and_wait() shows an error icon and waits for the user
 *    7b; the user reboots (pulling the battery, etc) into the main system
 * 8. main() calls maybe_install_firmware_update()
 *    ** if the update contained radio/hboot firmware **:
 *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
 *        -- after this, rebooting will reformat cache & restart main system --
 *    8b. m_i_f_u() writes firmware image into raw cache partition
 *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
 *        -- after this, rebooting will attempt to reinstall firmware --
 *    8d. bootloader tries to flash firmware
 *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
 *        -- after this, rebooting will reformat cache & restart main system --
 *    8f. erase_volume() reformats /cache
 *    8g. finish_recovery() erases BCB
 *        -- after this, rebooting will (try to) restart the main system --
 * 9. main() calls reboot() to boot main system
 */

Recovery执行升级

  • metadata: 键值对存储目标版本和源版本的信息,匹配成功才会进行OTA包的写入。
  • *.dat *.list: 按照block块diff得到的数据文件。
  • update-binary、update-script: recovery会fork子进程,exec执行OTA包内的update-binary,而update-binary实际上是一个脚本执行器,它执行的脚本就是OTA包的安装指令,这些指令存储在update-script内。
  1. recovery挂载分区、准备参数、获取升级文件后,调用install_package()(实际上是really_install_package()真正执行安装)进行OTA包的安装。该方法校验升级包后,通过try_update_binary()执行安装。
static int
really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
                       std::vector<std::string>& log_buffer, int retry_count)
{
    // ...
    
    // Map the update package into memory.
    ui->Print("Opening update package...\n");

    // ...

    // Verify package.
    if (!verify_package(map.addr, map.length)) {
        log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
        sysReleaseMap(&map);
        return INSTALL_CORRUPT;
    }

    // Try to open the package.
    ZipArchive zip;
    int err = mzOpenZipArchive(map.addr, map.length, &zip);
    if (err != 0) {
        LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
        log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));

        sysReleaseMap(&map);
        return INSTALL_CORRUPT;
    }

    // Verify and install the contents of the package.
    ui->Print("Installing update...\n");
    if (retry_count > 0) {
        ui->Print("Retry attempt: %d\n", retry_count);
    }
    ui->SetEnableReboot(false);
    int result = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count);
    ui->SetEnableReboot(true);
    ui->Print("\n");

    sysReleaseMap(&map);

    return result;
}
  1. try_update_binary()会fork一个子进程并通过pipe进行通信。fork后的父进程会从pipe中读取子进程发出的指令,并执行对应的方法,如打印字符串到屏幕、重试更新、设置更新进度条和抹除设备数据等。pipe关闭后,父进程获取子进程的退出状态即更新包的安装结果。子进程会exec一个打包进入OTA包的updater。它是一个可执行bin,是执行OTA安装的关键进程。
// 如果OTA升级包内包含update-binary,就会将它解压出来作为安装执行器。即fork的子进程会运行这个bin,这个bin真正执行将OTA升级包写入系统。
static int
try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache,
                  std::vector<std::string>& log_buffer, int retry_count)
{
    read_source_target_build(zip, log_buffer);

    // 这里是父进程准备与子进程通信的pipe
    int pipefd[2];
    pipe(pipefd);

    std::vector<std::string> args;

    // 根据系统是否是AB升级,确定升级命令,实际就是从OTA包中取出update-binary并组建升级命令
    // 升级命令既是调用子进程运行update-binary的指令
    int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
    mzCloseZipArchive(zip);
    if (ret) {
        close(pipefd[0]);
        close(pipefd[1]);
        return ret;
    }

    // 接下来通过注释阐述了父进程,即当前进程能够支持的函数
    // 这些函数将由子进程(即将创建的update-binary的进程)通过pipe向父进程(本进程)以字符串的形式发出函数名称和参数,进而根据函数名称调用函数
    // 基本常用的包括设置进度(显示到ui)、ui打印和清除数据
    // 子进程将被创建以执行升级,而本进程(父进程)会循环等待pipe的数据,并解释后执行对应的函数,实现进程分离独立运行(父进程实现UI和其他工作,子进程负责安装)
    // When executing the update binary contained in the package, the
    // arguments passed are:
    //
    //   - the version number for this interface
    //
    //   - an fd to which the program can write in order to update the
    //     progress bar.  The program can write single-line commands:
    //
    //        progress <frac> <secs>
    //            fill up the next <frac> part of of the progress bar
    //            over <secs> seconds.  If <secs> is zero, use
    //            set_progress commands to manually control the
    //            progress of this segment of the bar.
    //
    //        set_progress <frac>
    //            <frac> should be between 0.0 and 1.0; sets the
    //            progress bar within the segment defined by the most
    //            recent progress command.
    //
    //        firmware <"hboot"|"radio"> <filename>
    //            arrange to install the contents of <filename> in the
    //            given partition on reboot.
    //
    //            (API v2: <filename> may start with "PACKAGE:" to
    //            indicate taking a file from the OTA package.)
    //
    //            (API v3: this command no longer exists.)
    //
    //        ui_print <string>
    //            display <string> on the screen.
    //
    //        wipe_cache
    //            a wipe of cache will be performed following a successful
    //            installation.
    //
    //        clear_display
    //            turn off the text display.
    //
    //        enable_reboot
    //            packages can explicitly request that they want the user
    //            to be able to reboot during installation (useful for
    //            debugging packages that don't exit).
    //
    //   - the name of the package zip file.
    //
    //   - an optional argument "retry" if this update is a retry of a failed
    //   update attempt.
    //

    // 准备创建子进程,准备子进程参数,准备管道
    // Convert the vector to a NULL-terminated char* array suitable for execv.
    const char* chr_args[args.size() + 1];
    chr_args[args.size()] = NULL;
    for (size_t i = 0; i < args.size(); i++) {
        chr_args[i] = args[i].c_str();
    }

    pid_t pid = fork();

    if (pid == -1) {
        close(pipefd[0]);
        close(pipefd[1]);
        LOGE("Failed to fork update binary: %s\n", strerror(errno));
        return INSTALL_ERROR;
    }

    // 子进程,执行update-binary,跳转到updater.cpp
    if (pid == 0) {
        umask(022);
        close(pipefd[0]);
        execv(chr_args[0], const_cast<char**>(chr_args));
        fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
        _exit(-1);
    }
    close(pipefd[1]);

    *wipe_cache = false;
    bool retry_update = false;

    // 父进程,在管道的一端进行读循环,直到子进程退出时关闭管道后,跳出循环
    // 子进程(update-binary)会向管道写入函数名和参数,这些字符串形式的信息在接下来的while()中被解析,然后由父进程(本进程)执行对应的函数。
    char buffer[1024];
    FILE* from_child = fdopen(pipefd[0], "r");
    while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
        char* command = strtok(buffer, " \n");
        if (command == NULL) {
            continue;
        } else if (strcmp(command, "progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            char* seconds_s = strtok(NULL, " \n");

            float fraction = strtof(fraction_s, NULL);
            int seconds = strtol(seconds_s, NULL, 10);

            ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
        } else if (strcmp(command, "set_progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            float fraction = strtof(fraction_s, NULL);
            ui->SetProgress(fraction);
        } else if (strcmp(command, "ui_print") == 0) {
            char* str = strtok(NULL, "\n");
            if (str) {
                ui->PrintOnScreenOnly("%s", str);
            } else {
                ui->PrintOnScreenOnly("\n");
            }
            fflush(stdout);
        } else if (strcmp(command, "wipe_cache") == 0) {
            *wipe_cache = true;
        } else if (strcmp(command, "clear_display") == 0) {
            ui->SetBackground(RecoveryUI::NONE);
        } else if (strcmp(command, "enable_reboot") == 0) {
            // packages can explicitly request that they want the user
            // to be able to reboot during installation (useful for
            // debugging packages that don't exit).
            ui->SetEnableReboot(true);
        } else if (strcmp(command, "retry_update") == 0) {
            retry_update = true;
        } else if (strcmp(command, "log") == 0) {
            // Save the logging request from updater and write to
            // last_install later.
            log_buffer.push_back(std::string(strtok(NULL, "\n")));
        } else if (strcmp(command, "wipe_all") == 0){
            printf("set bWipeAfterUpdate to true.\n");
            bWipeAfterUpdate = true;
        }else {
            LOGE("unknown command [%s]\n", command);
        }
    }
    fclose(from_child);

    // 子进程结束,这里获取子进程的退出状态码
    int status;
    waitpid(pid, &status, 0);
    if (retry_update) {
        return INSTALL_RETRY;
    }
    // 分别获取子进程是否正常退出(不是因为崩溃或信号退出的),并获取退出状态码。若子进程正常退出,则这里已经完成了安装,接下来回到install.cpp完成收尾后重启。
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
        return INSTALL_ERROR;
    }

    return INSTALL_SUCCESS;
}
  1. 通过子进程执行安装。
  • 子进程是在install.cpptry_binary_update中fork后execv启动的。代码位于```updater.cpp`。
int main(int argc, char** argv) {
    // ...

    // 通过execv时传递的参数,获取管道fd
    int fd = atoi(argv[2]);
    FILE* cmd_pipe = fdopen(fd, "wb");
    setlinebuf(cmd_pipe);

    // 从OTA升级包中解压得到updater-script,该文件与本进程的bin(update-binary)在同一个目录下
    // 这个script是某种特定格式的脚本,有本进程解析并执行,其内容就是升级的所有内容。包含升级文件的校验、升级流程的控制和升级步骤,全部都在这个文件内控制。
    const char* package_filename = argv[3];
    g_package_file = package_filename;
    MemMapping map;
    if (sysMapFile(package_filename, &map) != 0) {
        printf("failed to map package %s\n", argv[3]);
        return 3;
    }
    ZipArchive za;
    int err;
    err = mzOpenZipArchive(map.addr, map.length, &za);
    if (err != 0) {
        printf("failed to open package %s: %s\n",
               argv[3], strerror(err));
        return 3;
    }
    ota_io_init(&za);

    const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
    if (script_entry == NULL) {
        printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename);
        return 4;
    }

    char* script = reinterpret_cast<char*>(malloc(script_entry->uncompLen+1));
    if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
        printf("failed to read script from package\n");
        return 5;
    }
    script[script_entry->uncompLen] = '\0';

    // 注册处理函数,实际上就是将函数名字符串与对应的函数指针关联起来
    // 从updater-script中读取到的执行升级任务的脚本,其实就是以下注册的函数名加上一些参数。即读出来的内容,根据对应的函数名调用对应的函数,就实现了解析执行安装脚本(updater-script)的功能。
    RegisterBuiltins();
    RegisterInstallFunctions();
    RegisterBlockImageFunctions(); // 例,这里注册了升级system分区的常用的指令,使得update-binary(本进程)可以执行对应的system分区升级命令。
    RegisterDeviceExtensions();
    FinishRegistration();

    // 解析脚本。这里调用的parse_string是updater-script所使用的一种特定格式语言的解析器,经过执行后会将结果保存到`Expr* root`。它是一个链表。这个链表实际上很简单,仅包含了解析出来的命令(字符串形式,在上面注册的Register*Func中找得到对应的函数)、对应的参数(传递给函数的参数)和链表成员指针(指向链表的下一个成员)
    // parse_string按行解析脚本,也将每一行结果保存为一个链表成员。所以每个链表成员实际上都是一行updater-script的内容,也是一行命令,对应了一个函数。
    Expr* root;
    int error_count = 0;
    int error = parse_string(script, &root, &error_count);
    if (error != 0 || error_count > 0) {
        printf("%d parse errors\n", error_count);
        return 6;
    }

    struct selinux_opt seopts[] = {
      { SELABEL_OPT_PATH, "/file_contexts" }
    };

    sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);

    if (!sehandle) {
        fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
    }

    // 完成了updater-script的解析(即通过parse_string将updater-script的指令转换成一个个函数调用指令,通过有序链表存储)
    // 这里开始解析链表,将链表指示的函数调用,通过上面注册的函数(Register*Function)找到对应的函数,并执行。
    UpdaterInfo updater_info;
    updater_info.cmd_pipe = cmd_pipe;
    updater_info.package_zip = &za;
    updater_info.version = atoi(version);
    updater_info.package_zip_addr = map.addr;
    updater_info.package_zip_len = map.length;

    State state;
    state.cookie = &updater_info;
    state.script = script;
    state.errmsg = NULL;

    if (argc == 5) {
        if (strcmp(argv[4], "retry") == 0) {
            state.is_retry = true;
        } else {
            printf("unexpected argument: %s", argv[4]);
        }
    }

    // 执行脚本指示的函数。其中state用于返回,提示调用结果。root即上文的Expr,里面包含了注册的函数指针,包含了从update-script解析出来的函数参数、函数名。
    char* result = Evaluate(&state, root);

    if (have_eio_error) {
        fprintf(cmd_pipe, "retry_update\n");
    }

    // 脚本执行,错误处理
    if (result == NULL) {
        if (state.errmsg == NULL) {
            printf("script aborted (no error message)\n");
            fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
        } else {
            printf("script aborted: %s\n", state.errmsg);
            char* line = strtok(state.errmsg, "\n");
            while (line) {
                // Parse the error code in abort message.
                // Example: "E30: This package is for bullhead devices."
                if (*line == 'E') {
                    if (sscanf(line, "E%u: ", &state.error_code) != 1) {
                         printf("Failed to parse error code: [%s]\n", line);
                    }
                }
                fprintf(cmd_pipe, "ui_print %s\n", line);
                line = strtok(NULL, "\n");
            }
            fprintf(cmd_pipe, "ui_print\n");
        }

        if (state.error_code != kNoError) {
            fprintf(cmd_pipe, "log error: %d\n", state.error_code);
            // Cause code should provide additional information about the abort;
            // report only when an error exists.
            if (state.cause_code != kNoCause) {
                fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
            }
        }

        free(state.errmsg);
        return 7;
    } else {
        fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result);
        free(result);
    }

    if (updater_info.package_zip) {
        mzCloseZipArchive(updater_info.package_zip);
    }
    sysReleaseMap(&map);
    free(script);

    return 0;
}
  1. 子进程解析脚本调用注册的函数完成处理后,即退出,由父进程完成收尾工作并重启。

多进程/线程架构

  • recovery中可能会同时存在多个进程和线程
  • 如updater就运行于独立的进程。
  • updater执行对应的定义于updater-script的命令时,可能会创建线程。
  • 如block_image_update对应的函数PerformBlockImageUpdate就会创建线程来写入block差分数据。
    if (params.canwrite) {
        params.nti.za = za;
        params.nti.entry = new_entry;

        pthread_mutex_init(&params.nti.mu, nullptr);
        pthread_cond_init(&params.nti.cv, nullptr);
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

        int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
        if (error != 0) {
            fprintf(stderr, "pthread_create failed: %s\n", strerror(error));
            return StringValue(strdup(""));
        }
    }

update-script、updater-binary与system.patch.dat、system.transfer.list

  1. update-binary是OTA包内包含的执行升级指令的Bin,这个bin根据updater-script定义的一系列脚本来执行升级;
  2. 在较高版本的Android OTA中,recovery支持以block diff形式生成的差分包进行升级。当update-binary进行system分区的升级时,往往是根据updater-script中定义的block_image_update指令进行块差异升级。其中,update-script仅仅只是定义了执行该命令,而真正保存差异的(即即将写入system分区的内容)是system.patch.dat和system.transfer.list
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值