recovery升级是显示进度条_Android recovery 升级流程

1.Android的启动模式

一般来讲,Android有三种启动模式:Fastboot模式,Recovery System 以及Main System。Fastboot:在这种模式下,可以修改手机的硬件,并且允许我们发送一些命令给

Bootloader。如使用电脑刷机,则需要进入fastboot模式,通过电脑执行命令将系统镜像刷到通过USB刷到Android设备中中。

Recovery:Recovery是一个小型的操作系统,并且会加载部分文件系统,这样才能从sdcard中读取升级包。

Main System: 即我们平时正常开机后所使用的手机操作系统模式

首先说一下,正常启动和进入Recovery的区别,一图以概之:bootloader选择开机进入recovery还是Android系统

上图中的上下两个部分,上面一部分是正常的启动模式,下面一部分为Recovery模式。正常的启动模式是从boot.img启动系统(Main System),而recovery模式则是从reovery.img启动系统;(reovery.img只包含内核、简单的文件管理系统和图形系统)

Boot分区包括linux内核和Ramdisk,Recovery分区也包括Linux内核和Ramdisk,一般来说内核是一样的,但Ramdisk区别则非常大,recovery中的ramdisk会有更多的recovery需要使用的程序和数据。这里说到的boot.img和recovery.img,其实就分别对应了Android设备中的boot和recovery分区。

2.Recovery升级原理

2.1 应用层升级流程

在Android应用层部分,OTA系统升级流程。大概的流程图如下所示:应用程序升级流程图

以上部分,只介绍了应用层层面的 ota升级包的下载、校验以及最后的发起安装过程。在这里,重要讲解进入Recovery模式后,OTA包的升级过程。

首先,在应用层下载升级包后,会调用RecoverySystem.installPackage(Context context, File packageFile)函数来发起安装过程,这个过程主要的原理,实际上只是往 /cache/recovery/command 写入ota升级包存放路径,然后重启到recovery模式,仅此而已。

public static void installPackage(Context context, File packageFile)

throws IOException {

String filename = packageFile.getCanonicalPath();

Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");

final String filenameArg = "--update_package=" + filename;

final String localeArg = "--locale=" + Locale.getDefault().toString();

bootCommand(context, filenameArg, localeArg);

}

private static void bootCommand(Context context, String... args) throws IOException {

RECOVERY_DIR.mkdirs(); // In case we need it COMMAND_FILE.delete(); // In case it's not writable LOG_FILE.delete();

FileWriter command = new FileWriter(COMMAND_FILE);

try {

for (String arg : args) {

if (!TextUtils.isEmpty(arg)) {

command.write(arg);

command.write("\n");

}

}

} finally {

command.close();

}

// Having written the command file, go ahead and reboot PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

pm.reboot(PowerManager.REBOOT_RECOVERY);

throw new IOException("Reboot failed (no permissions?)");

}

因此,实质上等同于以下命令:

echo -e "--update_package=/mnt/sdcard/ota/update.zip" > /cache/recovery/command

reboot recovery

2.2 OTA升级包的目录结构

OTA升级包的目录结构大致如下所示:

其中:

boot.img 是更新boot分区所需要的镜像文件。这个boot.img主要包括kernel、ramdisk。

system/目录的内容在升级后会放在系统的system分区,主要是系统app,library和binary二进制文件

recovery/目录主要用来更新recovery分区, install-recovery.sh是用来更新recovery分区的脚本。

update-binary是一个二进制文件,相当于一个脚本解释器,能够识别updater-script中描述的操作。

updater-script:此文件是一个脚本文件,具体描述了更新过程。

metadata文件是描述设备信息及环境变量的元数据。主要包括一些编译选项,签名公钥,时间戳以及设备型号等。

我们还可以在包中添加userdata目录,来更新系统中的用户数据部分。这部分内容在更新后会存放在系统的/data目录下。

update.zip包的签名:update.zip更新包在制作完成后需要对其签名,否则在升级时会出现认证失败的错误提示。而且签名要使用和目标板一致的加密公钥。默认的加密公钥及加密需要的三个文件在Android源码编译后生成的具体路径为:out/host/linux-x86/framework/signapk.jar

