前言
紧接着Android系统升级流程上篇,在上篇中,大概介绍了调用installPackage方法后发生的一系列的事,在这期间,系统准备好升级包,向misc分区中写入升级指令,接着重启进入recovery模式,本篇文章作为Android系统升级流程下篇,大概介绍重启后发生的事。
概述
一般来讲,Android有三种启动模式:Fastboot模式,Recovery System 以及Main System。那系统开机的时候,是根据什么来判断进入对应模式的呢?或者说,系统怎么判断要进入recovery模式的呢?
如何进入Recovery模式
第一种方式通过组合按键进入recovery模式,当系统上电开机的时候,会去检测此时是否有组合按键触发,如果有,则进入相应的模式,主要检测的代码如下路径:
bootable/bootloader/lk/app/aboot/aboot.c
void aboot_init(const struct app_descriptor *app)
{
....
/* Check if we should do something other than booting up */
if (keys_get_state(KEY_HOME) != 0)
boot_into_recovery = 1;
if (keys_get_state(KEY_VOLUMEUP) != 0)
boot_into_recovery = 1;
if(!boot_into_recovery)
{
if (keys_get_state(KEY_BACK) != 0)
goto fastboot;
if (keys_get_state(KEY_VOLUMEDOWN) != 0)
goto fastboot;
}
#if NO_KEYPAD_DRIVER
if (fastboot_trigger())
goto fastboot;
#endif
reboot_mode = check_reboot_mode(); //这里进行检测,但通常都为空,无定义
if (reboot_mode == RECOVERY_MODE) {
boot_into_recovery = 1;
} else if(reboot_mode == FASTBOOT_MODE) {
goto fastboot;
}
if (target_is_emmc_boot()) //系统如果支持EMMC,则从EMMC中获取
{
if(emmc_recovery_init()) //从BCB中读取指令,若发现recovery模式,则设置标志位boot_into_recovery为1
dprintf(ALWAYS,"error in emmc_recovery_init\n");
if(target_use_signed_kernel())
{
if((device.is_unlocked) || (device.is_tampered))
{
#ifdef TZ_TAMPER_FUSE
set_tamper_fuse_cmd();
#endif
#if USE_PCOM_SECBOOT
set_tamper_flag(device.is_tampered);
#endif
}
}
boot_linux_from_mmc();
}
else
{
recovery_init();
#if USE_PCOM_SECBOOT
if((device.is_unlocked) || (device.is_tampered))
set_tamper_flag(device.is_tampered);
#endif
boot_linux_from_flash();
}
....
}
当系统上电的时候,没有检测到有组合按键的触发时,则会通过第二种方式去检测,该检测方法是在bootloader阶段去读取SMEM里面的reboot_mode,通过该变量来决定系统系统接下来应该进入哪种模式。
如果reboot_mode 的值没有定义(一般都没有定义),则读取MISC 分区的BCB 进行判断(还记得上篇中说的往BCB写入recovery指令么),这里会先判断系统是否支持EMMC,如果支持,则读取EMMC内容,否则读取flash里面的内容。不管是从哪里读取,最后都会调用函数为emmc_recovery_init()或者recovery_init(),具体实现都在如下文件中:
\bootable\bootloader\lk\app\aboot\recovery.c
两个函数功能一样,这里选择recovery_init函数来进行解读:
int recovery_init (void)
{
struct recovery_message msg;
char partition_name[32];
unsigned valid_command = 0;
int update_status = 0;
// get recovery message
if (get_recovery_message(&msg))
return -1;
if (msg.command[0] != 0 && msg.command[0] != 255) {
dprintf(INFO, "Recovery command: %.*s\n", sizeof(msg.command), msg.command);
}
msg.command[sizeof(msg.command)-1] = '\0'; //Ensure termination
if (!strcmp("boot-recovery",msg.command))
{
if(!strcmp("RADIO",msg.status))
{
/* We're now here due to radio update, so check for update status */
int ret = get_boot_info_apps(UPDATE_STATUS, (unsigned int *) &update_status);
if(!ret && (update_status & 0x01))
{
dprintf(INFO,"radio update success\n");
strlcpy(msg.status, "OKAY", sizeof(msg.status));
}
else
{
dprintf(INFO,"radio update failed\n");
strlcpy(msg.status, "failed-update", sizeof(msg.status));
}
strlcpy(msg.command, "", sizeof(msg.command)); // clearing recovery command
set_recovery_message(&msg); // send recovery message
boot_into_recovery = 1; // Boot in recovery mode
return 0;
}
valid_command = 1;
strlcpy(msg.command, "", sizeof(msg.command)); // to safe against multiple reboot into recovery
strlcpy(msg.status, "OKAY", sizeof(msg.status));
set_recovery_message(&msg); // send recovery message
boot_into_recovery = 1; // Boot in recovery mode
return 0;
}
if (!strcmp("update-radio",msg.command)) {
dprintf(INFO,"start radio update\n");
valid_command = 1;
strlcpy(partition_name, "FOTA", sizeof(partition_name));
}
//Todo: Add support for bootloader update too.
if(!valid_command) {
//We need not to do anything
return 0; // Boot in normal mode
}
#ifdef OLD_FOTA_UPGRADE
if (read_update_header_for_bootloader(&header)) {
strlcpy(msg.status, "invalid-update", sizeof(msg.status));
goto SEND_RECOVERY_MSG;
}
if (update_firmware_image (&header, partition_name)) {
strlcpy(msg.status, "failed-update", sizeof(msg.status));
goto SEND_RECOVERY_MSG;
}
#else
if (set_ssd_radio_update(partition_name)) {
/* If writing to FOTA partition fails */
strlcpy(msg.command, "", sizeof(msg.command));
strlcpy(msg.status, "failed-update", sizeof(msg.status));
goto SEND_RECOVERY_MSG;
}
else {
/* Setting this to check the radio update status */
strlcpy(msg.command, "boot-recovery", sizeof(msg.command));
strlcpy(msg.status, "RADIO", sizeof(msg.status));
goto SEND_RECOVERY_MSG;
}
#endif
strlcpy(msg.status, "OKAY", sizeof(msg.status));
SEND_RECOVERY_MSG:
set_recovery_message(&msg); // send recovery message
boot_into_recovery = 1; // Boot in recovery mode
reboot_device(0);
return 0;
}
该函数主要做了如下事情:
先通过调用get_recovery_message()把BCB 读到recovery_message 结构体中,再读取其command 字段。如果字段是“boot-recovery”,则设置标志位boot_into_recovery为1,该标志位在boot_linux_from_flash函数中会用于判断是加载boot固件还是recovery固件。
获取到系统的启动模式后,则会开始执行boot_linux_from_flash函数,该函数实现如下:
bootable/bootloader/lk/app/aboot/aboot.c
int boot_linux_from_flash(void)
{
....
ptable = flash_get_ptable();
if (ptable == NULL) {
dprintf(CRITICAL, "ERROR: Partition table not found\n");
return -1;
}
if(!boot_into_recovery)
{
ptn = ptable_find(ptable, "boot");
if (ptn == NULL) {
dprintf(CRITICAL, "ERROR: No boot partition found\n");
return -1;
}
}
else
{
ptn = ptable_find(ptable, "recovery");
if (ptn == NULL) {
dprintf(CRITICAL, "ERROR: No recovery partition found\n");
return -1;
}
}
....
}
这里只列出部分代码,这里会通过判断boot_into_recovery的值,如果为0,则从flash/EMMC中获取boot固件,如果为1,则获取recovery固件。然后加载到DDR中执行。这里我们是进行系统升级,所以会加载recovery固件。
接下来我们就进入到recovery的世界,看看如何实现升级。
Recovery
recovery固件会包含kernel和recovery可执行文件,recovery固件执行时,其实也是启动kernel后,去执行recovery可执行文件。recovery的源码在如下目录:
bootable/recovery
每个可执行文件都是从main开始的,我们先进入main方法中查看:
int main(int argc, char **argv) {
....
//第一部分 {
redirect_stdio(TEMPORARY_LOG_FILE);
printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
load_volume_table();
has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
has_nvdata = volume_for_mount_point(NVDATA_ROOT) != nullptr;
mt_init_partition_type();
std::vector<std::string> args = get_args(argc, argv);
//第一部分 }
....
//第二部分 {
while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
&option_index)) != -1) {
switch (arg) {
...
case 'u':
update_package = optarg;
break;
case 'w':
should_wipe_data = true;
...
}
//第二部分 }
....
//第三部分 {
Device* device = make_device();
if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
printf("Quiescent recovery mode.\n");
ui = new StubRecoveryUI();
} else {
ui = device->GetUI();
if (!ui->Init(locale)) {
printf("Failed to initialize UI, use stub UI instead.\n");
ui = new StubRecoveryUI();
}
}
...
ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
device->StartRecovery();
//第三部分 }
//第四部分 {
if (update_package != nullptr) {
modified_flash = true;
if (!is_battery_ok()) { //判断电池的电是否足够,如果不够,则跳出
ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
BATTERY_OK_PERCENTAGE);
// Log the error code to last_install when installation skips due to
// low battery.
log_failure_code(kLowBattery, update_package);
status = INSTALL_SKIPPED;
} else if (bootreason_in_blacklist()) { //判断重启原因是否在黑名单里面,如Kernel_Panic或者Panic,如果是,则跳出
// Skip update-on-reboot when bootreason is kernel_panic or similar
ui->Print("bootreason is in the blacklist; skip OTA installation\n");
log_failure_code(kBootreasonInBlacklist, update_package);
status = INSTALL_SKIPPED;
} else {
if (retry_count == 0) {
set_retry_bootloader_message(retry_count + 1, args);
}
status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true,
retry_count);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
// When I/O error happens, reboot and retry installation RETRY_LIMIT
// times before we abandon this OTA update.
if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
copy_logs();
retry_count += 1;
set_retry_bootloader_message(retry_count, args);
// Print retry count on screen.
ui->Print("Retry attempt %d\n", retry_count);
// Reboot and retry the update
if (!reboot("reboot,recovery")) {
ui->Print("Reboot failed\n");
} else {
while (true) {
pause();
}
}
}
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error
// message is visible.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
}
}
//第四部分 }
....
//第五部分 {
if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
ui->SetBackground(RecoveryUI::ERROR);
if (!ui->IsTextVisible()) {
sleep(5);
}
}
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
finish_recovery();
switch (after) {
case Device::SHUTDOWN:
ui->Print("Shutting down...\n");
android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
break;
case Device::REBOOT_BOOTLOADER:
ui->Print("Rebooting to bootloader...\n");
android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
break;
default:
ui->Print("Rebooting...\n");
reboot("reboot,");
break;
}
while (true) {
pause();
}
// Should be unreachable.
return EXIT_SUCCESS;
//第五部分 }
}
main方法内容比较多,这里将整个main方法分为了五大部分,上面源码中有做了标志,大家可以对照着看。整个main方法主要做了五件事,如下:
第一部分
这里重定向log,加载分区表,获取recovery指令,通过get_args方法实现,如果参数未提供,则从BCB(Bootloader Control Block)查找,如果BCB中也没有则尝试在命令文件中查找相应的命令,将最终获得的参数写进BCB中,直到finish_recovery被调用完,再从BCB中清除。
第二部分
根据获取到的recovery指令,进行分支处理,如果是OTA升级,则走OTA分支,若是恢复出厂设置,则进行恢复出厂设置。其他分支亦然。这里只列出OTA和恢复出厂设置分支。
第三部分
初始化recovery UI,开始显示recovery界面,当系统为静默模式或者加载recovery UI失败的时候,会调用StubRecoveryUI获取UI,看源码注释,这个是给无屏幕设备使用的UI,类比Java中的一种抽象接口,无具体实现,即无具体的UI。这样做,估计也是为了更好的兼容,让下面的流程继续往下跑;
第四部分
检测到升级包非空,则进入升级流程。在进入升级前,会先检测设备电量是否充足,此次重启到该recovery模式下的原因是否属于黑名单内的原因,如kernel_panic或者panic,若是,则跳出升级流程。在满足所有条件的情况下,系统调用install_package方法进入真正的升级流程,且会向misc分区中写入升级次数,可以通过这个次数来判断由于意外重启导致的中断更新。如果升级失败,会尝试重启再次升级,最多可以尝试四次。
第五部分
recovery到了尾声,如果OTA升级失败,会在这里显示升级err,并持续5秒。在重启前,会调用finish_recovery方法进行一些结尾工作,清除recovery指令,避免下次重启还进入recovery模式,并且通过copy_log将recovery.log(即log重定向保存的log)拷贝到cache中,如last_log和last_kmsg等。结尾工作完成后,就直接重启了。
结语
本文简单介绍了系统在启动的时候,如何进入recovery模式。并将recovery的流程分为了5部分,大概介绍了升级流程以及一些升级失败处理。具体的install_package方法实现没在这展开分析,这一块的代码比较多,后续会找机会另起一篇文章进行分析。
最后
我在微信公众号也有写文章,更新比较及时,有兴趣者可以扫描如下二维码,或者微信搜索【Android系统实战开发】,关注有惊喜哦!