在android系統的手機啟動時,按下 (音量下+power) 組合鍵(大多數如此,也有例外)可進入recovery模式。此recovery模式一個重要的功能便是進行系統更新,這是OTA功能實作的基礎和關鍵。由于前段時間一直在進行OTA項目的開發,是以将recovery模式下代碼分析整理出來,以備不時之須。
recovery模式的代碼存在于源碼目錄下的bootable目錄中,主函數位于./bootable/recovery/recovery.c檔案中。我們将主函數的代碼複制如下,并對其中核心代碼進行分析。
int
main(int argc, char **argv) {
time_t start = time(NULL);
// If these fail, there's not really anywhere to complain...
freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
printf("Starting recovery on %s", ctime(&start));
device_ui_init(&ui_parameters);
ui_init();
ui_set_background(BACKGROUND_ICON_INSTALLING);
load_volume_table(); //加載系統分區表
get_args(&argc, &argv); //擷取參數資訊,包括需要執行的動作(更新、清除/data、清除cache等)及更新包路徑
int previous_runs = 0;
const char *send_intent = NULL; //通過finish_recovery函數傳遞到main system下的資訊
const char *update_package = NULL; //更新包路徑
int wipe_data = 0, wipe_cache = 0;
int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
case 'p': previous_runs = atoi(optarg); break;
case 's': send_intent = optarg; break;
case 'u': update_package = optarg; break; //解析獲得更新包路徑
case 'w': wipe_data = wipe_cache = 1; break;
case 'c': wipe_cache = 1; break;
case 't': ui_show_text(1); break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
}
#ifdef HAVE_SELINUX
struct selinux_opt seopts[] = {
{ SELABEL_OPT_PATH, "/file_contexts" }
};
sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
if (!sehandle) {
fprintf(stderr, "Warning: No file_contexts\n");
ui_print("Warning: No file_contexts\n");
}
#endif
device_recovery_start();
printf("Command:");
for (arg = 0; arg < argc; arg++) {
printf(" \"%s\"", argv[arg]);
}
printf("\n");
if (update_package) {
// For backwards compatibility on the cache partition only, if
// we're given an old 'root' path "CACHE:foo", change it to
// "/cache/foo".
if (strncmp(update_package, "CACHE:", 6) == 0) {
int len = strlen(update_package) + 10;
char* modified_path = malloc(len);
strlcpy(modified_path, "/cache/", len);
strlcat(modified_path, update_package+6, len);
printf("(replacing path \"%s\" with \"%s\")\n",
update_package, modified_path);
update_package = modified_path;
}
}
printf("\n");
property_list(print_property, NULL);
printf("\n");
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);
if (status == INSTALL_SUCCESS && wipe_cache) {
if (erase_volume("/cache")) {
LOGE("Cache wipe (requested by package) failed.");
}
}
if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n");
} else if (wipe_data) {
if (device_wipe_data()) status = INSTALL_ERROR;
if (erase_volume("/data")) status = INSTALL_ERROR;
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n");
} else if (wipe_cache) {
if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n");
} else {
status = INSTALL_ERROR; // No command specified
}
if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
if (status != INSTALL_SUCCESS || ui_text_visible()) {
prompt_and_wait();
}
// Otherwise, get ready to boot the main system...
finish_recovery(send_intent);
ui_print("Rebooting...\n");
android_reboot(ANDROID_RB_RESTART, 0, 0);
return EXIT_SUCCESS;
}
代碼段1 ./bootable/recovery/recovery.c檔案中的main函數,即系統進入到recovery模式執行的主流程
代碼分析如下:
代碼06-08:打開臨時日志檔案TEMPORARY_LOG_FILE(定義在recovery.c的開始部分),并寫入資訊。注意:此檔案裡的資訊最終會被寫入到日志檔案LAST_LOG_FILE
中,LAST_LOG_FILE的定義如下:static const char *LAST_LOG_FILE = "/cache/recovery/last_log";這就意味着可以獲得系統更新時詳細的LOG資訊,這對于
OTA項目的開發具有相當重要的作用;
代碼10-12:UI相關的操作,未研究,略去;
代碼 13:加載recovery模式下系統分區資訊,由于需要對某些分區進行讀寫操作,故需此步。此分區資訊以檔案形式存在,路徑為:./device/<生産廠商>/<産品型号>/
recovery.fstab;比如三星crespo4g系列此檔案在源碼下的全路徑為:./device/samsung/crespo4g/recovery.fstab,其内容如下所示:
# mount point fstype device
/sdcard vfat /dev/block/platform/s3c-sdhci.0/by-name/media
/system ext4 /dev/block/platform/s3c-sdhci.0/by-name/system
/cache yaffs2 cache
/data ext4 /dev/block/platform/s3c-sdhci.0/by-name/userdata
/misc mtd misc
/boot mtd boot
/recovery mtd recovery
/bootloader mtd bootloader
/radio mtd radio
代碼段2 recovery.fstab示例
代碼 14:調用get_args()擷取由正常模式下傳入到recovery模式下的參數,這些參數包括執行動作(系統更新、清空data分區、清空cache分區等)及更新包路徑,擷取來源為
BCB(結構體bootloader_message)或COMMAND_FILE(/cache/recovery/command),詳細資訊将在後文介紹;
代碼21-34:解析get_args所獲得的參數,如果是在進行OTA操作,我們将通過第26行擷取更新包的路徑update_package;
代碼57-93中,系統将根據不同的指令進行不同的操作,包括更新、清除/data分區、清除/cache分區等。
代碼57-70:對update_package的預處理,本人所遇到的情況是:在main system傳入的路徑為/mnt/sdcard/update.zip,而在recovery下需将其轉換為/sdcard/update.zip,相
關的操作便在此處進行(當然本文使用的為android源碼,并未加入自己改動的代碼);
代碼78-85:更新操作。其實核心代碼隻有一句,第79行調用install_package函數将更新包中的資料寫入到系統相應分區中,我們将在後文中詳細介紹此函數;
代碼86-90:清空/data分區(貌似就是恢複出廠設定吧?我沒詳細研究,不敢妄言);
代碼91-93:清空/cache分區;
代碼99-101:當更新失敗或者我們是手動進入recovery模式時,将調用prompt_and_wait(),顯示recovery模式下的互動界面,我們在後文中再詳細介紹;
代碼 104: 調用finish_recovery函數清空BCB塊(防止重複進入recovery模式)、将send_intent寫入檔案(傳遞到main system)、将日志寫入到LAST_LOG_FILE中等;
代碼 106: 系統重新開機。
以上即系統進入recovery模式下的執行流程,其中涉及到的關鍵步驟:如何擷取參數(get_args()),系統更新(install_package())及互動界面的顯示與操作(prompt_and_w-
ait())等将在下一篇文章中進行分析。