build/target/product/security/testkey.x509.pem

build/target/product/security/testkey.pk8MANIFEST.MF:这个manifest文件定义了与包的组成结构相关的数据。类似Android应用的mainfest.xml文件。

CERT.RSA:与签名文件相关联的签名程序块文件,它存储了用于签名JAR文件的公共签名。

CERT.SF:这是JAR文件的签名文件,其中前缀CERT代表签名者。

2.3 Recovery模式下的OTA升级流程

进入Recovery模式之后,便开始对下载的升级包进行升级,整体的流程图如下所示:

这里,详解介绍一下升级流程中的各个模块。

1. get_args(&argc, &argv)

get_args的原理流程图如下所示:

get_args()函数的主要作用是获取系统的启动参数,并回写到bootloader control block(BCB)块中。如果系统在启动recovery时已经传递了启动参数,那么这个函数只是把启动参数的内容复制到函数的参数boot对象中,否则函数会首先通过get_bootloader_message()函数从/misc分区的BCB中获取命令字符串来构建启动参数。如果/misc分区下没有内容,则会尝试解析/cache/recovery/command文件并读取文件的内容来建立启动参数。

接着,会把启动参数的信息通过set_bootloader_message()函数又保存到了BCB块中。这样做的目的是防止一旦升级或擦除数据的过程中发生崩溃或不正常断电,下次重启,Bootloader会依据BCB的指示,引导进入Recovery模式,从/misc分区中读取更新的命令,继续进行更新操作。因此,可以说是一种掉电保护机制。

get_args具体的流程如下图所示:

get_args函数核心代码如下:

static void get_args(int *argc, char ***argv) {

struct bootloader_message boot;

memset(&boot, 0, sizeof(boot));

//解析BCB模块 get_bootloader_message(&boot); // this may fail, leaving a zeroed structure

......

// --- if that doesn't work, try the command file if (*argc <= 1) {

FILE *fp = fopen_path(COMMAND_FILE, "r");//COMMAND_FILE指/cache/recovery/command if (fp != NULL) {

char *argv0 = (*argv)[0];

*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);

(*argv)[0] = argv0; // use the same program name

char buf[MAX_ARG_LENGTH];

for (*argc = 1; *argc < MAX_ARGS; ++*argc) {

if (!fgets(buf, sizeof(buf), fp)) break;

(*argv)[*argc] = strdup(strtok(buf, "\r\n")); // Strip newline. }

check_and_fclose(fp, COMMAND_FILE);

LOGI("Got arguments from %s\n", COMMAND_FILE);

}

}

......

set_bootloader_message(&boot); //回写BCB

这里需要说一下“BCB”,即bootloader control block, 中文可以呼之为“启动控制模信息块”,位于/misc分区,从代码上看,就是一个struct 结构体 :

struct bootloader_message {

char command[32];

char status[32];

char recovery[1024];

};

bootloader_message 结构体包含三个字段,具体含义如下:

command 字段中存储的是命令,它有以下几个可能值:

* boot-recovery:系统将启动进入Recovery模式

* update-radia 或者 update-hboot:系统将启动进入更新firmware的模式,这个更新过程由bootloader完成

* NULL:空值,系统将启动进入Main System主系统,正常启动。

status 字段存储的是更新的结果。更新结束后,由Recovery或者Bootloader将更新结果写入到这个字段中。

recovery 字段存放的是recovry模块的启动参数,一般包括升级包路径。其存储结构如下:第一行存放字符串“recovery”,第二行存放路径信息“–update_package=/mnt/sdcard/update.zip”等。 因此,参数之间是以“\n”分割的。

2. update_package

ota升级包的存放路径,从BCB或者/cache/recovery/command里面解析得到的,升级包一般下载后存放在cache或sdcard分区,当然,也有一些是存放到U盘之类的外接存储设备中的。一般赋值格式如下:--update_package=/mnt/sdcard/update.zip 或 --update_package=CACHE:update.zip

