Android系统升级流程---下篇

前言

紧接着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系统实战开发】,关注有惊喜哦!

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章