OTA差分升級

從網上轉載,如有不正確的地方,請各位拿磚:

 升級包的製作

1.整包的製作

升級包有整包與差分包之分。顧名思義,所謂整包即包含整個system分區中的數據文件;而差分包則僅包含兩個版本之間改動的部分。利用整包升級好比對電腦進行重作系統,格式分系統分區,並將新系統數據寫入分區;而利用差分包升級不會格式化system分區,只是對其中部分存儲段的內容進行重寫。除升級包之外,製作過程中還會涉及到另一種zip包,代碼中稱之爲target-files zipfile,我稱之爲差分資源包。首先闡述下整包的製作過程。

系統經過整編後,執行make otapackage命令,即可完成整包的製作,而此命令可分爲兩個階段進行。首先執行./build/core/Makefile中的代碼:

[plain] view plaincopy

  1. # -----------------------------------------------------------------  
  2. # OTA update package  
  3.   
  4. name := $(TARGET_PRODUCT)  
  5. ifeq ($(TARGET_BUILD_TYPE),debug)  
  6.   name := $(name)_debug  
  7. endif  
  8. name := $(name)-ota-$(FILE_NAME_TAG)  
  9.   
  10. INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip  
  11.   
  12. $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)  
  13.   
  14. $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS)  
  15.         @echo "Package OTA: $@"  
  16.         $(hide) ./build/tools/releasetools/ota_from_target_files -v \  
  17.            -n \  
  18.            -p $(HOST_OUT) \  
  19.            -k $(KEY_CERT_PAIR) \  
  20.            $(ota_extra_flag) \  
  21.            $(BUILT_TARGET_FILES_PACKAGE) $@  
  22. .PHONY: otapackage  
  23. otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)  
  24. # -----------------------------------------------------------------  

 
  1. # -----------------------------------------------------------------

  2. # OTA update package

  3.  
  4. name := $(TARGET_PRODUCT)

  5. ifeq ($(TARGET_BUILD_TYPE),debug)

  6. name := $(name)_debug

  7. endif

  8. name := $(name)-ota-$(FILE_NAME_TAG)

  9.  
  10. INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip

  11.  
  12. $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)

  13.  
  14. $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS)

  15. @echo "Package OTA: $@"

  16. $(hide) ./build/tools/releasetools/ota_from_target_files -v \

  17. -n \

  18. -p $(HOST_OUT) \

  19. -k $(KEY_CERT_PAIR) \

  20. $(ota_extra_flag) \

  21. $(BUILT_TARGET_FILES_PACKAGE) $@

  22. .PHONY: otapackage

  23. otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)

  24. # -----------------------------------------------------------------

代碼段1 make otapackage目標代碼

這段代碼作用在於將系統資源(包括system、recovery、boot等目錄)重新打包,生成差分資源包(即target-files zipfile,下文將統一使用“差分資源包”這一概念)。我們可以看下差分資源包中的文件結構,如下:

圖2 target-files zipfile目錄結構

其中,OTA目錄值得關注,因爲在此目錄下存在着一個至關重要的文件:OTA/bin/updater(後文會有詳述)。生成的差分資源包被傳遞給./build/tools/releasetools/

ota_from_target_files執行第二階段的操作:製作升級包。

圖3 ./build/tools/releasetools目錄下的文件

圖3是./build/tools/releasetools目錄下所包含的文件,這組文件是Google提供的用來製作升級包的代碼工具,核心文件爲:ota_from_target_files和img_from_target_files。其中,前者用來製作recovery模式下的升級包;後者則用來製作fastboot下的升級包(fastboot貌似是一種更底層的刷機操作,未過多研究,不再詳述)。其他文件則是爲此二者提供服務的,比如,common.py中包含有製作升級包所需操作的代碼;edify_generator.py則用於生成recovery模式下升級的腳本文件:<升級包>.zip/ META-INF/com/google/android/updater-script。

文件ota_from_target_files是本文所關注的重點,其中定義了兩個主要的方法:WriteFullOTAPackage和WriteIncrementalOTAPackage。前者用於生成整包,後者用來生成差分包。接着上文,當Makefile調用ota_from_target_files,並將差分資源包傳遞進來時,會執行WriteFullOTAPackage。此方法完成的工作包括:(1)將system目錄,boot.img等文件添加到整包中;(2)生成升級包中的腳本文件:<升級包>.zip/META-INF/com/google/android/updater-script;(3)將上文提到的可執行文件:OTA/bin/

updater添加到升級包中:META-INF/com/google/android/updater-script。摘取部分代碼片段如下:

