天天看點

Android之旅 -- Recovery相關原理分析

一、Recovery是如何構成的

  說recovery的構成并不貼切,應該說recovery.img的構成,它是由boot_img_hdr + zImage + recovery-ramdisk構成。boot_img_hd是個結構體它描述了很多重要的資訊。

Android之旅 -- Recovery相關原理分析
1 struct boot_img_hdr
 2 {
 3     unsigned char magic[BOOT_MAGIC_SIZE];
 4     unsigned kernel_size;  /* size in bytes */
 5     unsigned kernel_addr;  /* physical load addr */
 6     unsigned ramdisk_size; /* size in bytes */
 7     unsigned ramdisk_addr; /* physical load addr */
 8     unsigned second_size;  /* size in bytes */
 9     unsigned second_addr;  /* physical load addr */
10     unsigned tags_addr;    /* physical addr for kernel tags */
11     unsigned page_size;    /* flash page size we assume */
12     unsigned unused[2];    /* future expansion: should be 0 */
13     unsigned char name[BOOT_NAME_SIZE]; /* asciiz product name */
14     unsigned char cmdline[BOOT_ARGS_SIZE];
15     unsigned id[8]; /* timestamp / checksum / sha1 / etc */
16 };      
Android之旅 -- Recovery相關原理分析

其中kernel_size表示zImage的實際大小;kernel_addr表示zImage載入記憶體的實體位址,這個位址也是bootloader跳轉到核心的位址;ramdisk_size表示ramdisk(此處就是指recovery-ramdisk)的實際大小;ramdisk_addr是ramdisk加載到記憶體的實體位址,之後kernel會解壓并把它挂在成根檔案系統,我們的中樞神經-init.rc就隐藏于内;second_size,second_addr做擴充用一般都不會使用(在我的一個項目中把它擴充成另外一種功能有機會介紹給大家);tags_addr是傳參數用的實體記憶體位址,它作用是把bootloader中的參數傳遞給kernel;page_size是flash(eg. nandflash)的一個頁大小,一般為2K,這通常情況取決于你使用Flash晶片的頁大小;cmdline就是command line它可以由bootloader傳遞也可以在.config(kernel)中配置。

zImage是我們熟悉的核心鏡像,由kernel編譯生成。recovery-ramdisk是由mkbootfs、gzip打包生成的指令如下:

mkbootfs ramdiskdir | gzip > recovery-ramdisk.gz      

 我們可以通過解壓recovery.img來擷取真正的recovery可執行檔案,操作如下:

1 ./split_bootimg.pl recovery.img
2 mkdir out
3 cd out
4 gunzip -c ../recovery.img-ramdisk.gz | cpio -i      

/sbin目錄下的recovery就是可執行檔案了,還原可參照如下操作:

1 find . | cpio -o -H newc | gzip > ../recovery.img-ramdisk.gz
2 mkbootimg --kernel recovery.img-kernel --ramdisk recovery.img-ramdisk.gz -o new-recovery.img      

注: 上述使用到的工具下載下傳(android-img-tools)

        recovery.img與boot.img在結構上是一樣的。

        現在很多平台的打包方式是不一樣的,它們的做法是編譯時把kernel鏡像和根檔案系統打包在一起合稱為zImage。

        本文提到的bootloader指blob,下同。

二、Recovery是如何啟動的

       請看如下流程圖:

Android之旅 -- Recovery相關原理分析

這個流程圖隻是大概描述bootloader中選擇啟動部分的流程。當裝置上電或Reboot進入bootloader時會檢測此時是否有特殊鍵被按下也就是流程圖中的KeyPress,例如某手機開機時同時按下照相鍵+音量鍵就會進入recovery。有人會問如何檢測按鍵被按下?很簡單就是讀鍵盤控制寄存器的值,如果你是GPIO按鍵就讀GPIO的寄存器。如果沒有鍵被按下,bootloader會讀取misc分區中bootloader_message結構資訊。

