android recovery 系统代码分析【精】

2023-11-20

转自:http://blog.csdn.net/andyhuabing/article/details/9226569

http://blog.csdn.net/andyhuabing/article/details/9248713


最近做Recovery的规范及操作指导文档,花了一些时间将流程搞清。

Android利用Recovery模式,进行恢复出厂设置,OTA升级,patch升级及firmware升级。而在进入Recover前面其实还有升级检测,数据下载,启动检查等等操作。系列文章将会将整个流程梳理清楚。

1、Android启动流程

简要的流程图示,升级到新的版本或指定版本:


系统上电时的详细检测流程图:


下面我们从代码的情况简略分析一下:

机顶盒上电一般都是从地址 0x00000000 处开始启动,此时启动的程序叫 boot ,在linux上使用最多一般是 Uboot ,我们找到main函数:

Start.s文件:

.globl _start
_start:
b reset

做一些 bootstrap_check --> bootstrap --> check_boot_mode  etc 

最后:

ldr pc, _start_armboot  @ jump to C code 

跳转到C代码,类似就是main函数

void start_armboot (void)   首先做一些各种各式的硬件初始化,如 arch_cpu_init 、 board_init、interrupt_init、env_init 等

然后做一些开机启动的画面显示(开机动画也可以在此做)--> load_recovery() 这里就是决定是进行 Recovery 还是进行 Normal 的开机流程最重要入口函数

void load_recovery(void){

if (!strcmp(boot_select(), "kernel"))   // 进入检测参数ng
return;

check_buttom_recovery(&keycode)

        if(keycode == RECOVERY_KEY){  // 确认有长按键强制升级

              load_recovery_image();

        }

}

如何检测参数呢?参考的信息从哪里来,这里就有必要引入分区概念了。这里使用的都是 NAND FLASH ,对于其管理可以参考如下文章:

http://blog.csdn.net/andyhuabing/article/details/7824001

下列给出一个常用的android上分区表图:(常见的分区表,可根据项目另行修改大小及分区情况)



需要注意的是: NAND Flash 分区占用必须以 block size 为单位,并且要考虑分区的差错,坏块,建议容错块 30%,最小2个容错块。所以如果定义访问者有多个用户情况下,读取与写数据保持一致情况下,定义数据结构,预留30%分区空间,进行整个分区的读和写,这样子可以省去关注坏区情况,简化上层逻辑操作。


对于如何选择是启动正常的 "kernel "还是 "recovery",就是读取 misc 分区中的内容根据情况决定启动哪个。核心代码如下:

const char *boot_select(void)
{

HI_Flash_OpenByName("MISC");

        HI_Flash_Read(h,offset,buf,len);

        memcmp(buf,"boot-recovery",..); 还是 memcmp(buf,"kernel",..);


return "kernel" / "recovery";

}


另个一种模式就是长按键强制选择:

