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