1 struct bootloader_message {
2      char command[32];
3      char status[32];
4      char recovery[1024];
5 };      

(這裡順便提一下:如果你的存儲晶片是NandFlash bootloader_message是存放在Misc分區Block 0的第二個頁上,如果是MMC那麼就是這個分區的開始位置。在Android興起之初NandFlash很流行,但由于MMC總線接口簡單統一、大容量、易于更換大有替換NandFlash之勢)再看bootloader_message中的command如果其内容是"boot-recovery"那麼bootloader會進入recovery;bootloader_message中的recovery是放解析的參數它是告訴Recovery系統需執行什麼樣的動作,如:"recovery\n--update_package=CACHE:update.zip",這樣Recovery系統就會明白它将要更新AndroidOS系統,更新包是/cache/update.zip。同樣還有"recovery\n--wipe_date" 清除使用者資料後重新開機,通常用它做恢複出廠設定功能;再有"recovery\n--wipe_cache"清除cachec分區内容後重新開機,cache分區一般放臨時檔案用;當然你也可以自定義實作你想要的功能。那麼這個bootloader_message是誰寫入的呢?當你需要OTA更新的時候,系統會到指定位址下載下傳更新包放入Cache分區,然後把bootloader_message資訊寫入Misc分區然後重新開機,還有當你需要恢複出廠設定時它也會進行同樣的操作。

bootloader加載recovery.img時先讀取img頭資訊(上文提到的boot_img_hdr)把zImage和recovery-ramdisk加載至制定記憶體位址,設定參數等操作後,跳轉至kernel在記憶體中的位址(就是kernel_addr),至此kernel被加載啟動。挂載根檔案後執行init,init會解析init.rc,大家注意init.rc中有如下一條:

service recovery /sbin/recovery      

執行到這條時我們的recovery就算啟動了。

題外話:我們可以發現recovery可執行檔案是靜态編譯的,之是以這樣是因為recovery模式中沒有共享庫還有缺動态連結庫加載器(/system/bin/linker)。

三、Recovery是如何工作的

Android之旅 -- Recovery相關原理分析

    上圖主要描述recovery執行的主要功能的主體架構

    1、UpdateXXX.zip   

    我們知道recovery更新時需要一個特殊的UpdateXXX.zip包,解壓出來可以發現META-INF是一定會有的,在看其裡面的内容:

Android之旅 -- Recovery相關原理分析
|-- CERT.RSA
|-- CERT.SF
|-- com
|   `-- google
|       `-- android
|           |-- update-binary
|           |-- updater-script
|           `-- update-script
`-- MANIFEST.MF      
Android之旅 -- Recovery相關原理分析

    其中 CERT.RSA、CERT.SF、MANIFEST.MF是做包時産生的簽名檔案如下腳本可以生成:

java -jar signapk.jar xxx.pem xxx.pk8  xxx-unsigned.zip  updatexxx-signed.zip      

    其中密鑰檔案xxx.pem xxx.pk8 在 build/target/product/security/下就可以找到,signapk.jar是由build/tools/signapk/編譯生成的。當然各個廠家為了不混淆自家的更新封包件可能會定制自己的密鑰檔案。接下來我們來看看update-binary、updater-script、update-script這三個檔案。其中update-script是Amend腳本,在Android1.5之前使用的現在已經不再支援,它是由Recovery直接解析執行的。現在使用的updater-script是Edify腳本,是由update-binary解析執行的。後者的好處在于,它完全獨立于Recovery系統,在recovery中fork出一個程序來執行update-binary的,而且使用者可以擴充供執行的解析指令,這樣靈活性很高,可以做的事情越多。

    2、更新時recovery是如何更新系統的?

    細心的朋友可能早就發現了,boot.img和system.img雖然都叫xxx.img其實他們有本質的不同。boot.img不帶有檔案系統的資訊,通俗的話說就是"裸資料",直接寫入分區就好;而system.img是帶有檔案資訊的,所帶有的檔案資訊最終取決于你的存儲晶片和你所使用的檔案系統,比如使用的存儲晶片是NandFlash,那麼檔案系統可能是Yaffs(2)、Jffs(2)等等,很大一部分是Yaffs2,那system.img是帶有Yaffs2的檔案資訊(包括檔案類型、檔案chunk數、chunkID、Yaffs2的OOB資訊等等),如果使用的存儲晶片是MMC,那麼檔案系統可能是ext2、ext3、ext4、fat32等等,很多使用的是ext系列。那麼update.zip需要更新boot.img和system.img怎麼實作呢?我們先看如下腳本(以NandFlash-Yaffs2為例)

