该系列文章总纲链接:专题分纲目录 Android系统升级 Recovery模式


本章关键点总结 & 说明:

导图是不断迭代的,这里主要关注➕ recovery模式启动部分即可,主要从 更新包简介,mian函数解析开始解读,分析了main函数中关键的方法 获取参数getargs和执行菜单命令prompt_and_wait。

对于recovery模式,一般均采用第三方的方案,比如:在使用MTK平台时,我们这边一般会直接采用广升FOTA的服务商直接进行升级相关的工作。对于第三方的recovery代码,实际上也是参考android原生代码进行改动,核心原理不变,因此这里对recovery模式的代码研究还是采用 google 的原生recovery代码。

android 启动时会通过组合键 判定是否进入recovery模式,也可以通过android的RecoverySystem来进入。recovery模式下还是会启动bootloader、kernel,最后会通过bootargs来判定,如果进入recovery模式则加载recovery专属的rc文件,进而进入到recovery模式。

recovery模式主要是使用升级包进行升级,升级包解压后一般是这样

包含的文件简要说明:

  1. boot.img:更新boot分区所需要的文件。这个boot.img主要包括kernel+ramdisk。
  2. system/  :内容在升级后会放在系统的system分区。主要用来更新系统的一些应用或则应用会用到的一些库
  3. recovery/ :中的recovery-from-boot.p是boot.img和recovery.img的补丁(patch),主要用来更新recovery分区,其中etc/目录下的install-recovery.sh是更新脚本。
  4. META-INF放的是更新包的签名文件和更新脚本,只有更新包的签名和设备签名匹配才能进行系统升级,包含三个关键文件。

最后META-INF的几个文件解读如下所示:

  1. CERT.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名JAR文件的公共签名。
  2. CERT.SF:这是JAR文件的签名文件,其中前缀CERT代表签名者。
  3. MANIFEST.MF:manifest文件定义了与包的组成结构相关的数据。
  4. update-binary:二进制文件,相当于脚本解释器,能够识别updater-script中描述的操作。
  5. updater-script:脚本文件,具体描述了更新过程。我们可以根据具体情况编写该脚本来适应我们的具体需求。
  6. metadata:描述设备信息及环境变量的元数据。主要包括一些编译选项,签名公钥,时间戳以及设备型号等。

接下来我们从main函数启动开始分析,到如何传递参数,以及如何执行菜单命令 角度来逐步分析。

1 main函数启动分析