[python] view plaincopy

  1. script.FormatPartition("/system")  
  2.   script. FormatPartition ("/system")  
  3.   script.UnpackPackageDir("recovery", "/system")  
  4.   script.UnpackPackageDir("system", "/system")  
  5.   
  6.   (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)  
  7.   script.MakeSymlinks(symlinks)  
  8.   if OPTIONS.aslr_mode:  
  9.     script.RetouchBinaries(retouch_files)  
  10.   else:  
  11.     script.UndoRetouchBinaries(retouch_files)  

 
  1. script.FormatPartition("/system")

  2. script. FormatPartition ("/system")

  3. script.UnpackPackageDir("recovery", "/system")

  4. script.UnpackPackageDir("system", "/system")

  5.  
  6. (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)

  7. script.MakeSymlinks(symlinks)

  8. if OPTIONS.aslr_mode:

  9. script.RetouchBinaries(retouch_files)

  10. else:

  11. script.UndoRetouchBinaries(retouch_files)

代碼2 WriteFullOTAPackage代碼片段


其中的script爲edify_generator對象,其FormatPartition、UnpackPackageDir等方法分別是向腳本文件update-script中寫入格式化分區、解壓包等指令。

[python] view plaincopy

  1. def AddToZip(self, input_zip, output_zip, input_path=None):  
  2.     """Write the accumulated script to the output_zip file.  input_zip 
  3.     is used as the source for the 'updater' binary needed to run 
  4.     script.  If input_path is not None, it will be used as a local 
  5.     path for the binary instead of input_zip."""  
  6.   
  7.     self.UnmountAll()  
  8.   
  9.     common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",  
  10.                        "\n".join(self.script) + "\n")  
  11.   
  12.     if input_path is None:  
  13.       data = input_zip.read("OTA/bin/updater")  
  14.     else:  
  15.       data = open(os.path.join(input_path, "updater")).read()  
  16.     common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",  
  17.                        data, perms=0755)  

 
  1. def AddToZip(self, input_zip, output_zip, input_path=None):

  2. """Write the accumulated script to the output_zip file. input_zip

  3. is used as the source for the 'updater' binary needed to run

  4. script. If input_path is not None, it will be used as a local

  5. path for the binary instead of input_zip."""

  6.  
  7. self.UnmountAll()

  8.  
  9. common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",

  10. "\n".join(self.script) + "\n")

  11.  
  12. if input_path is None:

  13. data = input_zip.read("OTA/bin/updater")

  14. else:

  15. data = open(os.path.join(input_path, "updater")).read()

  16. common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",

  17. data, perms=0755)

代碼段3  edify_generator中的AddToZip方法

WriteFullOTAPackage執行的最後會調用此方法。將資源差分包中OTA/bin/updater文件copy到升級包中META-INF/com/google/android/update-binary。此文件是OTA升級的關鍵,其將在recovery模式下被執行,用來將代碼段2中生成的指令轉換爲相應的函數去執行,從而完成對系統數據的重寫。

2.差分包的製作

生成差分包調用的是文件./build/tools/releasetools/ota_from_target_files中的WriteIncrementalOTA方法,調用時需要將兩個版本的差分資源包作爲參數傳進來,形如:

./build/tools/releasetools/ota_from_target_files –n –i ota_v1.zip ota_v2.zip update.zip

其中,參數n表示忽略時間戳;i表示生成增量包(即差分包);ota_v1.zip與ota_v2.zip分別代表前後兩個版本的差分資源包;而update.zip則表示最終生成的差分包。

WriteIncrementalOTA函數會計算輸入的兩個差分資源包中版本的差異,並將其寫入到差分包中;同時,將updater及生成腳本文件udpate-script添加到升級包中。

三 問題二:將升級包寫入系統存儲區

製作完升級包後,之後便是將其寫入到相應存儲區中,這部分工作是在recovery模式下完成的。之前的幾篇筆記亦有描述,recovery模式下通過創建一個新的進程讀取並執行腳本文件META-INF/com/google/android/updater-script。見如下代碼:

[cpp] view plaincopy

  1. const char** args = (const char**)malloc(sizeof(char*) * 5);  
  2. args[0] = binary;  
  3. args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk  
  4. char* temp = (char*)malloc(10);  
  5. sprintf(temp, "%d", pipefd[1]);  
  6. args[2] = temp;  
  7. args[3] = (char*)path;  
  8. args[4] = NULL;  
  9.   
  10. pid_t pid = fork();  
  11. if (pid == 0) {  
  12.     close(pipefd[0]);  
  13.     execv(binary, (char* const*)args);  
  14.     _exit(-1);  
  15. }  
  16. close(pipefd[1]);  

 
  1. const char** args = (const char**)malloc(sizeof(char*) * 5);

  2. args[0] = binary;

  3. args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk

  4. char* temp = (char*)malloc(10);

  5. sprintf(temp, "%d", pipefd[1]);

  6. args[2] = temp;

  7. args[3] = (char*)path;

  8. args[4] = NULL;

  9.  
  10. pid_t pid = fork();

  11. if (pid == 0) {

  12. close(pipefd[0]);

  13. execv(binary, (char* const*)args);

  14. _exit(-1);

  15. }

  16. close(pipefd[1]);

