Recovery系统的框架结构说明及常用的客制化修改流程

一、Recovery系统简介

  Recovery模式指的是一种可以对安卓机内部的数据或系统进行修改的模式(类似于windows PE或DOS)。在这个模式下我们可以刷入新的Android系统,或者对已有的系统进行备份或升级,也可以在此模式下恢复出厂设置。系统进入recovery模式后会装载recovery分区,该分区包含recovery.img(与boot.img类似,也包含了标准的内核和根文件系统).进入该模式后主要就是运行了recovery服务(/sbin/recovery)。

二、Recovery系统的启动流程

2.1 进入Recovery系统的三种常见方法:

  1. 在关机情况下,同时按住电源(Power)+ 音量加(Vol +)键,直到出现Recovery界面为止。注:有的系统按键方式可能不同。
  2. 使用安卓辅助工具,如:刷机精灵、360手机助手等等。
  3. 使用adb命令reboot recovery启动。

2.2 从reboot启动到Recovery服务的流程

 在Bootloader开始如果没有组合键按下,就从MISC分区读取BCB块的command字段(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。其后就与正常的系统启动类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件在bootable/recovery/etc/init.rc。这个文件的主要作用就是:

  1. 设置环境变量。
  2. 建立etc连接。
  3. 新建目录,备用。
  4. 挂载/tmp为内存文件系统tmpfs
  5. 启动recovery(/sbin/recovery)服务。
  6. 启动adbd服务(用于调试)。

这里最重要的当然就是启动recovery服务了。
重启到recovery模式的流程图如下:
recoveryRebootProcess.png

三、Recovery系统的框架结构

3.1 源码路径和主要原文件

 在Android源码环境中,recovery的源码主要在bootable/recovery文件夹下,另外在device目录下,会根据各个设备定制自己的接口以及UI界面。
 在bootable/recovery目录下,主要的源文件有:

LOCAL_SRC_FILES := \
    adb_install.cpp \ //设置usb驱动,升级系统
    asn1_decoder.cpp \ //解码asn1格式
    device.cpp \ //recovery的头部显示和列表项,和通过make_device方法实现一个device设备
    fuse_sdcard_provider.cpp \ //加载升级文件升级
    recovery.cpp \ //会最先执行recovery.cpp中的main方法,及清除data等方法
    roots.cpp \ //进行进行分区挂载操作
    rotate_logs.cpp \ //mstar添加的文件
    screen_ui.cpp \ //界面的绘制文件,初始化UI等
    ui.cpp \ //初始化输入设备,如初始化按键,背光等
    verifier.cpp \ //签名验证的功能实现方法
    wear_ui.cpp \ // 继承于ScreenRecoveryUI的UI
    wear_touch.cpp \ //界面的触摸事件响应

 该部分代码在编译后,会统一输出到 out/recovery/root/目录;

3.2 Recovery模式的三个部分

Recovery的工作需要整个软件平台的配合,从通信架构上来看,主要有三个部分。

  1. MainSystem:即上面提到的正常启动模式(BCB中无命令情况),是引导boot.img启动的系统,Android的正常工作模式。更新时,在这种模式下的操作就是往 /cache/recovery/command 文件中写入ota升级命令及包存放路径。在重启进入Recovery模式之前,会向BCB中写入命令,以便在重启后告诉bootloader进入Recovery模式。
  2. Recovery:系统进入Recovery模式后会装载Recovery分区,该分区包含recovery.img。进入该模式后主要是运行Recovery服务(/sbin/recovery)来做相应的操作(重启、升级update.zip、擦除cache分区等)。
  3. Bootloader:除了正常的加载启动系统之外,还会通过读取MISC分区(BCB)获得来至Main system和Recovery的消息。

3.3 Recovery模式的两个通信接口

 在Recovery服务中上述的三个实体之间的通信是必不可少的,他们相互之间又有以下两个通信接口。

3.3.1 通过CACHE分区中的三个文件通信

Recovery通过/cache/recovery/目录下的三个文件与mian system通信。具体如下

  1. /cache/recovery/command:这个文件保存着Main system传给Recovery的命令行,每一行就是一条命令,下表给出一些常用的命令及其含义:

    命令 取值 含义
    send_intent 字符串 Recovery结束后将字符串写到这里,
    然后写入/cache/recovery/intent,比如升级结果
    update_package 路径 安装OTA升级包的路径
    wipe_data 擦除userdata以及cache,然后重启
    wipe_cache 擦除cache,然后重启
    set_encrypted_filesystem on off
    just_exit 退出和重启
  2. /cache/recovery/last_log:Recovery模式在工作中的log打印。在recovery服务运行过程中,stdout以及stderr会重定位到/tmp/recovery.log在recovery退出之前会将其转存到/cache/recovery/last_log中,供查看。

  3. /cache/recovery/intent:Recovery传递给Main system的信息。列如反馈升级是否成功。

3.3.2 通过BCB(Bootloader Control Block)通信

 BCB是bootloader与Recovery的通信接口,也是Bootloader与Main system之间的通信接口。存储在flash中的MISC分区,占用三个page,其本身就是一个结构体,具体成员以及各成员含义如下:

struct bootloader_message{
   
	char command[32];
	char status[32];
	char recovery[1024];
};
  1. command成员:其可能的取值我们在上文已经分析过了,即当我们想要在重启进入Recovery模式时,Main System会将boot-recovery命令写入。另外在退出Recovery时,会清除这个成员的值,防止重启时再次进入Recovery模式。
  2. status:在完成相应的更新后,Bootloader会将执行结果写入到这个字段。
  3. recovery:可被Main System写入,也可被Recovery服务程序写入。该文件的内容格式为:
“recovery\n
<recovery command>\n
<recovery command>

 该文件存储的就是一个字符串,必须以recovery\n开头,否则这个字段的所有内容域会被忽略。“recovery\n”之后的部分,是/cache/recovery/command支持的命令。可以将其理解为Recovery操作过程中对命令操作的备份。Recovery对其操作的过程为:先读取BCB的recovery字段然后读取/cache/recovery /command,然后将二者重新写回BCB的recovery字段,这样在进入Main system之前,确保操作被执行。在操作之后进入Main system之前,Recovery又会清空BCB的command域和recovery域,这样确保重启后不再进入Recovery模式。

注意!这里比较容易弄混淆的点:
BCB中的command和/cache/recovery/command的内容不等价,且不同类型。

  • BCB中的command:决定了bootloader该去引导启动android系统还是recovery系统
  • BCB中的recovery:这个字段就是操作命令的备份,此处内容和/cache/recovery/command内容是等价的。

Recovery系统的三个部分和两个通信接口的示意图如下:
RecoverySketchMap (1).png

四、Recovery的主要源码分析

 在进入文件系统后会执行bootable/recovery/etc/init.rc,在init.rc中下面代码可知,进入recovery模式后会执行sbin/recovery,此文件是bootable/recovery.cpp生成的(查看Android.mk可知),所以recovery.cpp是recovery模式的入口。

service recovery /sbin/recovery
    seclabel u:r:recovery:s0

 因为recovery.cpp的main函数太长了,这里分块分析recovery的主要源码,其实在main函数中主要做了下面几件事情:

  • 设置adb进程。
  • log重定向到recovery.log。
  • 装载分区表,填充fstab结构体。
  • 读取控制参数。
  • 加载语言显示。
  • 加载UI模型。
  • 死循环prompt_and_wait,等待操作;
  • 退出recovery模式

 recovery.cpp的main方法执行的流程图大概如下:
RecoveryMainTimingDiagram.png

4.1 设置adb进程

 在recovery的main方法中首先判断命令行参数是否为–adbd,如果有则执行minadbd_main函数,这样是为了方便使用adb sideload命令,如果参数为-adbd的话,那么它会变成精简版adbd,只支持sideload命令。

if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
   
        minadbd_main();
        return 0;
    }