3. int install_package (const char* path, int* wipe_cache, const char* install_file)

install_package函数所实现的功能为:调用really_install_package函数进行ota升级,并将升级结果写入到文件install_file(/cache/recovery/last_install)中。当升级完成后进入main system,可以通过此文件读取升级结果,并给出用户相应的提示:比如升级成功或失败。

int install_package(const char* path, int* wipe_cache, const char* install_file)

{

//install_file 为 /cache/recovery/last_install FILE* install_log = fopen_path(install_file, "w");

if (install_log) {

fputs(path, install_log);

fputc('\n', install_log);

} else {

LOGE("failed to open last_install: %s\n", strerror(errno));

}

int result = really_install_package(path, wipe_cache);

if (install_log) {

fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);

fputc('\n', install_log);

fclose(install_log);

}

return result;

}

4. static int really_install_package(const char path, int wipe_cache)

really_install_package函数在install_package函数中被调用,函数的主要作用是调用ensure_path_mounted确保升级包所在的分区已经挂载,另外,还会对升级包进行一系列的校验,

在具体升级时,对update.zip包检查时大致会分三步:

检验SF文件与RSA文件是否匹配;

检验MANIFEST.MF与签名文件中的digest是否一致;

检验包中的文件与MANIFEST中所描述的是否一致

通过校验后,调用try_update_binary函数去实现真正的升级。

5. static int try_update_binary(const char path, ZipArchive *zip, int wipe_cache)

try_update_binary是真正实现对升级包进行升级的函数:

static int try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {

const ZipEntry* binary_entry = mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);

......

const char* binary = "/tmp/update_binary";

unlink(binary);

int fd = creat(binary, 0755);

.....

//将升级包里面的update_binary解压到/tmp/update_binary bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);

close(fd);

mzCloseZipArchive(zip);

......

int pipefd[2];

pipe(pipefd);

const char** args = (const char**)malloc(sizeof(char*) * 5);

args[0] = binary; //update_binary存放路径 args[1] = EXPAND(RECOVERY_API_VERSION); // Recovery版本号 char* temp = (char*)malloc(10);

sprintf(temp, "%d", pipefd[1]);

args[2] = temp;

args[3] = (char*)path; //升级包存放路径 args[4] = NULL;

pid_t pid = fork();//fork一个子进程 if (pid == 0) {

close(pipefd[0]);

//子进程调用update-binary执行升级操作 execv(binary, (char* const*)args);

fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));

_exit(-1);

}

......

int status;

waitpid(pid, &status, 0);

if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {

//安装失败,返回INSTALL_ERROR return INSTALL_ERROR;

}

//安装成功,返回INSTALL_SUCCESS return INSTALL_SUCCESS;

}

总的来说,try_update_binary主要做了以下几个操作:

(1)mzOpenZipArchive():打开升级包,并将相关的信息拷贝到一个临时的ZipArchinve变量中。注意这一步并未对我们的update.zip包解压。

(2)mzExtractZipEntryToFile(): 解压升级包特定文件,将升级包里面的META-INF/com/google/android/update-binary 解压到内存文件系统的/tmp/update_binary中。

(3)fork创建一个子进程 , 使用系统调用函数execv( ) 去执行/tmp/update-binary程序,

(4)update-binary: 这个是Recovery OTA升级的核心程序,是一个二进制文件,实现代码位于系统源码bootable/recovery/updater。其实质是相当于一个脚本解释器,能够识别updater-script中描述的操作并执行。

(5)updater-script:updater-script是我们升级时所具体使用到的脚本文件,具体描述了更新过程,它主要用以控制升级流程的主要逻辑。具体位置位于升级包中/META-INF/com/google/android/update-script,在我们制作升级包的时候产生。在升级的时候,由update_binary程序从升级包里面解压到内存文件系统的/tmp/update_script中,并按照update_script里面的命令,对系统进行升级。比如,一个完整包升级的update_script的内容大致如下所示:

