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

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

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

代码路径在 Android 源码的根路径: bootable\recovery 其入口文件就是 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 函数

  1. int
  2. main(int argc, char **argv) {
  3. time_t start = time(NULL);
  4. // If these fail, there's not really anywhere to complain...
  5. freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
  6. freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
  7. 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\n");
            continue;
        }
    }

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

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

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

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.\n");
    } 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.\n");
        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.\n");
        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\n", 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...\n");
    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() 函数先:

  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...\n", volume);
  6. ensure_path_unmounted(volume);
  7. if (strcmp(volume, "/cache") == 0) {
  8. // Any part of the log we'd copied to cache is now gone.
  9. // Reset the pointer so we copy from the beginning of the temp
  10. // log.
  11. tmplog_offset = 0;
  12. }
  13. return format_volume(volume);
  14. }

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

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...\n");
    ui_show_indeterminate_progress();
    LOGI("Update location: %s\n", path);

更新 ui 显示

for(;((i < 5)&&(ensure_path_mounted(path) != 0));i++){
        LOGE("Can't mount %s\n",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...\n");
    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系统完成显示任务。而其他功能就靠它自己了。

android recovery 主系统代码分析【转】的更多相关文章

  1. android recovery 主系统代码分析

    阅读完上一篇文章: http://blog.csdn.net/andyhuabing/article/details/9226569 我们已经清楚了如何进入正常模式和Recovery模式已有深刻理解了 ...

  2. Android 4.2启动代码分析(一)

    Android系统启动过程分析 Android系统的框架架构图如下(来自网上):   Linux内核启动之后----->就到Android的Init进程 ----->进而启动Android ...

  3. android recovery 系统代码分析 -- 选择进入【转】

    本文转载自:http://blog.csdn.net/andyhuabing/article/details/9226569 最近做Recovery的规范及操作指导文档,花了一些时间将流程搞清. An ...

  4. android recover 系统代码分析 -- 选择进入

    最近做Recovery的规范及操作指导文档,花了一些时间将流程搞清. Android利用Recovery模式,进行恢复出厂设置,OTA升级,patch升级及firmware升级.而在进入Recover ...

  5. android adb 流程原理代码分析(一)

    由于要用到adb的知识,但是对adb啥也不了解,看了下android的代码,adb的源码在system/core/adb下面,然后网上搜下了资料,发现很多大神的源码分析,瞬间信心爆棚,把大神写的博客都 ...

  6. 例子Architecting Android…The clean way?----代码分析

    Presention层:   整个应用启动的时候,就执行依赖的初始化.编译项目之后,Dagger依赖框架使用ApplicationComponent生成一个DaggerApplicationCOmpo ...

  7. Android 7.0系统代码调用安装apk时报错FileUriExposedException完美解决

    项目更新遇到问题   Android项目开发中经常遇到下载更新的需求,以前调用系统安装器执行安装操作代码如下: Intent intent = new Intent(); intent.setActi ...

  8. 吴裕雄--天生自然Android开发学习:android 背景相关与系统架构分析

    1.Android背景与当前的状况 Android系统是由Andy Rubin创建的,后来被Google收购了:最早的版本是:Android 1.1版本 而现在最新的版本是今年5.28,Google ...

  9. 考勤系统代码分析——主页布局easyui框架

    考勤系统主页的布局用的是easyui的Layout控件 Layout:布局容器有5个区域:北.南.东.西和中间.中间区域面板是必须的,边缘的面板都是可选的.每个边缘区域面板都可以通过拖拽其边框改变大小 ...

随机推荐

  1. 给网站添加运行时间的JavaScript完整代码

    function secondToDate(second) { if (!second) { return 0; } var time = new Array(0, 0, 0, 0, 0); if ( ...

  2. 12、scala函数式编程集合

    1.Scala的集合体系结构 2.List 3.LikedList 4.Set 5.集合的函数式编程 6.函数式编程综合案例:统计单词总数 1.Scala的集合体系结构 Scala中集合体系主要包括: ...

  3. TensorFlow: Could not load requested Qt binding.

    使用Eclipse 引入tensorflow,出现 Could not load requested Qt binding.  问题 ImportError: Could not load reque ...

  4. iOS crash log 解析 symbol address = stack address - slide 运行时获取slide的api 利用dwarfdump从dsym文件中得到symbol

    概述: 为什么 crash log 内 Exception Backtrace 部分的地址(stack address)不能从 dsym 文件中查出对应的代码? 因为 ASLR(Address spa ...

  5. 转录组入门(3):了解fastq测序数据

    sra文件转换为fastq格式 fastq-dump -h --split-3 也就是说如果SRA文件中只有一个文件,那么这个参数就会被忽略.如果原文件中有两个文件,那么它就会把成对的文件按*_1.f ...

  6. javascript模块化编程(一)(http://www.ruanyifeng.com/blog/2012/10/javascript_module.html)

    Javascript模块化编程(一):模块的写法   随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个 ...

  7. js的基础运用

    总结: 1.定义:分为隐式定义和显式定义可以先定义后赋值. 2.+:当两边都是数值则运行加法运算,若一遍是字符型则进行拼接. 3.数值变字符:数值变量.toString()方法. 字符变数值:通过加一 ...

  8. lua_note_01_lua介绍

    1. lua 1. lua 1.1. lua介绍 1.2. Lua 特性 1.3. 特点 1.4. Lua 应用场景 1.5. 环境搭建 1.6. VS lua 1.1. lua介绍 Lua 是一种轻 ...

  9. 消除input框的默认样式

    input, button, select, textarea { outline: none; -webkit-appearance: none; border-radius: 0; } outli ...

  10. Excel表格

    自己一个一个试出来,并写上解释. 还不熟练,待多写代码多练习. #!/usr/bin/python # -*- coding:utf-8 -*- import os import xlwt impor ...