4.2 输出log重定向到recovery.log

 重定向标准输出和标准出错log到/tmp/recovery.log这个文件里,这个文件是临时log文件,在recovery模式finish的时候会将这个文件里面的log保存到/cache/recovery/last_log中。为了方便调试,可以将临时log重定位到控制台输出,修改参数:static const char ``*TEMPORARY_LOG_FILE = ``"/dev/console"``;

redirect_stdio(TEMPORARY_LOG_FILE);

4.3 装载分区表

 之后会调用roots.cpp文件中的load_volume_table()方法来初始化并装载recovery的分区表到fstab结构体中,load_volume_table()方法如下:

roots.cpp

void load_volume_table()
{
   
    int i;
    int ret;

    //加载分区表到fstab,具体就是去加载/etc/recovery.fstab这个文件,是Vold进程中的函数
    fstab = fs_mgr_read_fstab_default();
    if (!fstab) {
   
        LOG(ERROR) << "failed to read default fstab";
        return;
    }

    //将对应的信息加入到一条链表中
    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
    //如果load到的分区表为空,则做释放操作
    if (ret < 0 ) {
   
        LOG(ERROR) << "failed to add /tmp entry to fstab";
        fs_mgr_free_fstab(fstab);
        fstab = NULL;
        return;
    }

    //打印分区表信息,这类信息在recovery启动的时候在log中可以看到,具体形式如下:
    //编号|  挂载节点|  文件系统类型|  块设备|  长度
    printf("recovery filesystem table\n");
    printf("=========================\n");
    for (i = 0; i < fstab->num_entries; ++i) {
   
        Volume* v = &fstab->recs[i];
        printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
               v->blk_device, v->length);
    }
    printf("\n");
}

 上面提到的Vold进程是在kernel初始化的时候启动的,所有的热插拔设备都是通过Vold 进程挂载的,Vold的入口是/system/vold/main.cpp文件的main函数,fs_mgr_read_fstab_default()方法就是去解析/etc/recovery.fstab这个文件,上面具体log如下:

