平述factory reset——从main system到重引导流程
關于Android或linux的引導流程,網上大都是從開機開始講述的,或者直接跳過bootloader引導階段,直接從init進程開始說起。這里我從手機正常運行狀態開始,到重啟狀態以及重啟之后的狀態略做陳述,意在給讀者展開一個更加直白的整機引導框架。?
一、device重啟之前?
在手機的“setting–>備份與重置—>恢復出廠設置”里可以找到該設置,一旦執行了該設置,我們的手機便會恢復到原出廠設置狀態,當然里面的用戶數據、我們自行安裝的應用等都將被全部清除(有些選項是可選擇性刪除的,eg:內部空間上的音樂、圖片等)。下面一起看下恢復出廠設置的工作流程。?
操作中是從setting中進行的,當然代碼中我們也從settings中開始看起。?
settings中涉及到恢復出廠設置的源碼流程文件在MasterClearConfirm.java中。我們可以根據settings中的privacy_settings.xml進行查找,privacy_settings.xml是settings中主布局文件中的“備份與重置”Fragment選項,通過它我們可以找到“factory reset”的PreferenceScreen標簽:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
從中我們會發現對應Fragment是“com.android.settings.MasterClear”。到此我們就可以就找到了對應的java文件了——MasterClear.java。進入到該java文件后我們發現,在showFinalConfirmation()函數中真正加載的Fragment是“MasterClearConfirm.class”,如下所示:
private void showFinalConfirmation() {Preference preference = new Preference(getActivity());preference.setFragment(MasterClearConfirm.class.getName());preference.setTitle(R.string.master_clear_confirm_title);preference.getExtras().putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());((SettingsActivity) getActivity()).onPreferenceStartFragment(null, preference);}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
我們找到MasterClearConfirm.java就可以看到里面有我們想要的恢復出廠設置的操作。?
在函數doMasterClear()中會發送ACTION_MASTER_CLEAR廣播,而接收者可以在framework/base/core/res/ AndroidManifest.xml中找到:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
從中可以看出接收者就是MasterClearReceiver,它是框架層的一個service。打開MasterClearReceiver.java,里面只重載了一個onReceive()函數。里面開辟了一個新的線程進行rebootWipeUserData的操作。該函數中組織好參數后通過bootCommand()往cache中的command文件中寫指令,并遠程調用PowerManagerService.java中的reboot()重啟至recovery模式。走到這里,不熟悉Android系統啟動流程的屌絲們也許這里就走不動了,唯一得到的信息就是附近應該有IPowerManager.aidl文件。其實,在這里是遠程調用了一個系統級的服務——Powermanager。按照應用層的邏輯,我們會找對應的aidl文件,并繼續尋找其中reboot功能函數的具體實現方法進一步去找onBind函數或asInterface的連接服務函數。但這里我們是找不到的。因為,該服務不是應用層的服務,是在系統啟動的時候,zygote進程起來后,通過systemManager直接加載到服務列表中的,這里直接進行了使用(如何從PowerManager.java調到PowerManagerService.java中的,具體參看zygote進程的啟動過程,在此過程中有PowerManager服務的注冊流程)。按圖索驥,我們可以找到PowerManagerService.java文件,該文件就是PowerManager服務的具體實現。在其中,我們可以找到reboot函數。該函數中的前半部分都是對權限的check,往后看會發現shutdownOrRebootInternal函數。在該函數中,設計者直接new出了一個Runnable線程,順次看下求,在run函數中有ShutdownThread(ShutdownThread繼承自Thread,是一個線程類)的reboot函數分支。在其中,將reason賦值給mRebootReason之后,進行了shutdownInner處理。該函數中,我們只需要看其最后一句:beginShutdownSequence(),點進去,進一步我們會發現在該函數最后啟動了一個ShutdownThread線程實例sInstance。下面我們直接跳轉到其run()函數。在該運行實體的最后會來到rebootOrShutdown()函數,該函數中,我們會發現lowLevelReboot分支和最后的lowLevelShutdown函數,這兩個函數里面做的工作十分類似,都是去設置相應的Prop項。在這里我們只看lowLevelReboot分支。該函數中有詳細說明:
if (reason.equals(PowerManager.REBOOT_RECOVERY)) {// If we are rebooting to go into recovery, instead of// setting sys.powerctl directly we'll start the// pre-recovery service which will do some preparation for// recovery and then reboot for us.//// This preparation can take more than 20 seconds if// there's a very large update package, so lengthen the// timeout.SystemProperties.set("ctl.start", "pre-recovery");duration = 120 * 1000L;} else {SystemProperties.set("sys.powerctl", "reboot," + reason);duration = 20 * 1000L;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
所以,接下來直接看set函數。在該函數中最終還是調用了JNI的東西(Android4.4之前是在ShutdownThread.java文件中的lowLevelReboot函數中直接調用的):?
?
僅從函數名判斷不出什么,再看native_set函數的定義:?
?
這里申明了一個native類型的函數,但怎么去找到其native函數的具體實現呢?觀其文件名SystemProperties.java可以得知其對應注冊的native函數命中應該是SystemProperties開頭的。我們找到AndroidRuntime.cpp文件,里面有大量的native函數的注冊(這里為什么直接定位到了AndroidRuntime.cpp文件?也跟Android啟動流程中涉及到的systemmanager注冊服務機制有關,具體需要研究下這塊)。從中可以看到register_android_os_SystemProperties()函數的聲明。點擊去會發現里面也是調用了AndroidRuntime::registerNativeMethods()函數(該函數是native函數注冊的一個工具函數,很多函數注冊時都是通過它)。再看其參數method_table,該變量是一JNINativeMethod類型的數組,里面盛放的就是需要注冊的函數列表:?
?
從中可以找到SystemProperties_set函數,它就是我們要找的對應java側SystemProperties.java文件中native_set()函數的native實體。進入到該函數會發現其最終也是通過property_set(key, val)系統函數將”ctl.start”, “pre-recovery”key, val或”sys.powerctl”, “reboot,”設置下去的。至此,在device重啟之前的factory reset流程我們便走完了(具體設置完這些屬性后,device為什么就會重啟了呢?需要深入研究下device的電源管理或Android的關機流程這塊了,這里不做分析)。至此,整個factory reset流程,我們才分析完一半,而另一半分布在device重啟后的過程中,下面展開分析。?
二、Device重啟之后?
提到重啟,就不得不提bootloader。它是系統剛啟動時運行的一段或幾段程序,主要用來初始化硬件設備、引導系統內核啟動。下面簡單介紹下bootloader文件的一般組成:?
bootloader一般有好多個文件組成,如Android手機一般會有:PBL(Prime Bootloader), SBL1/2/3(Second Bootloader), APPSBL(有的也稱為aboot、hboot), HLOS(基帶baseband相關)和TZ(TrustZone相關的鏡像)。而iphone手機一般是:BootRom(PBL, SecureROM), LLB(Low Level Bootloader),iBoot(stage 2 bootloader,常用于recovery模式), iBBS(精簡版的ibOOT)和iBEC(用于從DFU-Device Firmware Upgrade模式恢復)。對于我的Exynos板子,由于其并非手機設備,包含的bootloader相對較少,有:PBL( 也叫bl0,燒在iROM的只讀代碼), BL1(stage 1 bootloader), BL2(stage 2 bootloader,就是uboot中的spl), tzsw(trustzone firmware)和uboot。Bootloader分為多階段的引導,這部分除了正常的硬件初始化工作外,還有我們更關注的一點是簽名驗證。每一階段都先驗證下一階段的鏡像病驗證通過后才加載,形成一個安全信任鏈,保證這些bootloader和后面的內核的完整性。這里之根據factory reset中涉及到的流程做淺嘗解析。?
bootloader啟動時匯編中入口文件為arch\arm\crt0.S,忽略其前期對硬件和環境的初始化,直接看跳往c語言的函數kmain:”bl Kmain“,該函數位于main.c文件中。?
進入到kmain函數中,會發現函數體中調用的大多數都是”_init”結尾的函數,顧名思義,他們都是為了初始化環境而存在的(該部分省略不議)。我們直接看到該函數最后,在快結束的地方發現它thread_create了一個線程,該線程的名字就叫bootstrap2,點擊bootstrap2函數進入。與前面kmain函數類似,一直都是*_init(該部分見名知意,都是平臺相關的初始化環節),我們忽略前面的,只看最后一個apps_init()。這里apps_init 是關鍵,對 LK 中所謂app 初始化并運行起來,而 aboot_init 就將在這里開始被運行,android linux 內核的加載工作就在 aboot_init 中完成的 。該函數中包含兩個for函數,且循環條件一致:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
該循環條件到底是什么意思呢?本人也是在網上大量的搜索,問google,求度娘,然而得到的最多的就是這么一句話:“至于會有那些 app 被放入 boot thread section, 則定義在 include/app.h 中的 APP_START(appname)”。但到app.h文件中卻只找到:
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname, #define APP_END };- 1
- 2
- 1
- 2
一直不明白其中原理。直到找到對應的system-onesegment.ld文件(該文件在”bootable\bootloader\lk\ build-目標平臺”目錄下),該問題才有了眉目:
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*). = ALIGN(4);__commands_start = .;KEEP (*(.commands))__commands_end = .;. = ALIGN(4);__apps_start = .;KEEP (*(.apps))__apps_end = .;. = ALIGN(4); __rodata_end = . ;- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
原來,在其最終的連接文件里,是將需要啟動的apps括在了SECTIONS下的.rodata段中,且以__apps_start為開頭,以__apps_end標志結束(這里涉及到文件結構的部分內容,內容拓展可以看《程序員的自我修養—鏈接、裝載與庫》一書)。此時再結合網上所說的app.h中的那句話也就明了了許多了。正如網上所說“在 app 中只要像 app/aboot/aboot.c 指定就會在 bootloader bootup 時放入 thread section 中被執行”。這點我們可以直接在整個lk中搜索關鍵字“APP_START”會發現我們的bootloader中到底有多少個類似這樣的app(不同的bootloader情況有所不同):?
?
如上圖,我們可以得知滿足條件的app有aboot、clocktests、pcitests、shell、stringtests和tests,我們這里只關注aboot。?
我們找到aboot.c文件,找到aboot_init()函數。根據源碼注釋,依次實現了:?
1、設置NAND/EMMC讀取信息頁面大小:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
2、讀取按鍵信息,判斷是正常開機,還是進入 fastboot ,還是進入recovery 模式:?
在函數體內,除了上半部分對按鍵進行判斷以確定模式走向外,還有對BCB區域中command指令的讀取來判斷:init.c文件中的check_hard_reboot_mode():
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
或者check_reboot_mode():
unsigned check_reboot_mode(void) {uint32_t restart_reason = 0;uint32_t soc_ver = 0;uint32_t restart_reason_addr;soc_ver = board_soc_version();if (platform_is_8974() && BOARD_SOC_VERSION1(soc_ver))restart_reason_addr = RESTART_REASON_ADDR;elserestart_reason_addr = RESTART_REASON_ADDR_V2;/* Read reboot reason and scrub it */restart_reason = readl(restart_reason_addr);writel(0x00, restart_reason_addr);return restart_reason; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
從上面代碼會發現最終的返回值restart_reason_addr是從RESTART_REASON_ADDR或者RESTART_REASON_ADDR_V2中取得的,這里就是BCB中的上個command里的內容了。也是在這里開始決定接下來系統會走向main system、Recovery還是fastboot模式的。如果啟動的是main system或者Recovery模式,則會執行下文的后續步驟;如果是fastboot模式,則通過fastboot_init()直接初始化并啟動到fastboot階段,該模式下沒有內核的加載和啟動。?
3、加載內核:如果是啟動main system則執行boot_linux_from_mmc()進行加載,如果是啟動Recovery模式則通過boot_linux_from_flash()加載,?
4、啟動內核:
- 1
- 2
- 3
- 1
- 2
- 3
這里linux kernel起來之后,會初始化init進程,并布置ramdisk文件系統等等后續應用工作,這里具體流程請參看linux kernel啟動流程。?
三、結束語?
至此,從main system到bootloader然后再次到模式選擇分析結束,接下來我們可以選擇具體的main system 、recovery或者fastboot模式繼續往下分析,具體分析這里不再繼續。main system模式具體見網上Android或linux的啟動流程。而recovery模式接下的具體流程可以參見recovery源碼流程分析。
總結
以上是生活随笔為你收集整理的平述factory reset——从main system到重引导流程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android LK Bootlaode
- 下一篇: msm8974 camera drive