代碼段4 創建新進程安裝升級包

分析代碼之前,首先介紹linux中函數fork與execv的用法。
pid_t fork( void)

        創建新的進程,fork調用的一個奇妙之處就是它僅僅被調用一次,卻能夠返回兩次,它可能有三種不同的返回值:
  1)在父進程中,fork返回新創建子進程的進程ID;
  2)在子進程中,fork返回0;
  3)如果出現錯誤,fork返回一個負值;
        在fork函數執行完畢後,如果創建新進程成功,則出現兩個進程,一個是子進程,一個是父進程。在子進程中,fork函數返回0,在父進程中,fork返回新創建子進程的進程ID。我們可以通過fork返回的值來判斷當前進程是子進程還是父進程(http://os.chinaunix.net/a2012/0203/1306/000001306508.shtml)。

int execv(const char *progname, char *const argv[])
        execv會停止執行當前的進程,並且以progname應用進程替換被停止執行的進程,進程ID沒有改變。
        progname: 被執行的應用程序。
        argv: 傳遞給應用程序的參數列表, 注意,這個數組的第一個參數應該是應用程序名字本身,並且最後一個參數應該爲NULL,不參將多個參數合併爲一個參數放入數組。

 代碼4見於bootable/recovery/install.c的try_update_binary函數中,是OTA升級的核心代碼之一。通過對fork及execv函數的介紹可知,代碼4創建了一個新的進程並在新進程中運行升級包中的META-INF/com/google/android/updater-binary文件(參數binary已在此前賦值),此文件將按照META-INF/com/google/android/updater-script中的指令將升級包裏的數據寫入到存儲區中。OK,我們來看下META-INF/com/google/android/updater-binary文件的來歷。

在源代碼的./bootable/recovery/updater目錄下,存在着如下幾個文件:

圖4 ./bootable/recovery/updater目錄

 

通過查看Android.mk代碼可知,文件install.c、updater.c將會被編譯爲可執行文件updater存放到目錄out/target/product/<product-name>/obj/EXECUTABLES/

updater_intermediates/中;而在生成差分資源包(target-files zipfile)時,會將此文件添加到壓縮包中。

[plain] view plaincopy

  1. built_ota_tools := \  
  2.     $(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \  
  3.     $(call intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static \  
  4.     $(call intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq \  
  5.     $(call intermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3 \  
  6.     $(call intermediates-dir-for,EXECUTABLES,updater)/updater  

 
  1. built_ota_tools := \

  2. $(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \

  3. $(call intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static \

  4. $(call intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq \

  5. $(call intermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3 \

  6. $(call intermediates-dir-for,EXECUTABLES,updater)/updater

代碼段5 Makefile中定義的變量built_ota_tools

[plain] view plaincopy

  1. $(hide) mkdir -p $(zip_root)/OTA/bin  
  2.     $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/  
  3.     $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/  

 
  1. $(hide) mkdir -p $(zip_root)/OTA/bin

  2. $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/

  3. $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/

代碼段6 複製built_ota_tools工具到差分資源包

如代碼段5,Makefile中定義了執行OTA所需要的一組工具(built_ota_tools),其中便包括由圖4中文件編譯而成的文件updater;而在生成差分資源包時,會將這組工具拷貝到差分資源包的OTA/bin目錄中(見代碼段6);在生成升級包時(無論是執行WriteFullOTAPackage還是WriteIncrementalOTAPackage),最後都會調用edify_generator的AddToZip方法,將updater添加到升級包中(更名爲"META-INF/com/google/android/update-binary");最終在recovery模式下被執行,這便是其來龍去脈。而關於updater的執行,也大致的描述下吧。

由前文可知,updater主要由bootable/recovery/updater目錄下的install.c和updater.c編譯而成,主函數位於updater.c。其中,在install.c中定義了讀寫系統存儲區的操作函數(這纔是重寫系統數據的真正代碼)並將這些函數與updater-script中的指令映射起來。而在updater.c會首先裝載install.c定義的函數,之後便解析升級腳本updater-script,執行其對應的操作命令。與此同時,執行updater的進程還會與父進程通信,通知父進程進行UI的相關操作(代碼見bootable/recovery/install.c中的try_update_binary函數)。

貌似差不多了,就此打住。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章