0 /vendor ext4 /dev/block/platform/mstar_mci.0/by-name/vendor 0
[ 12.959471] 1 /system ext4 /dev/block/platform/mstar_mci.0/by-name/system 0
[ 12.959476] 2 /system ext4 /dev/block/platform/mstar_mci.0/by-name/system 0
[ 12.959491] 3 /data ext4 /dev/block/platform/mstar_mci.0/by-name/userdata 0
[ 12.959496] 4 /cache ext4 /dev/block/platform/mstar_mci.0/by-name/cache 0
[ 12.959501] 5 /vendor ext4 /dev/block/platform/mstar_mci.0/by-name/vendor 0
[ 12.959506] 6 /tvservice ext4 /dev/block/platform/mstar_mci.0/by-name/tvservice 0
[ 12.959511] 7 /tvconfig ext4 /dev/block/platform/mstar_mci.0/by-name/tvconfig 0
[ 12.959517] 8 /tvdatabase ext4 /dev/block/platform/mstar_mci.0/by-name/tvdatabase 0
[ 12.959522] 9 /tvcustomer ext4 /dev/block/platform/mstar_mci.0/by-name/tvcustomer 0
[ 12.959527] 10 /tvcertificate ext4 /dev/block/platform/mstar_mci.0/by-name/tvcertificate 0
[ 12.959532] 11 /boot1 emmc /dev/block/mmcblk0boot0 0
[ 12.959537] 12 /boot2 emmc /dev/block/mmcblk0boot1 0
[ 12.959542] 13 /MBOOT emmc /dev/block/platform/mstar_mci.0/by-name/MBOOT 0
[ 12.959547] 14 /MPOOL emmc /dev/block/platform/mstar_mci.0/by-name/MPOOL 0
[ 12.959552] 15 /misc emmc /dev/block/platform/mstar_mci.0/by-name/misc 0
[ 12.959556] 16 /recovery emmc /dev/block/platform/mstar_mci.0/by-name/recovery 0
[ 12.959561] 17 /boot emmc /dev/block/platform/mstar_mci.0/by-name/boot 0
[ 12.959566] 18 /tee emmc /dev/block/platform/mstar_mci.0/by-name/tee 0
[ 12.959571] 19 /RTPM emmc /dev/block/platform/mstar_mci.0/by-name/RTPM 0
[ 12.959576] 20 /dtb emmc /dev/block/platform/mstar_mci.0/by-name/dtb 0
[ 12.959581] 21 /optee emmc /dev/block/platform/mstar_mci.0/by-name/optee 0
[ 12.959586] 22 /armfw emmc /dev/block/platform/mstar_mci.0/by-name/armfw 0
[ 12.959591] 23 auto auto /devices/platform/mstar_fcie* 0
[ 12.959595] 24 auto auto /devices/platform/mstar_sdio* 0
[ 12.959600] 25 auto auto /devices/Mstar-ehci* 0
[ 12.959605] 26 auto auto /devices/Mstar-xhci* 0
[ 12.959610] 27 /tmp ramdisk ramdisk 0

 挂载完相应的分区以后,就需要获取命令参数,因为只有挂载了对应的分区,才能访问到记录操作命令的/cache/recovery/command这个文件及BCB块,如果分区都没找到,那么当然就找不到分区上的文件,挂载分区这个步骤是至关重要的。

