原創文章,歡迎轉載,轉載請注明出處http://www.cnblogs.com/becklc/archive/2012/09/24/2676600.html
本文依據android2.3源碼只分析Recovery相關原理,不針對代碼走讀,現在Android版本已經4.x.x但是recovery的基本原理不變。
一、Recovery是如何構成的
說recovery的構成並不貼切,應該說recovery.img的構成,它是由boot_img_hdr + zImage + recovery-ramdisk構成。boot_img_hd是個結構體它描述了很多重要的信息。
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 };
其中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是如何啟動的
請看如下流程圖:
這個流程圖只是大概描述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是如何工作的
上圖主要描述recovery執行的主要功能的主體框架
1、UpdateXXX.zip
我們知道recovery升級時需要一個特殊的UpdateXXX.zip包,解壓出來可以發現META-INF是一定會有的,在看其里面的內容:
|-- CERT.RSA |-- CERT.SF |-- com | `-- google | `-- android | |-- update-binary | |-- updater-script | `-- update-script `-- MANIFEST.MF
其中 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涉及到的相關原理,從這么一個小小的系統中可以發現它涵蓋了大量的知識點,此篇只揭開了它一片小小的面紗。