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系統實戰開發】,關注有驚喜哦!

在這裏插入圖片描述

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