1 format("MTD", "system");
2 mount("MTD", "system", "/system");
3 package_extract_dir("system", "/system");

4 package_extract_file("boot.img", "/tmp/boot.img"),
5 write_raw_image("/tmp/boot.img", "boot");      

我們可以發現對更新boot.img、system.img是不同的腳本。更新system時先格式化system分區,大家看到MTD并不是格式化成MTD檔案系統,MTD是Nandflash驅動的管理層,Yaffs2檔案系統是建構MTD層之上,MTD起到呈上啟下的作用,另像分區資訊、裝置節點等都有MTD層來管理。如果檢視代碼就會發現format類型是MTD時就會把這個分區格式化成Yaffs2的。我們可以通過cat /proc/mtd來檢視分區資訊。格式化完成之後我們會把system分區挂載到某一目錄下,然後把update.zip中的system檔案下所有檔案寫到剛挂載的目錄下。而對于boot.img的更新是直接把update.zip中的boot.img寫到裝置中也就是nandflash晶片中。那麼兩者同樣是寫有什麼不同呢?直接寫裝置節點是直接由mtd接口來完成的,nandflash隻在涉及到的頁的OOB區添加ECC校驗資訊。但是如果是帶有檔案系統的寫首先需要通過Yaffs檔案系統的檔案接口加上OOB的一些資訊,然後再通過MTD接口來完成,nandflash在涉及到的頁的OOB區添加ECC和檔案系統的一些資訊如:chunkid、chunksize等資訊。

    3、幾條很好用的腳本指令

    getprop   擷取系統屬性,eg:getprop("ro.flag")=="ok"                                            一般和其他腳本聯合使用

    assert      腳本中的斷言,eg:assert(getprop("ro.flag") == "ok");                             如果失敗則終止腳本繼續執行

    ifelse        腳本中的判斷,eg:ifelse(getprop("ro.flag") == "ok", ui_print("ok"), ui_print("no,ok"));

    run_program    執行第三方可執行檔案,eg:run_program("test_binary");                    可執行檔案必須是靜态編譯的

    腳本指令這裡就不一一列舉了,網上資料很多。

四、Recovery的适配及修改

    遇到一個新項目Recovery需要修改哪些呢?

    1、進入按鍵,在bootloader中需要檢測哪些鍵被按下時進入Recovery

    2、分區資訊,如果你是nandflash存儲晶片可以檢視cat /proc/mtd, 若是eMMC看recovery.fstab檔案中的内容是否正确

    3、recovery中的按鍵,上下選擇及執行按鍵是否映射正确,可以參看kernel中的頭檔案<linux/input.h>

    4、是否支援你的檔案系統,筆者分析使用的代碼隻支援Yaffs2及ext3的

    5、簽名驗證,簽名驗證的密鑰證書是否一緻,可以寫個小應用驗證下

    6、顯示,framebuffer裝置節點是否一緻讀寫是否正常,tty裝置配置成圖形類型是否正常,RGB是否适配

    7、misc分區,misc分區是recovery預設的傳遞參數的區域,可以換掉

     上述隻例舉筆者在工作中遇到的一些情況,不同的平台、環境、需求時需注意的地方可能不盡相同

本文主要分析了Recovery涉及到的相關原理,從這麼一個小小的系統中可以發現它涵蓋了大量的知識點,此篇隻揭開了它一片小小的面紗。