從網上轉載,如有不正確的地方,請各位拿磚:
升級包的製作
1.整包的製作
升級包有整包與差分包之分。顧名思義,所謂整包即包含整個system分區中的數據文件;而差分包則僅包含兩個版本之間改動的部分。利用整包升級好比對電腦進行重作系統,格式分系統分區,並將新系統數據寫入分區;而利用差分包升級不會格式化system分區,只是對其中部分存儲段的內容進行重寫。除升級包之外,製作過程中還會涉及到另一種zip包,代碼中稱之爲target-files zipfile,我稱之爲差分資源包。首先闡述下整包的製作過程。
系統經過整編後,執行make otapackage命令,即可完成整包的製作,而此命令可分爲兩個階段進行。首先執行./build/core/Makefile中的代碼:
[plain] view plaincopy
- # -----------------------------------------------------------------
- # OTA update package
- name := $(TARGET_PRODUCT)
- ifeq ($(TARGET_BUILD_TYPE),debug)
- name := $(name)_debug
- endif
- name := $(name)-ota-$(FILE_NAME_TAG)
- INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
- $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
- $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS)
- @echo "Package OTA: $@"
- $(hide) ./build/tools/releasetools/ota_from_target_files -v \
- -n \
- -p $(HOST_OUT) \
- -k $(KEY_CERT_PAIR) \
- $(ota_extra_flag) \
- $(BUILT_TARGET_FILES_PACKAGE) $@
- .PHONY: otapackage
- otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)
- # -----------------------------------------------------------------
-
# -----------------------------------------------------------------
-
# OTA update package
-
name := $(TARGET_PRODUCT)
-
ifeq ($(TARGET_BUILD_TYPE),debug)
-
name := $(name)_debug
-
endif
-
name := $(name)-ota-$(FILE_NAME_TAG)
-
INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
-
$(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
-
$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS)
-
@echo "Package OTA: $@"
-
$(hide) ./build/tools/releasetools/ota_from_target_files -v \
-
-n \
-
-p $(HOST_OUT) \
-
-k $(KEY_CERT_PAIR) \
-
$(ota_extra_flag) \
-
$(BUILT_TARGET_FILES_PACKAGE) $@
-
.PHONY: otapackage
-
otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)
-
# -----------------------------------------------------------------
代碼段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
- script.FormatPartition("/system")
- script. FormatPartition ("/system")
- script.UnpackPackageDir("recovery", "/system")
- script.UnpackPackageDir("system", "/system")
- (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)
- script.MakeSymlinks(symlinks)
- if OPTIONS.aslr_mode:
- script.RetouchBinaries(retouch_files)
- else:
- script.UndoRetouchBinaries(retouch_files)
-
script.FormatPartition("/system")
-
script. FormatPartition ("/system")
-
script.UnpackPackageDir("recovery", "/system")
-
script.UnpackPackageDir("system", "/system")
-
(symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)
-
script.MakeSymlinks(symlinks)
-
if OPTIONS.aslr_mode:
-
script.RetouchBinaries(retouch_files)
-
else:
-
script.UndoRetouchBinaries(retouch_files)
代碼2 WriteFullOTAPackage代碼片段
其中的script爲edify_generator對象,其FormatPartition、UnpackPackageDir等方法分別是向腳本文件update-script中寫入格式化分區、解壓包等指令。
[python] view plaincopy
- def AddToZip(self, input_zip, output_zip, input_path=None):
- """Write the accumulated script to the output_zip file. input_zip
- is used as the source for the 'updater' binary needed to run
- script. If input_path is not None, it will be used as a local
- path for the binary instead of input_zip."""
- self.UnmountAll()
- common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
- "\n".join(self.script) + "\n")
- if input_path is None:
- data = input_zip.read("OTA/bin/updater")
- else:
- data = open(os.path.join(input_path, "updater")).read()
- common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
- data, perms=0755)
-
def AddToZip(self, input_zip, output_zip, input_path=None):
-
"""Write the accumulated script to the output_zip file. input_zip
-
is used as the source for the 'updater' binary needed to run
-
script. If input_path is not None, it will be used as a local
-
path for the binary instead of input_zip."""
-
self.UnmountAll()
-
common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
-
"\n".join(self.script) + "\n")
-
if input_path is None:
-
data = input_zip.read("OTA/bin/updater")
-
else:
-
data = open(os.path.join(input_path, "updater")).read()
-
common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
-
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
- const char** args = (const char**)malloc(sizeof(char*) * 5);
- args[0] = binary;
- args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk
- char* temp = (char*)malloc(10);
- sprintf(temp, "%d", pipefd[1]);
- args[2] = temp;
- args[3] = (char*)path;
- args[4] = NULL;
- pid_t pid = fork();
- if (pid == 0) {
- close(pipefd[0]);
- execv(binary, (char* const*)args);
- _exit(-1);
- }
- close(pipefd[1]);
-
const char** args = (const char**)malloc(sizeof(char*) * 5);
-
args[0] = binary;
-
args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk
-
char* temp = (char*)malloc(10);
-
sprintf(temp, "%d", pipefd[1]);
-
args[2] = temp;
-
args[3] = (char*)path;
-
args[4] = NULL;
-
pid_t pid = fork();
-
if (pid == 0) {
-
close(pipefd[0]);
-
execv(binary, (char* const*)args);
-
_exit(-1);
-
}
-
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
- built_ota_tools := \
- $(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \
- $(call intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static \
- $(call intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq \
- $(call intermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3 \
- $(call intermediates-dir-for,EXECUTABLES,updater)/updater
-
built_ota_tools := \
-
$(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \
-
$(call intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static \
-
$(call intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq \
-
$(call intermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3 \
-
$(call intermediates-dir-for,EXECUTABLES,updater)/updater
代碼段5 Makefile中定義的變量built_ota_tools
[plain] view plaincopy
- $(hide) mkdir -p $(zip_root)/OTA/bin
- $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/
- $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/
-
$(hide) mkdir -p $(zip_root)/OTA/bin
-
$(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/
-
$(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函數)。
貌似差不多了,就此打住。