assert(getprop("ro.product.device") == "rk31sdk" ||

getprop("ro.build.product") == "rk301dk");

show_progress(0.500000, 0);

format("ext4", "EMMC", "/dev/block/mtd/by-name/system", "0", "/system");

mount("ext4", "EMMC", "/dev/block/mtd/by-name/system", "/system");

package_extract_dir("recovery", "/system");

package_extract_dir("system", "/system");

symlink("Roboto-Bold.ttf", "/system/fonts/DroidSans-Bold.ttf");

symlink("mksh", "/system/bin/sh");

......

set_perm_recursive(0, 0, 0755, 0644, "/system");

set_perm_recursive(0, 2000, 0755, 0755, "/system/bin");

......

set_perm(0, 0, 06755, "/system/xbin/su");

set_perm(0, 0, 06755, "/system/xbin/tcpdump");

show_progress(0.200000, 0);

show_progress(0.200000, 10);

write_raw_image(package_extract_file("boot.img"), "boot");

show_progress(0.100000, 0);

clear_misc_command();

unmount("/system");

update_script常用的命令如下:

因此,根据上面的升级脚本,可以知道,升级包的大致升级流程如下:

判断是不是升级包是否适用于该设备,如果不适用,则停止升级,否则继续。

显示进度条

格式化system分区

挂载system分区

将ota升级包里面的system、recovery目录解压到system分区

建立一些软链接,升级过程需要用到

设置部分文件权限

将升级包里面的boot.img写入到/boot分区

清空misc分区,即BCB块置为NULL

卸载system分区

6. wipe data/cache

main函数,在执行完install_package后,会根据传入的wipe_data/wipe_cache,决定是否执行/data和/cache分区的清空操作。

7. prompt_and_wait

这个函数的作用就是一直在等待用户输入,是一个不断的循环,可以选择Recovery模式下的一些选项进行操作,包括恢复出厂设置和重启等。如果升级失败, prompt_and_wait会显示错误,并等待用户响应。

8. finish_recovery

OTA升级成功,清空misc分区(BCB置零),并将保存到内存系统的升级日志/tmp/recovery.log保存到/cache/recovery/last_log。重启设备进入Main System,升级完成。

从上面的流程中,可以知道,Recovery模式下的OTA升级成功,只是更新了/system和/boot两个最核心的分区,而本身用来升级的Recovery自身并没有在那个时候得到更新。Recovery分区的更新,是在重启进入主系统的时候,由install-recovery.sh来更新的。这样可以保证,即使升级失败,Recovery模式也不会受到影响,仍然可以手动进入Recovery模式执行升级或擦除数据操作。

在Recovery升级的时候,有一句:package_extract_dir("recovery", "/system");

这条命令就是将升级包里面的recovery目录的内容,解压到/system分区

recovery目录下的文件,主要有install-recovery.sh和 recovery-from-boot.p,目录结构如下所示:

其中:

recovery-from-boot.p 是boot.img和recovery.img的补丁(patch)

install-recovery.sh - 这个网站可出售。 - 最佳的install-recovery 来源和相关信息。 则是来用安装recovery-from-boot.p的升级脚本, 主要是利用android系统的 applypatch 工具来打补丁。

至此,一个完整的OTA包升级就正式完成了!

4. Bootloader、BCB、Recovery与Main System之间的交互

首先,通过前面的介绍,可以知道, Recovery System与Main System的交互,主要是通过/cache分区下的文件进行信息交互的。具体如下:

其中,command的值一般有以下一个或多个:

其次,Bootloader与Recovery和Main System之间也是存在交互的: Bootloader会通过解析BCB模块,决定启动系统到Recovery或Main System。而Recovery或Main System也能够操作BCB,进而影响到Bootloader的行为。

当Main System系统关键进程崩溃太多次的时候,系统还会自发启动进入到Recovery模式。

另外,部分平台的Android设备,在Recovery模式下,也能够对Bootloader进行升级。

Bootloader、BCB、Recovery与Main System四者相互影响,又独立工作。它们之间斩不断理还乱的关系,可以以下图概括之:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值