//从上面建立的分区表信息中读取是否有cache分区,因为log等重要信息都存在cache分区里
has_cache = volume_for_path(CACHE_ROOT) != nullptr;

// MStar Android Patch Begin
    if(has_cache){
   
        //mstar添加的确定是否有cache分区的方法
        ensure_path_mounted(CACHE_ROOT);
    }
// MStar Android Patch End

4.4 读取控制参数

 在main方法中通过get_args方法获取启动参数。

//从传入的参数或/cache/recovery/command文件中得到相应的命令
std::vector<std::string> args = get_args(argc, argv);

 recovery和bootloader要通过/misc才能相互通信,对应的信息数据结构体为bootloader_message;get_args(argc,argv)方法如下:

struct bootloader_message{
   
    char command[32];//bootloader 启动时读取改数据,决定是否进入recovery模式
    char status[32];//由bootloader进行更新,标识升级的结果;
    char recovery[768];//recovery要执行的命令,recovery从中读取信息;
    char stage[32]; // 恢复字段,它仅用于存储恢复命令行
    char reserved[1148]; // 保留字段
};

static std::vector<std::string> get_args(const int argc, char** const argv) {
   
  CHECK_GT(argc, 0);

  bootloader_message boot = {
   };//参数结构体
  std::string err;
  if (!read_bootloader_message(&boot, &err)) {
    // 从BCB中获取参数,这里有可能是为空的情况。
    LOG(ERROR) << err;
    // If fails, leave a zeroed bootloader_message.
    boot = {
   };
  }

  ...
  
  // 将启动recovery时的参数放入args,这里至少有一个/sbin/recovery元素
  std::vector<std::string> args(argv, argv + argc);
  
  // 去解析recovery字段的值,然后写入到args中
  if (args.size() == 1) {
   
    boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
    std::string boot_recovery(boot.recovery);
    std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
    if (!tokens.empty() && tokens[0] == "recovery") {
   
      for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
   
        // Skip empty and '\0'-filled tokens.
        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
      }
      LOG(INFO) << "Got " << args.size() << " arguments from boot message";
    } else if (boot.recovery[0] != 0) {
   
      LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
    }
  }
    
  // 如果上述情况为空,则从/cache/recovery/command获取参数,其中COMMAND_FILE=/cache/recovery/command
  if (args.size() == 1 && has_cache) {
   
    std::string content;
    if (ensure_path_mounted(COMMAND_FILE) == 0 &&
        android::base::ReadFileToString(COMMAND_FILE, &content)) {
   
      std::vector<std::string> tokens = android::base::Split(content, "\n");
      // All the arguments in COMMAND_FILE are needed (unlike the BCB message,
      // COMMAND_FILE doesn't use filename as the first argument).
      for (auto it = tokens.begin(); it != tokens.end(); it++) {
   
        // Skip empty and '\0'-filled tokens.
        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
      }
      LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;
    }
  }

  //将启动参数写入到BCB块的recovery字段中
  std::vector<std::string> options(args.cbegin() + 1, args.cend());
  if (!update_bootloader_message(options, &err)) {
   
    LOG(ERROR) << "Failed to set BCB message: " << err;
  }

  return args;
}

 get_args()函数的主要作用是建立recovery的启动参数,如果系统启动recovery时已经传递了启动参数,那么这个函数只是把启动参数的内容复制到函数的参数boot对象中,否则函数会首先从/misc分区中获取命令字符串来构建启动参数。如果/misc分区下没有内容,则尝试打开/cache/recovery/command文件并读取文件的内容来建立启动参数。从这个函数我们可以看到,更新系统最简单的方式是把更新命令写到/cache/recovery/command文件中。
 get_args()函数的结尾调用了update_bootloader_message()函数,函数的作用是把启动参数的信息又保存到了/misc分区的BCB的recovery字段,以及给command字段添加boot-recovery命令。这样做的目的是防止升级过程中发生崩溃,这样重启后仍然可以从/misc分区中读取更新的命令,继续进行更新操作。这也是为什么get_args()函数要从几个地方读取启动参数的原因。
 之后通过while循环解析获取到的参数,并把对应的功能设置为true或者给相应的变量赋值获取到对应的命令,后面会根据变量来执行对应的操作。

    while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
                              &option_index)) != -1) {
   
        switch (arg) {
   
        case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
        case 'u': update_package = optarg; break;
        case 'w': should_wipe_data = true; break;
        case 'c': should_wipe_cache = true; break;
        case 't': show_text = true; break;
        case 's': sideload = true; break;
        case 'a': sideload = true; sideload_auto_reboot = true; break;
        case 'x': just_exit = true; break;
        case 'l': locale = optarg; break;
        case 'p': shutdown_after = true; break;
        case 'r': reason = optarg; break;
        case 'e': security_update = true; break;
        case 0: {
   
            std::string option = OPTIONS[option_index].name;
            if (option == "wipe_ab") {
   
                should_wipe_ab = true;
            } else if (option == "wipe_package_size") {
   
                android::base::ParseUint(optarg, &wipe_package_size);
            } else if (option == "prompt_and_wipe_data") {
   
                should_prompt_and_wipe_data = true;
            }
            break;
        }
        // MStar Android Patch Begin
        case 'd': dev_uuid = optarg; break;
        case 'b': dev_label= optarg; break;
        // MStar Android Patch Begin
        case '?':
            LOG(ERROR) << "Invalid command argument";
            continue;
        }
    }

