• android recovery 主系统代码分析【转】


    本文转载自:http://blog.csdn.net/andyhuabing/article/details/9248713

    阅读完上一篇文章: http://blog.csdn.net/andyhuabing/article/details/9226569 

    我们已经清楚了如何进入正常模式和Recovery模式已有深刻理解了,假设进入了Recovery模式,那么其核心代码是怎么做的呢?

    代码路径在 Android 源码的根路径: bootable ecovery 其入口文件就是 recovery.c 中 main函数

    下面就开始逐步了解其Recovery的设计思想:

    static const char *COMMAND_FILE = "/cache/recovery/command";
    static const char *INTENT_FILE = "/cache/recovery/intent";
    static const char *LOG_FILE = "/cache/recovery/log";

    注解里面描述的相当清楚:

     * The recovery tool communicates with the main system through /cache files.
     *   /cache/recovery/command - INPUT - command line for tool, one arg per line
     *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
     *   /cache/recovery/intent - OUTPUT - intent that was passed in

    static const char *LAST_LOG_FILE = "/cache/recovery/last_log";

    static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
    static const char *CACHE_ROOT = "/cache";
    static const char *SDCARD_ROOT = "/sdcard";

    下面的描述针对写入的 command 有大致的介绍:

     * The arguments which may be supplied in the recovery.command file:
     *   --send_intent=anystring - write the text out to recovery.intent
     *   --update_package=path - verify install an OTA package file
     *   --wipe_data - erase user data (and cache), then reboot
     *   --wipe_cache - wipe cache (but not user data), then reboot
     *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs

    两种升级模式步骤说明:

     * After completing, we remove /cache/recovery/command and reboot.
     * Arguments may also be supplied in the bootloader control block (BCB).
     * These important scenarios must be safely restartable at any point:
     *
     * FACTORY RESET
     * 1. user selects "factory reset"
     * 2. main system writes "--wipe_data" to /cache/recovery/command
     * 3. main system reboots into recovery
     * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"
     *    -- after this, rebooting will restart the erase --
     * 5. erase_volume() reformats /data
     * 6. erase_volume() reformats /cache
     * 7. finish_recovery() erases BCB
     *    -- after this, rebooting will restart the main system --
     * 8. main() calls reboot() to boot main system
     *
     * 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 是如何工作的啦。下面就从具体代码开始一步步分析。

    1、recovery main 函数

    [cpp] view plain copy
     
     print?
    1. int  
    2. main(int argc, char **argv) {  
    3.     time_t start = time(NULL);  
    4.   
    5.     // If these fail, there's not really anywhere to complain...  
    6.     freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);  
    7.     freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);  
    8.     printf("Starting recovery on %s", ctime(&start));  


    将标准输出和标准错误输出重定位到"/tmp/recovery.log",如果是eng模式,就可以通过adb pull /tmp/recovery.log, 看到当前的log信息,这为我们提供了有效的调试手段。

    ui_init();

    一个简单的基于framebuffer的ui系统,叫miniui 主要建立了图像部分(gglInit、gr_init_font、framebuffer)及进度条和事件处理(input_callback)

    load_volume_table();

    根据 /etc/recovery.fstab 建立分区表

    // command line args come from, in decreasing precedence:
    //   - the actual command line
    //   - the bootloader control block (one per line, after "recovery")
    //   - the contents of COMMAND_FILE (one per line)

    get_args(&argc, &argv);

    misc 分区以及 CACHE:recovery/command 文件中读入参数,写入到argc, argv (get_bootloader_message) 并有可能写回 misc 分区(set_bootloader_message)

    做完以上事情后就开始解析具体参数:

        while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
            switch (arg) {
            case 'p': previous_runs = atoi(optarg); break;
            case 's': send_intent = optarg; break;
            case 'u': update_package = optarg; break;
            case 'w': wipe_data = wipe_cache = 1; break;
            case 'c': wipe_cache = 1; break;
            case 't': ui_show_text(1); break;
            case '?':
                LOGE("Invalid command argument ");
                continue;
            }
        }

        printf("Command:");
        for (arg = 0; arg < argc; arg++) {
            printf(" "%s"", argv[arg]);
        }
        printf(" ");

    以上仅仅是打印表明进入到哪一步,方便调试情况的掌握

    下面的代码就是具体干的事情了:

        if (update_package != NULL) {
            status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);
            if (status == INSTALL_SUCCESS && wipe_cache) {
                if (erase_volume("/cache")) {
                    LOGE("Cache wipe (requested by package) failed.");
                }
            }
            if (status != INSTALL_SUCCESS) ui_print("Installation aborted. ");
        } else if (wipe_data) {
            if (device_wipe_data()) status = INSTALL_ERROR;
            if (erase_volume("/data")) status = INSTALL_ERROR;
            if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
            if (status != INSTALL_SUCCESS) ui_print("Data wipe failed. ");
            clear_sdcard_update_bootloader_message();
        } else if (wipe_cache) {
            if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
            if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed. ");
            clear_sdcard_update_bootloader_message();
        } else {
            status = update_by_key();  // No command specified
        }

    根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user data分区等等,后在会将继续详细分解。
    
    
    if (status != INSTALL_SUCCESS) prompt_and_wait();
    如果前面做的操作成功则进入重启流程,否则由用户操作,可选操作为: reboot, 安装update.zip,除cache分区, 擦除user data分区

     

        // Otherwise, get ready to boot the main system...
        finish_recovery(send_intent);

    先看函数注解:

    // clear the recovery command and prepare to boot a (hopefully working) system,
    // copy our log file to cache as well (for the system to read), and
    // record any intent we were asked to communicate back to the system.
    // this function is idempotent: call it as many times as you like.

    其实主要的就是如下函数操作:

        // Remove the command file, so recovery won't repeat indefinitely.
        if (ensure_path_mounted(COMMAND_FILE) != 0 || 
            (unlink(COMMAND_FILE) && errno != ENOENT)) {
            LOGW("Can't unlink %s ", COMMAND_FILE);
        }
        

        将指定分区mounted 成功并 unlink 删除一个文件的目录项并减少它的链接数

        ensure_path_unmounted(CACHE_ROOT);

        将指定分区 unmounted 
        sync();  // For good measure.

    对于上面的代码总结:

    它的功能如下:
    1、将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command
    2、将 /tmp/recovery.log 复制到 "CACHE:recovery/log";
    3、清空 misc 分区,这样重启就不会进入recovery模式
    4、删除command 文件:CACHE:recovery/command;

    最后重启机器

        ui_print("Rebooting... ");
        android_reboot(ANDROID_RB_RESTART, 0, 0);

    2、factory reset 核心代码实现

    按照前面所列的8条步骤,其中1-6及7-8都与 main 通用流程一样,不再复述。

     * 5. erase_volume() reformats /data
     * 6. erase_volume() reformats /cache

    这两个操作是如何做到的呢?

    if (erase_volume("/data")) status = INSTALL_ERROR;

    if (erase_volume("/cache")) status = INSTALL_ERROR;

    最后就是

    clear_sdcard_update_bootloader_message();

    看看 erase_volume() 函数先:

    [cpp] view plain copy
     
     print?
    1. static int  
    2. erase_volume(const char *volume) {  
    3.     ui_set_background(BACKGROUND_ICON_INSTALLING);  
    4.     ui_show_indeterminate_progress();  
    5.     ui_print("Formatting %s... ", volume);  
    6.   
    7.     ensure_path_unmounted(volume);  
    8.   
    9.     if (strcmp(volume, "/cache") == 0) {  
    10.         // Any part of the log we'd copied to cache is now gone.  
    11.         // Reset the pointer so we copy from the beginning of the temp  
    12.         // log.  
    13.         tmplog_offset = 0;  
    14.     }  
    15.   
    16.     return format_volume(volume);  
    17. }  


    上面红字标明的是重要函数调用

    int ensure_path_unmounted(const char* path) {

    Volume* v = volume_for_path(path);

     result = scan_mounted_volumes();

    return unmount_mounted_volume(mv);

    }

    就是将指定的path中径mount point进行卸载掉,而 format_volume的主要功能就是:

    MtdWriteContext *write = mtd_write_partition(partition);

    mtd_erase_blocks(write, -1);

    mtd_write_close(write);

    不要细说了吧,就是将整个分区数据全清掉。

    最后一个函数:

    void
    clear_sdcard_update_bootloader_message() {
        struct bootloader_message boot;
        memset(&boot, 0, sizeof(boot));
        set_bootloader_message(&boot);
    }

    就是将misc分区数据重置清0

    这样子就完成的恢复出厂设置的情况了。将 data/cache分区erase擦掉就好了。

    3、OTA 安装 核心代码实现

    主要函数就是如何安装 Package :

     * 5. install_package() attempts to install the update
     *    NOTE: the package install must itself be restartable from any point

    int
    install_package(const char* path, int* wipe_cache, const char* install_file)

    -->

    static int
    really_install_package(const char *path, int* wipe_cache){

    clear_sdcard_update_bootloader_message();

        ui_set_background(BACKGROUND_ICON_INSTALLING);
        ui_print("Finding update package... ");
        ui_show_indeterminate_progress();
        LOGI("Update location: %s ", path);

        更新 ui 显示

        for(;((i < 5)&&(ensure_path_mounted(path) != 0));i++){
            LOGE("Can't mount %s ",path);
            sleep(1);
        }
        if((i >= 5)&&(ensure_path_mounted(path) != 0)){
            return INSTALL_CORRUPT;
        }

        

      确保升级包所在分区已经mount,通常为 cache 分区或者 SD 分区
     
      RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
    // Look for an RSA signature embedded in the .ZIP file comment given
    // the path to the zip.  Verify it matches one of the given public
    // keys.
    //
    // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
    // or no key matches the signature).
    
      err = verify_file(path, loadedKeys, numKeys);
    /res/keys中装载公钥,并进行确认文件的合法性
    
    
        /* Try to open the package.
         */
        ZipArchive zip;
        err = mzOpenZipArchive(path, &zip);    
        打开升级包,将相关信息存到ZipArchive数据机构中,便于后面处理。
    
    
        /* Verify and install the contents of the package.
         */
        ui_print("Installing update...
    ");
        return try_update_binary(path, &zip, wipe_cache);    
         进行最后的安装包文件

    }

    // If the package contains an update binary, extract it and run it.
    static int
    try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
        const ZipEntry* binary_entry =
                mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);

        char* binary = "/tmp/update_binary";
        unlink(binary);
        int fd = creat(binary, 0755);

        bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
        close(fd);
        mzCloseZipArchive(zip);

       

    将升级包内文件META-INF/com/google/android/update-binary 复制为/tmp/update_binary

        // 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:

        int pipefd[2];
        pipe(pipefd);

        char** args = malloc(sizeof(char*) * 5);

        args[0] = binary;
        args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in android.mk
        args[2] = malloc(10);
        sprintf(args[2], "%d", pipefd[1]);
        args[3] = (char*)path;
        args[4] = buf_uuid;
        args[5] = NULL;

        组装新的进程参数    

        pid_t pid = fork();
        if (pid == 0) { // child process
            close(pipefd[0]);
            execv(binary, args);

        } 

        // parent process
        close(pipefd[1]);

        ui_show_progress

        ui_set_progress

        ui_print

    总结一下代码主要行为功能:

    1、将会创建新的进程,执行:/tmp/update_binary

    2、同时,会给该进程传入一些参数,其中最重要的就是一个管道fd,供新进程与原进程通信。

    3、新进程诞生后,原进程就变成了一个服务进程,它提供若干UI更新服务:

    a)   progress

    b)   set_progress

    c)   ui_print

    这样,新进程就可以通过老进程的UI系统完成显示任务。而其他功能就靠它自己了。

  • 相关阅读:
    SpringCloud系列——TX-LCN分布式事务管理
    SpringCloud系列——限流、熔断、降级
    SpringBoot系列——Logback日志,输出到文件以及实时输出到web页面
    常用的js、java编码解码方法
    WebSocket数据加密——AES与RSA混合加密
    使用Fiddler重定向App的网络请求
    C# 调用 taskkill命令结束服务进程
    Install .Net Core For CentOS
    cron表达式详解[转]
    WinServer远程部署系统打包批处理文件
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/7300574.html
Copyright © 2020-2023  润新知