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
### MySQL Binary Logs Usage and Configuration Binary logs, often referred to as binlogs, play an essential role in the replication process of MySQL databases. These logs record all changes made to the database, including table creation, updates, deletions, but not queries that do not modify data[^1]. The primary purpose is to enable point-in-time recovery and facilitate replication between master and slave servers. #### Types of Binary Log Files Binlog files are categorized into several types based on their function: - **Master Binary Log**: This file records transactions executed by the master server before they are sent to any slaves. It serves as a source for replicating events from the master to one or more slaves. - **Slave Relay Log**: On each slave server, this log contains copies of events received from the master's binary log. Once these events have been applied locally, entries will be removed from the relay log automatically after some time depending upon configuration settings. - **Error Logs**: Although not directly related to transactional activities like other two categories mentioned above, error logs provide valuable information about issues encountered during operation which can help diagnose problems affecting both masters and replicas[^2]. #### Managing Binlog Directory The location where binary logs reside depends largely on how `my.cnf` has configured it within your environment. By default, without specifying otherwise through parameters such as `--datadir`, binaries would typically go under `/var/lib/mysql/`. However, administrators may choose alternative locations using directives inside my.cnf specific sections dedicated either globally (`[mysqld]`) or per-service instance basis(`[mysqld_safe]`). For example: ```ini [mysqld] log-bin=/path/to/binlogdir/mysql-bin.log expire_logs_days=7 max_binlog_size=100M ``` This snippet sets up logging at /path/to/binlogdir while also configuring automatic cleanup policies ensuring only seven days worth plus limiting individual sizes accordingly[^3].
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值