int main(int argc, char **argv) {time_t start = time(NULL);redirect_stdio(TEMPORARY_LOG_FILE);if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {adb_main();//如果参数中有adb,作为adbd的daemon启动return 0;}//读取/etc/recovery.fstab文件,保存了recovery模式下分区情况(名称+参数)load_volume_table();ensure_path_mounted(LAST_LOG_FILE);rotate_last_logs(KEEP_LOG_COUNT);//获得启动参数(按照优先级,分别是recovery->misc->/cache/recovery/command中命令)get_args(&argc, &argv);const char *send_intent = NULL;const char *update_package = NULL;int wipe_data = 0, wipe_cache = 0, show_text = 0;bool just_exit = false;bool shutdown_after = false;int arg;//解析启动参数while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {switch (arg) {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; //擦除cachecase 't': show_text = 1; break;//指示升级时是否显示UIcase 'x': just_exit = true; break; //退出case 'l': locale = optarg; break; //指定locale//...case '?':LOGE("Invalid command argument\n");continue;}}//...//Device很多函数是空函数,设计上留给厂商实现。Device* device = make_device();ui = device->GetUI();gCurrentUI = ui;ui->SetLocale(locale);ui->Init();int st_cur, st_max;if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) {ui->SetStage(st_cur, st_max);}ui->SetBackground(RecoveryUI::NONE);if (show_text) ui->ShowText(true);//SElinux相关device->StartRecovery();//空函数printf("Command:");for (arg = 0; arg < argc; arg++) {printf(" \"%s\"", argv[arg]);}printf("\n");if (update_package) {//预处理更新命令if (strncmp(update_package, "CACHE:", 6) == 0) {int len = strlen(update_package) + 10;char* modified_path = (char*)malloc(len);strlcpy(modified_path, "/cache/", len);strlcat(modified_path, update_package+6, len);printf("(replacing path \"%s\" with \"%s\")\n",update_package, modified_path);update_package = modified_path;}}printf("\n");property_list(print_property, NULL);property_get("ro.build.display.id", recovery_version, "");printf("\n");int status = INSTALL_SUCCESS;if (update_package != NULL) {//如果更新系统status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);if (status == INSTALL_SUCCESS && wipe_cache) {//更新成功,启动并擦出cacheif (erase_volume("/cache")) {LOGE("Cache wipe (requested by package) failed.");}}if (status != INSTALL_SUCCESS) {ui->Print("Installation aborted.\n");char buffer[PROPERTY_VALUE_MAX+1];property_get("ro.build.fingerprint", buffer, "");if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) {ui->ShowText(true);//屏幕上打印失败版本信息}}} else if (wipe_data) {//擦除数据if (device->WipeData()) status = INSTALL_ERROR;if (erase_volume("/data")) status = INSTALL_ERROR;if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");} else if (wipe_cache) {//擦除cacheif (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");} else if (!just_exit) {//只是退出status = INSTALL_NONE;  // No command specifiedui->SetBackground(RecoveryUI::NO_COMMAND);}if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {copy_logs();//执行命令错误,屏幕上显示标志ui->SetBackground(RecoveryUI::ERROR);}Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {//进入菜单模式Device::BuiltinAction temp = prompt_and_wait(device, status);if (temp != Device::NO_ACTION) after = temp;}// Save logs and clean up before rebooting or shutting down.finish_recovery(send_intent);//根据after参数设置,关闭/重启系统switch (after) {case Device::SHUTDOWN:ui->Print("Shutting down...\n");property_set(ANDROID_RB_PROPERTY, "shutdown,");break;case Device::REBOOT_BOOTLOADER:ui->Print("Rebooting to bootloader...\n");property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");break;default:ui->Print("Rebooting...\n");property_set(ANDROID_RB_PROPERTY, "reboot,");break;}sleep(5); // should reboot before this finishesreturn EXIT_SUCCESS;
}

main函数并不复杂,主要实现了几个关键功能:

  1. 加载recovery.fstab分区表
  2. 获取启动参数
  3. UI初始化 ( 厂商接口)
  4. 解析并执行相关命令(升级/擦除数据 /擦除cache/退出)
  5. 显示菜单(如果失败,显示失败的版本信息)
  6. 退出recovery,根据参数,关闭/重启系统

2 如何传递参数

2.1 关键结构体bootloader_message

Bootloader和reovery模块以及主系统分区通信主要是通过misc来完成的,misc的数据结构是bootloader_message,定义如下:

struct bootloader_message {char command[32];//bootloader 启动时读取改数据,决定是否进入recovery模式char status[32]; //由recovery或者bootloader进行更新,标识升级的结果;/* 由Android系统进行写入,recovery从中读取信息,以recovrery开头,后面是recovery中执行的命令,命令以\n分割 */char recovery[768];// The 'recovery' field used to be 1024 bytes.  It has only ever// been used to store the recovery command line, so 768 bytes// should be plenty.  We carve off the last 256 bytes to store the// stage string (for multistage packages) and possible future// expansion.char stage[32];char reserved[224];
};

command命令详细说明:

  1. 如果command的值是boot-recovery,则进入recovery模式
  2. 如果command的值是update-radia /update-hboot,则进入firmware模式,由bootloader更新
  3. 如果command的值是NULL,则进入主系统,正常启动。

recovery命令详细说明:存放的是recovery模块启动时的参数。

2.2 getargs获取参数(misc分区相关)

代码如下:

static void get_args(int *argc, char ***argv) {struct bootloader_message boot;memset(&boot, 0, sizeof(boot));//读取misc分区命令到boot变量中get_bootloader_message(&boot);  // this may fail, leaving a zeroed structurestage = strndup(boot.stage, sizeof(boot.stage));//...if (*argc <= 1) {//如果命令没有传递参数boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure terminationconst char *arg = strtok(boot.recovery, "\n");//从misc分区中读取命令建立启动参数,这样argc和argv就会有新的值。if (arg != NULL && !strcmp(arg, "recovery")) {*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);(*argv)[0] = strdup(arg);for (*argc = 1; *argc < MAX_ARGS; ++*argc) {if ((arg = strtok(NULL, "\n")) == NULL) break;(*argv)[*argc] = strdup(arg);}} else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {}}if (*argc <= 1) {//如果从misc分区中没有读到命令//COMMAND_FILE 为/cache/recovery/commandFILE *fp = fopen_path(COMMAND_FILE, "r");if (fp != NULL) {char *token;char *argv0 = (*argv)[0];*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);(*argv)[0] = argv0;  // use the same program namechar buf[MAX_ARG_LENGTH];//使用读取的文件内容建立启动参数for (*argc = 1; *argc < MAX_ARGS; ++*argc) {if (!fgets(buf, sizeof(buf), fp)) break;token = strtok(buf, "\r\n");if (token != NULL) {(*argv)[*argc] = strdup(token);  // Strip newline.} else {--*argc;}}check_and_fclose(fp, COMMAND_FILE);}}//把启动参数放到boot对象中strlcpy(boot.command, "boot-recovery", sizeof(boot.command));strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));int i;for (i = 1; i < *argc; ++i) {strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));strlcat(boot.recovery, "\n", sizeof(boot.recovery));}set_bootloader_message(&boot);
}

这里继续分析上面提到的关键点

@1 get_bootloader_message的实现如下:

int get_bootloader_message(struct bootloader_message *out) {Volume* v = volume_for_path("/misc");//打开misc分区//...if (strcmp(v->fs_type, "mtd") == 0) {//mtd格式return get_bootloader_message_mtd(out, v);} else if (strcmp(v->fs_type, "emmc") == 0) {//emmc格式return get_bootloader_message_block(out, v);}LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);return -1;
}

该方法主要是从misc分区读取数据。

@2 set_bootloader_message的实现如下:

int set_bootloader_message(const struct bootloader_message *in) {Volume* v = volume_for_path("/misc");//...if (strcmp(v->fs_type, "mtd") == 0) {return set_bootloader_message_mtd(in, v);} else if (strcmp(v->fs_type, "emmc") == 0) {return set_bootloader_message_block(in, v);}LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type);return -1;
}

这里把参数回写到misc分区,这样做是为了防止升级过程中发生崩溃,重启仍然可以从misc分区读取到更新的命令,继续进行更新操作,这也是为什么getargs要从几个地方读取启动参数的原因。

@3 finish_recovery函数

如果recovery模式下正常退出,则会清理掉misc分区中的内容,关键代码内容如下:

static void finish_recovery(const char *send_intent) {//...// Reset to normal system boot so recovery won't cycle indefinitely.struct bootloader_message boot;memset(&boot, 0, sizeof(boot));set_bootloader_message(&boot);//...sync();  // For good measure.
}

这里就是向misc分区中写入0。

3 执行菜单命令

执行菜单关键的函数是prompt_and_wait,这里打印屏幕菜单并接收用户输入,函数代码如下:

static Device::BuiltinAction prompt_and_wait(Device* device, int status) {const char* const* headers = prepend_title(device->GetMenuHeaders());for (;;) {finish_recovery(NULL);//根据命令执行,修改UI背景switch (status) {case INSTALL_SUCCESS:case INSTALL_NONE:ui->SetBackground(RecoveryUI::NO_COMMAND);break;case INSTALL_ERROR:case INSTALL_CORRUPT:ui->SetBackground(RecoveryUI::ERROR);break;}ui->SetProgressType(RecoveryUI::EMPTY);//等待用户输入int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);//用户选择权 交给device对象处理Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item);int wipe_cache = 0;//处理菜单命令switch (chosen_action) {case Device::NO_ACTION:break;case Device::REBOOT:case Device::SHUTDOWN:case Device::REBOOT_BOOTLOADER:return chosen_action;case Device::WIPE_DATA:wipe_data(ui->IsTextVisible(), device);if (!ui->IsTextVisible()) return Device::NO_ACTION;break;case Device::WIPE_CACHE:ui->Print("\n-- Wiping cache...\n");erase_volume("/cache");ui->Print("Cache wipe complete.\n");if (!ui->IsTextVisible()) return Device::NO_ACTION;break;case Device::APPLY_EXT: {//sdcard卡上更新ensure_path_mounted(SDCARD_ROOT);char* path = browse_directory(SDCARD_ROOT, device);if (path == NULL) {ui->Print("\n-- No package file selected.\n", path);break;}//...break;}//...case Device::APPLY_ADB_SIDELOAD://启动adbd,也是卡刷入口status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);//...break;}}
}

整个函数的逻辑是:在屏幕上打印菜单,之后等待用户输入(用户只能通过音量+ 音量-来上下选择,power键确认),用户输入后,根据用户输入的命令,做不同的处理,命令与处理的对应关系如下:

命令 处理方式
REBOOT 重启
WIPE_DATA 擦除data分区,这就是恢复出厂设置所进行的操作,清除手机上所有的用户数据,包括cache分区
WIPE_CACHE 仅擦出cache分区下的内容
APPLY_EXT 通过UI在sdcard上选择一个文件进行更新操作
APPLY_CACHE 同上,只是路径变成了cache
APPLY_ADB_SIDELOAD 启动adbd(注意:这里adbd只是一个mini版本),让用户通过adb连接来执行sideload命令上传,更新文件到/tmp/update.zip,然后再执行更新操作。

Android系统升级 Recovery模式(01)Recovery模式启动相关推荐

  1. 【android系统】android系统升级流程分析(一)---recovery模式中进行update包升级流程分析

    今天我们直接来看下android中具体的升级过程是如何的. 升级流程概述 升级的流程图: 升级流程分析 第一步:升级包获取 升级获取可以通过远程下载,也可直接拷贝到指定目录即可. 第二步:准备升级 然 ...

  2. Android系统升级 Recovery模式(02)Recovery升级过程

    该系列文章总纲链接:专题分纲目录 Android系统升级 Recovery模式 本章关键点总结 & 说明: 导图是不断迭代的,这里主要关注➕ recovery升级过程部分即可,主要从 一般升级 ...

  3. android recovery中文下载地址,Android Recovery模式与Recovery界面的中文详细说明

    Android Recovery模式与Recovery界面的中文详细说明 Android Recovery模式 来源: ChinaUnix博客 日期: 2009.04.25 22:58 (共有条评论) ...

  4. android+rom+bootloader+flash,Android ROM开发(4) bootloader 三种启动模式

    Andrew Huang 转载请注明作者及网址 HTC手机中用的bootloader称为HBoot,不知道是不是HTC Bootloader 之意,从官网的源码看这个bootloader不象是u-bo ...

  5. android rom 工作,Android ROM开发(4) bootloader 三种启动模式

    Andrew Huang 转载请注明作者及网址 HTC手机中用的bootloader称为HBoot,不知道是不是HTC Bootloader 之意,从官网的源码看这个bootloader不象是u-bo ...

  6. fastboot模式和recovery模式

    目录 fastboot模式和recovery模式简介 进入Fastboot的方式 Fastboot使用方式 解释 选项 分区 进入模式 命令操作 准备工具 具体步骤 使用fastboot刷写分区时报错 ...

  7. media recovery oracle,Oracle非归档模式Media Recovery错误之--ORA-26040

    Oracle非归档模式MediaRecovery错误之--ORA-26040系统环境:操作系统:LinuxRH55Oracle:Oracle11gR2模拟案例:1.查看数据库模式18:12:36SYS ...

  8. VMware | Workstation中如何进入恢复模式(Recovery)?

    我为什么要进入恢复模式? 因为Mac OS出于保护系统的目的,加入了Rootless机制,不再能够随心所欲的读写很多路径下的东西了,直接导致我无法创建符号链接到:"/usr/bin" ...

  9. VMware15Pro进入MacOS10.14恢复模式(Recovery)

    1.在VMware界面中选中需要启动的MacOS页面中,点击启动按钮后面向下箭头,点击"打开电源时进入固件". 2.进入到引导界面中,用方向键选中"Enter setup ...

最新文章

  1. 【转】Android APK反编译就这么简单 详解(附图)
  2. umi不输出html文件,部署 - UmiJS 中文文档
  3. wxWidgets:wxSashWindow类用法
  4. 9个典型的开发者关系面试题
  5. struts2获取请求参数的三种方式及传递给JSP参数的方式
  6. 泛型类java_Java泛型 - 通用类( Generic Classes)
  7. Linux Linux常用命令二
  8. Javascript读取本地文件
  9. 终极算法——第二章:终极算法
  10. Himall商城LinqHelper帮助类(2)
  11. 图·思·腾·想·宗教符号
  12. 如何处理phpmyadmin中访问被拒绝
  13. 告别传智,重新上路----近5年来的总结及未来5年的计划
  14. PHP尚能饭否?八个项目告诉你老牌语言如何绽放新的生命力
  15. 信用风险评估评分卡 之 极端值
  16. 面向气象灾害预警信息的5G网络切片技术研究
  17. matlab小点轨迹仿真,无碳小车Matlab轨迹仿真及路径图
  18. 利用CMailServer搭建邮件服务器
  19. Redis—苹果Mac电脑os系统安装Redis步骤
  20. Win常见文件后缀名

热门文章

  1. Android 禁止横竖屏切换、设置横屏
  2. 投诉申告:招行竟然如此对待零存整取客户?
  3. golang环境安装异常【已解决】
  4. 渗透测试技术----提权(第三方提权和WCE)
  5. How to listen to music for free
  6. android7 root方案包,一加7Pro安卓10/9.0安装面具ROOT方案
  7. PS教程——复古人物海报效果
  8. Java——书单列表BookList
  9. tp5连接Workerman
  10. 部署项目出现Mysql数据聚合不合法