static int check_buttom_recovery( HI_U32 *keyvalue)
{

       HI_KEYLED_Open()

        HI_KEYLED_GetValue(&u32PressStatus, &u32KeyValue);

        while(u32KeyValue == RECOVERY_KEY){

                  udelay(1000);

                  if (s32Cnt >= MAX_KEY_PRESS_COUNT)
break;

                  s32Cnt++;

        }


ok, 开机启动选择进入的模式流程基本搞定。下一遍进行 Recovery 代码继续分析。



阅读完上一篇文章: 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.   
  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();

一个简单的基于framebufferui系统,叫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.   
  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...\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系统完成显示任务。而其他功能就靠它自己了。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android recovery 系统代码分析【精】 的相关文章

  • View系列 (三) — Measure 流程详解

    Measure 流程详解 一 概述 二 单一 View 的测量流程 1 流程图 2 源码分析 三 ViewGroup 的测量流程 1 流程图 2 源码分析 一 概述 测量过程分为 View的measure过程 和 ViewGroup的mea
  • android之媒体硬解OMX的实现

    转自 http blog csdn net vincent blog article details 7578112 android的多媒体部分采用的编解码标准是OMX 当然这个标准是用于硬件编解码的 软件编解码在这里我就不说了 直接从st
  • android 启动过程分析

    Servicemanager需要先启动 zygote后面的service需要用到servicemanager的服务
  • 《Linux设备节点创建》用户空间ueventd创建设备节点规则

    转自 http blog csdn net tankai19880619 article details 11726371 说明 本文基于Android2 3和Linux2 6 其余版本仅供参考 一 devfs udev和sysfs是什么关
  • Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析 setContentView源码阅读 的讲解 本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 不得不翻阅 ActivityThrea
  • android 系统级应用和服务的启动流程

    activityManagerService java 1 systemRaady 收到systemReady 通知 2 AppGlobals getPackageManager getPersistentApplications STOC
  • http://blog.csdn.net/haomcu/article/details/7267090

    转自 http blog csdn net haomcu article details 7267090 一 Android平台Wifi的基本代码路径 1 Wpa supplicant源码部分 external wpa supplicant
  • 为什么ViewGroup的onDraw()方法不执行

    问题 ViewGroup onDraw不执行的原因 怎么让ViewGroup onDraw执行 android代码一直在优化 我看了几个版本的源码 目前 我用的是API30的源码 再去看ViewGroup为什么不走onDraw 的时候 已经
  • 1-APP启动源码分析-1

    桌面app也就是我们认识的launcher app 点击app icon启动到app内部的过程分为2种情况 一种是冷启动 一种叫热启动 冷启动 系统没有创建过app的进程 也就是后台没有此app进程 所以冷启动系统会创建一个新的进程分配给a
  • Python+uiautomator2手机UI自动化测试实战 --1. 环境搭建

    转自 https blog csdn net ricky yangrui article details 81414870 一 简介 uiautomator2是一个python库 用于Android的UI自动化测试 其底层基于Google
  • Android apk安装管理(PackageManagerService 分析)

    Android apk安装管理 PackageManagerService 分析 本篇主要分析了系统启动阶段包管理服务的启动流程 其中的几个接口在apk安装时也会被调用 包管理服务启动时主要做的工作大致有如下几方面 1 建立java层的in
  • Activity启动流程

    简述 Activity 启动分为两种 1 Activity中通过startActivity 方法启动一个Activity 2 从桌面通过点击应用图标启动一个App然后显示Activity 我们通过第二点来分析 更全面一点 先走一波流程图 以
  • Android Display System --- Surface Flinger

    转自一醉千年大大 http blog csdn net yili xie archive 2009 11 12 4803527 aspx SurfaceFlinger 是Android multimedia 的一个部分 在Android 的
  • Android源码分析 - Service启动流程

    开篇 本篇以android 11 0 0 r25作为基础解析 在之前的文章中 我们已经分析过了四大组件中Activity和ContentProvider的启动流程 这次我们就来讲讲四大组件之一的Service是如何启动和绑定的 流程图 在查
  • MediaScanner生成及保存thumbnail的方式

    转自 http blog csdn net qikaibinglan article details 6130589 本文简单研究一下MediaScanner生成及保存thumbnail的方式 并给出代码快速查询图片的thumbnail 1
  • android MediaPlayer 中的JNI总结

    1 在android media MediaPlayer cpp 中 定义fields静态变量 里面有两个重要的成员变量 context 用来保存创建的mediaplayer post event 用来将JNI层的事件回调给JAVA层 实现
  • android input 机制源码分析

    具体文字说明请参考 http blog csdn net luoshengyang article details 6882903
  • Dalvik虚拟机简要介绍和学习计划

    通过修改 android framework base core jni AndroidRuntime cpp 中的 property get dalvik vm heapsize heapsizeOptsBuf 4 16m 来修改 dal
  • android 中的的 sp/wp/RefBase

    转自 http blog csdn net innost article details 6752443 5 1 概述 初次接触Android源码时 见到最多的一定是sp和wp 即使你只是沉迷于Java世界的编码 那么Looper和Hand
  • android recovery 系统代码分析【精】

    转自 http blog csdn net andyhuabing article details 9226569 http blog csdn net andyhuabing article details 9248713 最近做Reco

随机推荐