Android 系統Recovery工作原理
Android系統Recovery模式的工作原理
在使用update.zip包更新時怎樣從主系統(main system)重新開機進入Recovery模式,進入Recovery模式後怎樣判斷做何種操作,以及怎樣獲得主系統發送給Recovery服務的指令,這一系列問題的解決是通過整個軟體平台的不同部分之間的密切通信配合來完成的。為此,我們必須要了解Recovery模式的工作原理,這樣才能知道我們的update.zip包是怎樣一步步進入Recovery中更新并最後到達主系統的。
一、 Recovery模式中的三個部分
Recovery的工作需要整個軟體平台的配合,從通信架構上來看,主要有三個部分。
①MainSystem:即上面提到的正常啟動模式(BCB中無指令),是用boot.img啟動的系統,Android的正常工作模式。更新時,在這種模式中我們的上層操作就是使用OTA或者從SD卡中更新update.zip包。在重新開機進入Recovery模式之前,會向BCB中寫入指令,以便在重新開機後告訴bootloader進入Recovery模式。
②Recovery:系統進入Recovery模式後會裝載Recovery分區,該分區包含recovery.img(同boot.img相同,包含了标準的核心和根檔案系統)。進入該模式後主要是運作Recovery服務(/sbin/recovery)來做相應的操作(重新開機、更新update.zip、擦除cache分區等)。
③Bootloader:除了正常的加載啟動系統之外,還會通過讀取MISC分區(BCB)獲得來至Mainsystem和Recovery的消息。
二、 Recovery模式中的兩個通信接口
在Recovery服務中上述的三個實體之間的通信是必不可少的,他們互相之間又有以下兩個通信接口。
(一)通過CACHE分區中的三個檔案:
Recovery通過/cache/recovery/目錄下的三個檔案與mian system通信。具體如下
①/cache/recovery/command:這個檔案儲存着Main system傳給Recovery的指令行,每一行就是一條指令,支援以下幾種的組合。
--send_intent=anystring
//write the text out to recovery/intent
在Recovery結束時在finish_recovery函數中将定義的intent字元串作為參數傳進來,并寫入到/cache/recovery/intent中
--update_package=root:path
//verify install an OTA package file
Main system将這條指令寫入時,代表系統需要更新,在進入Recovery模式後,将該檔案中的指令讀取并寫入BCB中,然後進行相應的更新update.zip包的操作。
--wipe_data
//erase user data(and cache),then reboot。
擦除使用者資料。擦除data分區時必須要擦除cache分區。
--wipe_cache
//wipe cache(but not user data),then reboot。
擦除cache分區。
②/cache/recovery/log:Recovery模式在工作中的log列印。在recovery服務運作過程中,stdout以及stderr會重定位到/tmp/recovery.log在recovery退出之前會将其轉存到/cache/recovery/log中,供檢視。
③/cache/recovery/intent:Recovery傳遞給Main system的資訊。作用不詳。
(二)通過BCB(BootloaderControl Block):
BCB是bootloader與Recovery的通信接口,也是Bootloader與Main system之間的通信接口。存儲在flash中的MISC分區,占用三個page,其本身就是一個結構體,具體成員以及各成員含義如下:
structbootloader_message{
charcommand[32];
charstatus[32];
charrecovery[1024];
};
①command成員:當我們想要在重新開機進入Recovery模式時,會更新這個成員的值。另外在成功更新後結束Recovery時,會清除這個成員的值,防止重新開機時再次進入Recovery模式。
②status:在完成相應的更新後,Bootloader會将執行結果寫入到這個字段。
③recovery:可被Main System寫入,也可被Recovery服務程式寫入。該檔案的内容格式為:
“recovery\n
<recovery command>\n
<recovery command>”
該檔案存儲的就是一個字元串,必須以recovery\n開頭,否則這個字段的所有内容域會被忽略。“recovery\n”之後的部分,是/cache/recovery/command支援的指令。可以将其了解為Recovery操作過程中對指令操作的備份。Recovery對其操作的過程為:先讀取BCB然後讀取/cache/recovery/command,然後将二者重新寫回BCB,這樣在進入Main system之前,確定操作被執行。在操作之後進入Main system之前,Recovery又會清空BCB的command域和recovery域,這樣確定重新開機後不再進入Recovery模式。
三、 如何從MainSystem重新開機并進入Recovery模式
我們先看一下以上三個部分是怎樣進行通信的,先看下圖:
我們隻看從Main System如何進入Recovery模式,其他的通信在後文中詳述。先從Main System開始看,當我們在Main System使用update.zip包進行更新時,系統會重新開機并進入Recovery模式。在系統重新開機之前,我們可以看到,Main System定會向BCB中的command域寫入boot-recovery(粉紅色線),用來告知Bootloader重新開機後進入recovery模式。這一步是必須的。至于Main System是否向recovery域寫入值我們在源碼中不能肯定這一點。即便如此,重新開機進入Recovery模式後Bootloader會從/cache/recovery/command中讀取值并放入到BCB的recovery域。而Main System在重新開機之前肯定會向/cache/recovery/command中寫入Recovery将要進行的操作指令。
至此,我們就大概知道了,在上層使用update.zip更新時,主系統是怎樣告知重新開機後的系統進入Recovery模式的,以及在Recovery模式中完成什麼樣的操作。
Recovery服務流程細節
Recovery服務毫無疑問是Recovery啟動模式中最核心的部分。它完成Recovery模式所有的工作。Recovery程式對應的源碼檔案位于:
/bootable/recovery/recovery.cpp。
一、 Recovery的三類服務:
先看一下在這個源碼檔案中開始部分的一大段注釋,這将對我們了解Recovery服務的主要功能有很大幫助。
從注釋中我們可以看到Recovery的服務内容主要有三類:
①FACTORY RESET,恢複出廠設定。
②OTA INSTALL,即我們的update.zip包更新。
③ENCRYPTED FILE SYSTEM ENABLE/DISABLE,使能/關閉加密檔案系統。
具體的每一類服務的大概工作流程,注釋中都有,我們在下文中會詳細講解OTA INSTALL的工作流程。這三類服務的大概的流程都是通用的,隻是不同操作展現與不同的操作細節。下面我們看Recovery服務的通用流程。
二. Recovery服務的通用流程:
在這裡我們以OTA INSTALL的流程為例具體分析。并從相關函數的調用過程圖開始,如下圖:
我們順着流程圖分析,從recovery.c的main函數開始:
1.ui_init():Recovery服務使用了一個基于framebuffer的簡單ui(miniui)系統。這個函數對其進行了簡單的初始化。在Recovery服務的過程中主要用于顯示一個背景圖檔(正在安裝或安裝失敗)和一個進度條(用于顯示進度)。另外還啟動了兩個線程,一個用于處理進度條的顯示(progress_thread),另一個用于響應使用者的按鍵(input_thread)。
2.get_arg():這個函數主要做了上圖中get_arg()往右往下直到parse arg/v的工作。我們對照着流程一個一個看。
①get_bootloader_message():主要工作是根據分區的檔案格式類型(mtd或emmc)從MISC分區中讀取BCB資料塊到一個臨時的變量中。
②然後開始判斷Recovery服務是否有帶指令行的參數(/sbin/recovery,根據現有的邏輯是沒有的),若沒有就從BCB中讀取recovery域。如果讀取失敗則從/cache/recovery/command中讀取然後。這樣這個BCB的臨時變量中的recovery域就被更新了。在将這個BCB的臨時變量寫回真實的BCB之前,又更新的這個BCB臨時變量的command域為“boot-recovery”。這樣做的目的是如果在更新失敗(比如更新還未結束就斷電了)時,系統在重新開機之後還會進入Recovery模式,直到更新完成。
③在這個BCB臨時變量的各個域都更新完成後使用set_bootloader_message()寫回到真正的BCB塊中。
這個過程可以用一個簡單的圖來概括,這樣更清晰:
3.parserargc/argv:解析我們獲得參數。注冊所解析的指令(register_update_command),在下面的操作中會根據這一步解析的值進行一步步的判斷,然後進行相應的操作。
4.if(update_package):判斷update_package是否有值,若有就表示需要更新更新包,此時就會調用install_package()(即圖中紅色的第二個階段)。在這一步中将要完成安裝實際的更新包。這是最為複雜,也是更新update.zip包最為核心的部分。我們在下一節詳細分析這一過程。為從宏觀上了解Recovery服務的架構,我們将這一步先略過,假設已經安裝完成了。我們接着往下走,看安裝完成後Recovery怎樣一步步結束服務,并重新開機到新的主系統的。
5.if(wipe_data/wipe_cache):這一步判斷實際是兩步,在源碼中是先判斷是否擦除data分區(使用者資料部分)的,然後再判斷是否擦除cache分區。值得注意的是在擦除data分區的時候必須連帶擦除cache分區。在隻擦除cache分區的情形下可以不擦除data分區。
6.maybe_install_firmware_update():如果更新包中包含/radio/hboot firmware的更新,則會調用這個函數。檢視源碼發現,在注釋中(OTA INSTALL)有這一個流程。但是main函數中并沒有顯示調用這個函數。目前尚未發現到底是在什麼地方處理。但是其流程還是向上面的圖示一樣。即,① 先向BCB中寫入“boot-recovery”和“—wipe_cache”之後将cache分區格式化,然後将firmware image 寫入原始的cache分區中。②将指令“update-radio/hboot”和“—wipe_cache”寫入BCB中,然後開始重新安裝firmware并重新整理firmware。③之後又會進入圖示中的末尾,即finish_recovery()。
7.prompt_and_wait():這個函數是在一個判斷中被調用的。其意義是如果安裝失敗(update.zip包錯誤或驗證簽名失敗),則等待使用者的輸入處理(如通過組合鍵reboot等)。
8.finish_recovery():這是Recovery關閉并進入Main System的必經之路。其大體流程如下:
① 将intent(字元串)的内容作為參數傳進finish_recovery中。如果有intent需要告知Main System,則将其寫入/cache/recovery/intent中。這個intent的作用尚不知有何用。
② 将記憶體檔案系統中的Recovery服務的日志(/tmp/recovery.log)拷貝到cache(/cache/recovery/log)分區中,以便告知重新開機後的Main System發生過什麼。
③ 擦除MISC分區中的BCB資料塊的内容,以便系統重新開機後不在進入Recovery模式而是進入更新後的主系統。
④ 删除/cache/recovery/command檔案。這一步也是很重要的,因為重新開機後Bootloader會自動檢索這個檔案,如果未删除的話又會進入Recovery模式。原理在上面已經講的很清楚了。
9.reboot():這是一個系統調用。在這一步Recovery完成其服務重新開機并進入Main System。這次重新開機和在主系統中重新開機進入Recovery模式調用的函數是一樣的,但是其方向是不一樣的。是以參數也就不一樣。檢視源碼發現,其重新開機模式是RB_AUTOBOOT。這是一個系統的宏。
至此,我們對Recovery服務的整個流程架構已有了大概的認識。下面就是更新update.zip包時特有的也是Recovery服務中關于安裝更新包最核心的第二個階段。即我們圖例中的紅色2的那個分支。