4.5 加载显示语言

 这个方法就是去判断/cache/recovery/last_locale文件是否存在,如果存在就读取里面的值,获取到的内容关系到显示那个国家的语言,如果没有获取到locale就使用默认的语言,848中的默认语言是英语。

if (locale.empty()) {
   
        if (has_cache) {
   
            locale = load_locale_from_cache();
        }

        if (locale.empty()) {
   
            locale = DEFAULT_LOCALE;
        }
    }

4.6 加载UI模式

加载UI界面的流程大概有下面几步:

  1. 新建一个Device类的对象;
  2. 调用Device类的GetUI()返回一个RecoveryUI对象,这里应该返回的是ScreenRecoveryUI,ScreenRecoveryUI继承于RecoveryUI;
  3. 调用Init()初始化UI;
  4. 调用RecoveryUi的init方法去设置国家语言,然后初始化输入设备,并创建一个线程用于监听输入事件;
  5. 调用minui库的gr_init方法初始化图形显示,主要是打开设备、分配内存、初始化一些参数;
  6. 通过LoadBitmap()加载png图片生成surface对象
  7. 创建一个子线程更新progress进度条;
  8. 调用SetBackground方法设置背景图片;

 Recovery中显示UI界面的framebuffer使用的是minui库,该库在网上也能查到相应的方法说明,下面会有详细介绍。

4.6.1 在main方法中

这里主要做了这几件事情:

  • 新建一个device设备;
  • 获取到UI;
  • 调用UI的Init方法进行初始化;
  • 设置背景;
	Device* device = make_device();//新建一个Device设备
    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
   
        //如果是静态UI模式则进入这里
        printf("Quiescent recovery mode.\n");
        ui = new StubRecoveryUI();
    } else {
   
        ui = device
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值