OTA制作及升级过程笔记【转】
本文轉載自:http://www.it610.com/article/5752570.htm
1、概述
1.1 文檔概要
前段時間學習了AndroidRecovery模式及OTA升級過程,為加深理解和防止以后遺忘,所以寫這篇文檔進行一個總結和梳理,以便日后查閱回顧。文檔主要包括兩部分,第一部分為OTA升級包的制作過程分析,第二部分為Recovery模式下OTA升級包安裝過程的分析,其中包括Recovery模式分析及服務流程。
1.2 參考文獻
《Recovery 開發指導》
《Android系統Recovery工作原理之使用update.zip升級過程分析》
《OTA本質與實現流程分析》
《Android系統啟動過程分析》
2、OTA升級包制作工程
2.1 OTA升級簡介
所謂OTA(Over-the-AirTechnology)是指手機終端通過無線網絡下載遠程服務器上的升級包,對系統或應用進行升級的技術。有關網絡部分不做過多討論,本文重點放在系統升級這一概念上。
圖1 某android手機存儲設備結構圖
以PC機進行類比,假設計算機操作系統裝在C盤,當加電啟動時,引導程序會將C盤的系統程序裝入內存并運行,而系統升級或重裝系統,則是將C盤中原來的系統文件部分或全部重寫。對于手機及其上的ANDROID系統而言,同樣如此,需要一個存儲系統文件的“硬盤”。
圖1 是某款手機的存儲設備結構圖,其存儲區(紅色框圖部分)分為四部分:SRAM、Nand Flash、SDRAM及外設地址空間。其中Nand Flash中存儲著全部系統數據(通過專門的燒寫工具將編譯后的映象文件download到Nand Flash中,工具由IC廠商提供),包括boot.img、system.img、recovery.img等,因此Nand Flash即是上文所說的手機上的“硬盤”。圖1最右部分(圖中綠色框圖部分)是Nand Flash存儲區更詳細的劃分,我們將主要關注system分區(藍色框圖),因為OTA升級主要是對這部分系統數據的重寫(當然boot分區也可升級)。除此之外,藍黑色區域標示的misc分區也應值得注意,它在OTA升級中發揮著重要的作用。
OK,一言以蔽之,所謂OTA就是將升級包(zip壓縮包)寫入到系統存儲區,因此我們需要考慮兩個問題:1、升級包是如何生成的?2、升級包是如何寫入到system分區的?
2.2 OTA升級包update.zip結構
2.2.1、update.zip包的目錄結構
|----boot.img
|----system/
|----recovery/
`|----recovery-from-boot.p
`|----etc/
`|----install-recovery.sh
|---META-INF/
`|CERT.RSA
`|CERT.SF
`|MANIFEST.MF
`|----com/
`|----google/
`|----android/
`|----update-binary
`|----updater-script
`|----android/
`|----metadata
2.2.2、update.zip包目錄結構詳解
以上是我們用命令makeotapackage 制作的update.zip包的標準目錄結構。(和實際的略有不同)
1、boot.img是更新boot分區所需要的文件。這個boot.img主要包括kernel+ramdisk,包括應用會用到的一些庫等等。可以將Android源碼編譯out/target/product/ xxxx /system/中的所有文件拷貝到這個目錄來代替。
2、system/目錄的內容在升級后會放在系統的system分區。主要用來更新系統的一些應用或則應用會用到的一些庫等等。可以將Android源碼編譯out/target/product/xxxx/system/中的所有文件拷貝到這個目錄來代替。
3、recovery/目錄中的recovery-from-boot.p是boot.img和recovery.img的補丁(patch),主要用來更新recovery分區,其中etc/目錄下的install-recovery.sh是執行更新的腳本。
4、update-binary是一個二進制文件,相當于一個腳本解釋器,能夠識別updater-script中描述的操作。它是Android源碼編譯后生成的out/target/product/xxxx/systembin/updater文件,可將updater重命名為update-binary得到。該文件在具體的更新包中的名字由源碼中bootable/recovery/install.c中的宏ASSUMED_UPDATE_BINARY_NAME的值而定。
5、updater-script:此文件是一個腳本文件,具體描述了更新過程。我們可以根據具體情況編寫該腳本來適應我們的具體需求。該文件的命名由源碼中bootable/recovery/updater/updater.c文件中的宏SCRIPT_NAME的值而定。
6、metadata文件是描述設備信息及環境變量的元數據。主要包括一些編譯選項,簽名公鑰,時間戳以及設備型號等。
7、我們還可以在包中添加userdata目錄,來更新系統中的用戶數據部分。這部分內容在更新后會存放在系統的/data目錄下。
8、update.zip包的簽名:update.zip更新包在制作完成后需要對其簽名,否則在升級時會出現認證失敗的錯誤提示。而且簽名要使用和目標板一致的加密公鑰。加密公鑰及加密需要的三個文件在Android源碼編譯后生成的具體路徑為:
out/host/linux-x86/framework/signapk.jar
build/target/product/security/testkey.x509.pem
build/target/product/security/testkey.pk8。
我們用命令makeotapackage制作生成的update.zip包是已簽過名的,如果自己做update.zip包時必須手動對其簽名。具體的加密方法:
$ java –jar gingerbread/out/host/linux/framework/signapk.jar –wgingerbread/build/target/product/security/testkey.x509.pem gingerbread/build/target/product/security/testkey.pk8update.zip update_signed.zip
以上命令在update.zip包所在的路徑下執行,其中signapk.jartestkey.x509.pem以及testkey.pk8文件的引用使用絕對路徑。update.zip 是我們已經打好的包,update_signed.zip包是命令執行完生成的已經簽過名的包。
9、MANIFEST.MF:這個manifest文件定義了與包的組成結構相關的數據。類似Android應用的mainfest.xml文件。
10、CERT.RSA:與簽名文件相關聯的簽名程序塊文件,它存儲了用于簽名JAR文件的公共簽名。
11、CERT.SF:這是JAR文件的簽名文件,其中前綴CERT代表簽名者。
另外,在具體升級時,對update.zip包檢查時大致會分三步:①檢驗SF文件與RSA文件是否匹配。②檢驗MANIFEST.MF與簽名文件中的digest是否一致。③檢驗包中的文件與MANIFEST中所描述的是否一致。
12、在做的MTK平臺的項目(如8670、9976),需要增加與項目強相關的適配文件(scatter.txt、SEC_VER.txt、type.txt),scatter.txt分散加載文件,將可執行映像文件分散加載到不同的內存段(文件內容:指定不同內存段的起始地址)。
type.txt是build升級包過程生成的,里面的值1代表FullOTA,0代表DiffOTA,android的上層的update流程中會check這個值。
scatter.txt也是build升級包過程生成的,里面的內容來自于/mediatek/misc/ota_scatter.txt。具體code可見腳本ota_from_target_files。而mediatek/misc/ota_scatter.txt是在build full ota時會產生。該文件主要用于在升級的時候check升級前后parition layout是否有改變。
SEC_VER.TXT是在編譯時從alpsmediatekcustom$PROJECTsecurityecovery下copy過來的,用于在打開SUPPORT_SBOOT_UPDATE之后會使用,具體可以參考[FAQ05739] SD或者OTA升級secutiry device和non-security device的區別。
在./mk new時,會執行ptgen,執行ptgen會依賴OTA_SCATTER_FILE,見mediatek/build/libs/pregen.mk:218,然后再build/tools/makeMtk.mk中的142以及692會生成ota_scatter.txt。
2.3 OTA升級包制作工程分析
升級包有整包與差分包之分。顧名思義,所謂整包即包含整個system分區中的數據文件;而差分包則僅包含兩個版本之間改動的部分。利用整包升級好比對電腦進行重作系統,格式化系統分區,并將新系統數據寫入分區;而利用差分包升級不會格式化system分區,只是對其中部分存儲段的內容進行重寫。除升級包之外,制作過程中還會涉及到另一種zip包,代碼中稱之為8675-cota-target_files-xxx.zip,我稱之為差分資源包。首先闡述下整包的制作過程。
2.3.1 整包的制作
系統經過整編后,執行makeotapackage命令,即可完成整包的制作,而此命令可分為兩個階段進行。首先執行./build/core/Makefile中的代碼:
#-----------------------------------------------------------------
# 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目標代碼
如上代碼是Makefile文件中目標otapackage的執行代碼。首先,make otapackage命令會執行Makefile(./build/core/Makefile)中otapackage的目標代碼(如代碼1所示)。由代碼可知,otapackage目標的執行只依賴于$(INTERNAL_OTA_PACKAGE_TARGET),而不存在任何規則(根據Makefile語法,規則必須以TAB鍵開始,而目標otapackage的定義之后卻是變量name的聲明,因此不存在規則),因此只需要關注目標$(INTERNAL_OTA_PACKAGE_TARGET)的生成。顯然,此目標的生成依賴于目標文件:$(BUILT_TARGET_FILES_PACKAGE)和$(OTATOOLS),且其執行的命令為./build/tools/releasetools/ota_from_target_files。也就是說,make otapackage所完成的功能全是通過這兩個目標文件和執行的命令完成的,我們將分別對這三個關鍵點進行分析。
a)$(OTATOOLS)
目標文件OTATOOLS的編譯規則如下所示
1.OTATOOLS:=$(HOST_OUT_EXECUTABLES)/minigzip
2.$(HOST_OUT_EXECUTABLES)/mkbootfs
3.$(HOST_OUT_EXECUTABLES)/mkbootimg
4.$(HOST_OUT_EXECUTABLES)/fs_config
5.$(HOST_OUT_EXECUTABLES)/mkyaffs2image
6.$(HOST_OUT_EXECUTABLES)/zipalign
7.$(HOST_OUT_EXECUTABLES)/aapt
8.$(HOST_OUT_EXECUTABLES)/bsdiff
9.$(HOST_OUT_EXECUTABLES)/imgdiff
10.$(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar
11.$(HOST_OUT_JAVA_LIBRARIES)/signapk.jar
12.$(HOST_OUT_EXECUTABLES)/mkuserimg.sh
13.$(HOST_OUT_EXECUTABLES)/genext2fs
14.$(HOST_OUT_EXECUTABLES)/tune2fs
15.$(HOST_OUT_EXECUTABLES)/e2fsck
16.$(HOST_OUT_EXECUTABLES)/make_ext4fs
17.
18..PHONY:otatools
19.otatools:$(OTATOOLS)
可以看出變量OTATOOLS為系統中一系列文件的集合。那么這些文件又有什么用處呢? 事實上,這些文件用于壓縮(minigzip:用于gzip文件;make_ext4fs:將文件轉換為ext4類型;mkyaffs2image:用于yaffs文件系統;......)、解壓縮、差分(bsdiff,imgdiff)、簽名(singapk.jar)等功能,結合代碼段1可得到如下結論:目標$(INTERNAL_OTA_PACKAGE_TARGET)的執行依賴于這一系列系統工具--僅此而已。也就是說,目標文件$(OTATOOLS)僅僅指定了命令執行所需要的工具,并未執行任何操作。
注:變量$(HOST_OUT_EXECUTABLES)指代的是out/host/linux-x86/bin目錄,而變量$(HOST_OUT_JAVA_LIBRARIES)/表示的是out/host/linux-x86/framework目錄,這意味著我們可以從此目錄下找到上述工具,并為我們所用。
b)$(BUILT_TARGET_FILES_PACKAGE)
目標OTATOOLS指明了執行makeotapackage命令所需要的系統工具,而目標$(BUILT_TARGE_FILES_PACKAGE)的生成則完成了兩件事:重新打包system.img文件和生成差分資源包。$(BUILT_TARGE_FILES_PACKAGE)的編譯規則如下所示:
1.#-----------------------------------------------------------------
2.#Azipofthedirectoriesthatmaptothetargetfilesystem.
3.#ThiszipcanbeusedtocreateanOTApackageorfilesystemimage
4.#asapost-buildstep.
5.#
6.name:=$(TARGET_PRODUCT)
7.ifeq($(TARGET_BUILD_TYPE),debug)
8.name:=$(name)_debug
9.endif
10.name:=$(name)-target_files-$(FILE_NAME_TAG)
11.
12.intermediates:=$(callintermediates-dir-for,PACKAGING,target_files)
13.BUILT_TARGET_FILES_PACKAGE:=$(intermediates)/$(name).zip
14.$(BUILT_TARGET_FILES_PACKAGE):intermediates:=$(intermediates)
15.$(BUILT_TARGET_FILES_PACKAGE):
16.zip_root:=$(intermediates)/$(name)
17.
18.#$(1):Directorytocopy
19.#$(2):Locationtocopyitto
20.#The"ls-A"istoprevent"acps/*d"fromfailingifsisempty.
21.definepackage_files-copy-root
22.if[-d"$(strip$(1))"-a"$$(ls-A$(1))"];then
23.mkdir-p$(2)&&
24.$(ACP)-rd$(strip$(1))/*$(2);
25.fi
26.endef
27.
28.built_ota_tools:=
29.$(callintermediates-dir-for,EXECUTABLES,applypatch)/applypatch
30.$(callintermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static
31.$(callintermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq
32.$(callintermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3
33.$(callintermediates-dir-for,EXECUTABLES,updater)/updater
34.$(BUILT_TARGET_FILES_PACKAGE):PRIVATE_OTA_TOOLS:=$(built_ota_tools)
35.
36.$(BUILT_TARGET_FILES_PACKAGE):PRIVATE_RECOVERY_API_VERSION:=$(RECOVERY_API_VERSION)
37.
38.ifeq($(TARGET_RELEASETOOLS_EXTENSIONS),)
39.#defaulttocommondirfordevicevendor
40.$(BUILT_TARGET_FILES_PACKAGE):tool_extensions:=$(TARGET_DEVICE_DIR)/../common
41.else
42.$(BUILT_TARGET_FILES_PACKAGE):tool_extensions:=$(TARGET_RELEASETOOLS_EXTENSIONS)
43.endif
44.
45.#Dependingonthevariousimagesguaranteesthattheunderlying
46.#directoriesareup-to-date.
47.
48.ifeq($(TARGET_USERIMAGES_USE_EXT4),true)
49.$(BUILT_TARGET_FILES_PACKAGE):$(INSTALLED_CACHEIMAGE_TARGET)
50.endif
51.
52.$(BUILT_TARGET_FILES_PACKAGE):
53.$(INSTALLED_BOOTIMAGE_TARGET)
54.$(INSTALLED_RADIOIMAGE_TARGET)
55.$(INSTALLED_RECOVERYIMAGE_TARGET)
56.$(INSTALLED_FACTORYIMAGE_TARGET)
57.$(INSTALLED_SYSTEMIMAGE)
58.$(INSTALLED_CACHEIMAGE_TARGET)
59.$(INSTALLED_USERDATAIMAGE_TARGET)
60.$(INSTALLED_SECROIMAGE_TARGET)
61.$(INSTALLED_ANDROID_INFO_TXT_TARGET)
62.$(built_ota_tools)
63.$(APKCERTS_FILE)
64.$(HOST_OUT_EXECUTABLES)/fs_config
65.|$(ACP)
66.@echo"Packagetargetfiles:$@"
67.$(hide)rm-rf$@$(zip_root)
68.$(hide)mkdir-p$(dir$@)$(zip_root)
69.@#Componentsoftherecoveryimage
70.$(hide)mkdir-p$(zip_root)/RECOVERY
71.$(hide)$(callpackage_files-copy-root,
72.$(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)
73.ifdefINSTALLED_KERNEL_TARGET
74.$(hide)$(ACP)$(INSTALLED_KERNEL_TARGET)$(zip_root)/RECOVERY/kernel
75.$(hide)$(ACP)$(recovery_ramdisk)$(zip_root)/RECOVERY/ramdisk
76.endif
77.ifdefINSTALLED_2NDBOOTLOADER_TARGET
78.$(hide)$(ACP)
79.$(INSTALLED_2NDBOOTLOADER_TARGET)$(zip_root)/RECOVERY/second
80.endif
81.ifdefBOARD_KERNEL_CMDLINE
82.$(hide)echo"$(BOARD_KERNEL_CMDLINE)">$(zip_root)/RECOVERY/cmdline
83.endif
84.ifdefBOARD_KERNEL_BASE
85.$(hide)echo"$(BOARD_KERNEL_BASE)">$(zip_root)/RECOVERY/base
86.endif
87.@#Componentsofthefactoryimage
88.$(hide)mkdir-p$(zip_root)/FACTORY
89.$(hide)$(callpackage_files-copy-root,
90.$(TARGET_FACTORY_ROOT_OUT),$(zip_root)/FACTORY/RAMDISK)
91.ifdefINSTALLED_KERNEL_TARGET
92.$(hide)$(ACP)$(INSTALLED_KERNEL_TARGET)$(zip_root)/FACTORY/kernel
93.endif
94.ifdefBOARD_KERNEL_PAGESIZE
95.$(hide)echo"$(BOARD_KERNEL_PAGESIZE)">$(zip_root)/RECOVERY/pagesize
96.endif
97.ifdefINSTALLED_2NDBOOTLOADER_TARGET
98.$(hide)$(ACP)
99.$(INSTALLED_2NDBOOTLOADER_TARGET)$(zip_root)/FACTORY/second
100.endif
101.ifdefBOARD_KERNEL_CMDLINE
102.$(hide)echo"$(BOARD_KERNEL_CMDLINE)">$(zip_root)/FACTORY/cmdline
103.endif
104.ifdefBOARD_KERNEL_BASE
105.$(hide)echo"$(BOARD_KERNEL_BASE)">$(zip_root)/FACTORY/base
106.endif
107.@#Componentsofthebootimage
108.$(hide)mkdir-p$(zip_root)/BOOT
109.$(hide)$(callpackage_files-copy-root,
110.$(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)
111.ifdefINSTALLED_KERNEL_TARGET
112.$(hide)$(ACP)$(INSTALLED_KERNEL_TARGET)$(zip_root)/BOOT/kernel
113.$(hide)$(ACP)$(INSTALLED_RAMDISK_TARGET)$(zip_root)/BOOT/ramdisk
114.endif
115.ifdefINSTALLED_2NDBOOTLOADER_TARGET
116.$(hide)$(ACP)
117.$(INSTALLED_2NDBOOTLOADER_TARGET)$(zip_root)/BOOT/second
118.endif
119.ifdefBOARD_KERNEL_CMDLINE
120.$(hide)echo"$(BOARD_KERNEL_CMDLINE)">$(zip_root)/BOOT/cmdline
121.endif
122.ifdefBOARD_KERNEL_BASE
123.$(hide)echo"$(BOARD_KERNEL_BASE)">$(zip_root)/BOOT/base
124.endif
125.ifdefBOARD_KERNEL_PAGESIZE
126.$(hide)echo"$(BOARD_KERNEL_PAGESIZE)">$(zip_root)/BOOT/pagesize
127.endif
128.#wschen
129.ifneq"""$(CUSTOM_BUILD_VERNO)"
130.$(hide)echo"$(CUSTOM_BUILD_VERNO)">$(zip_root)/BOOT/board
131.endif
132.
133.#[etonbegin]:addedbyLiuDekuanforu-bootupdate
134.$(hide)$(ACP)$(PRODUCT_OUT)/uboot_eyang77_ics2.bin$(zip_root)/uboot.bin
135.#[etonend]
136.
137.$(hide)$(foreacht,$(INSTALLED_RADIOIMAGE_TARGET),
138.mkdir-p$(zip_root)/RADIO;
139.$(ACP)$(t)$(zip_root)/RADIO/$(notdir$(t));)
140.@#Contentsofthesystemimage
141.$(hide)$(callpackage_files-copy-root,
142.$(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)
143.@#Contentsofthedataimage
144.$(hide)$(callpackage_files-copy-root,
145.$(TARGET_OUT_DATA),$(zip_root)/DATA)
146.@#ExtracontentsoftheOTApackage
147.$(hide)mkdir-p$(zip_root)/OTA/bin
148.$(hide)$(ACP)$(INSTALLED_ANDROID_INFO_TXT_TARGET)$(zip_root)/OTA/
149.$(hide)$(ACP)$(PRIVATE_OTA_TOOLS)$(zip_root)/OTA/bin/
150.@#SecurityinformationoftheOTApackage
151.@echo"[SECOTA]AddingSecurityinformationtoOTApackage"
152.@echo"[SECOTA]path:mediatek/custom/$(MTK_PROJECT)/security/recovery/SEC_VER.txt"
153.$(hide)$(ACP)mediatek/custom/$(MTK_PROJECT)/security/recovery/SEC_VER.txt$(zip_root)/OTA/
154.@#Filesthatdonotendupinanyimages,butarenecessaryto
155.@#buildthem.
156.$(hide)mkdir-p$(zip_root)/META
157.$(hide)$(ACP)$(APKCERTS_FILE)$(zip_root)/META/apkcerts.txt
158.$(hide)echo"$(PRODUCT_OTA_PUBLIC_KEYS)">$(zip_root)/META/otakeys.txt
159.$(hide)echo"recovery_api_version=$(PRIVATE_RECOVERY_API_VERSION)">$(zip_root)/META/misc_info.txt
160.ifdefBOARD_FLASH_BLOCK_SIZE
161.$(hide)echo"blocksize=$(BOARD_FLASH_BLOCK_SIZE)">>$(zip_root)/META/misc_info.txt
162.endif
163.ifdefBOARD_BOOTIMAGE_PARTITION_SIZE
164.$(hide)echo"boot_size=$(BOARD_BOOTIMAGE_PARTITION_SIZE)">>$(zip_root)/META/misc_info.txt
165.endif
166.ifdefBOARD_RECOVERYIMAGE_PARTITION_SIZE
167.$(hide)echo"recovery_size=$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)">>$(zip_root)/META/misc_info.txt
168.endif
169.ifdefBOARD_SYSTEMIMAGE_PARTITION_SIZE
170.$(hide)echo"system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)">>$(zip_root)/META/misc_info.txt
171.endif
172.ifdefBOARD_SECROIMAGE_PARTITION_SIZE
173.$(hide)echo"secro_size=$(BOARD_SECROIMAGE_PARTITION_SIZE)">>$(zip_root)/META/misc_info.txt
174.endif
175.ifdefBOARD_CACHEIMAGE_PARTITION_SIZE
176.$(hide)echo"cache_size=$(BOARD_CACHEIMAGE_PARTITION_SIZE)">>$(zip_root)/META/misc_info.txt
177.endif
178.ifdefBOARD_USERDATAIMAGE_PARTITION_SIZE
179.$(hide)echo"userdata_size=$(BOARD_USERDATAIMAGE_PARTITION_SIZE)">>$(zip_root)/META/misc_info.txt
180.endif
181.$(hide)echo"tool_extensions=$(tool_extensions)">>$(zip_root)/META/misc_info.txt
182.ifdefmkyaffs2_extra_flags
183.$(hide)echo"mkyaffs2_extra_flags=$(mkyaffs2_extra_flags)">>$(zip_root)/META/misc_info.txt
184.endif
185.ifdefINTERNAL_USERIMAGES_SPARSE_EXT_FLAG
186.$(hide)echo"extfs_sparse_flag=$(INTERNAL_USERIMAGES_SPARSE_EXT_FLAG)">>$(zip_root)/META/misc_info.txt
187.endif
188.$(hide)echo"default_system_dev_certificate=$(DEFAULT_KEY_CERT_PAIR)">>$(zip_root)/META/misc_info.txt
189.ifdefPRODUCT_EXTRA_RECOVERY_KEYS
190.$(hide)echo"extra_recovery_keys=$(PRODUCT_EXTRA_RECOVERY_KEYS)">>$(zip_root)/META/misc_info.txt
191.endif
192.@#Zipeverythingup,preservingsymlinks
193.$(hide)(cd$(zip_root)&&zip-qry../$(notdir$@).)
194.@#Runfs_configonallthesystem,bootramdisk,andrecoveryramdiskfilesinthezip,andsavetheoutput
195.$(hide)zipinfo-1$@|awk'BEGIN{FS="SYSTEM/"}/^SYSTEM//{print"system/"$$2}'|$(HOST_OUT_EXECUTABLES)/fs_config>$(zip_root)/META/filesystem_config.txt
196.$(hide)zipinfo-1$@|awk'BEGIN{FS="BOOT/RAMDISK/"}/^BOOT/RAMDISK//{print$$2}'|$(HOST_OUT_EXECUTABLES)/fs_config>$(zip_root)/META/boot_filesystem_config.txt
197.$(hide)zipinfo-1$@|awk'BEGIN{FS="RECOVERY/RAMDISK/"}/^RECOVERY/RAMDISK//{print$$2}'|$(HOST_OUT_EXECUTABLES)/fs_config>$(zip_root)/META/recovery_filesystem_config.txt
198.$(hide)(cd$(zip_root)&&zip-q../$(notdir$@)META/*filesystem_config.txt)
199.target-files-package:$(BUILT_TARGET_FILES_PACKAGE)
200.ifneq($(TARGET_PRODUCT),sdk)
201.ifeq($(filtergeneric%,$(TARGET_DEVICE)),)
202.ifneq($(TARGET_NO_KERNEL),true)
203.ifneq($(recovery_fstab),)
system.img文件的重新打包是通過$(BUILT_TARGE_FILES_PACKAGE)的依賴條件$(INSTALLED_SYSTEMIMAGE)目標文件的編譯來完成的,而$(BUILT_TARGE_FILES_PACKAGE)所有的執行命令(代碼第66行至最后)都只為完成一件事,生成差分資源包所對應的目錄并將其打包為ZIP包。具體的操作包括:
·創建$(zip_root)目錄(代碼第66~68行),$(zip_root)即out/target/product/<product-name>/obj/PACKAGING/target_files_from_intermedias/<product-name>-target_files-<version-name>;
·創建/$(zip_root)/RECOVERY目錄并將COPY相關文件(代碼69~86);
·創建/$(zip_root)/FACTORY目錄并將COPY相關文件(代碼87~106);
·創建/$(zip_root)/BOOT目錄并將COPY相關文件(代碼107~131);
·創建其他目錄并COPY文件(代碼132~191);
·將$(zip_root)目錄壓縮為資源差分包(代碼192~198)等。
經過目標文件$(BUILT_TARGE_FILES_PACKAGE)的執行后,system.img已經被重新打包,且差分資源包也已經生成,剩下的工作就是將差分資源包(即target-files zipfile,下文將統一使用“差分資源包”這一概念)傳遞給ota_ from _target _files代碼,由它來生成OTA整包。
總之,前述代碼的作用就在于將系統資源(包括system、recovery、boot等目錄)重新打包,生成差分資源包。我們可以看下差分資源包中的文件結構,如下:
圖2 target-fileszipfile目錄結構
其中,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中包含有制作升級包所需操作的代碼,各種工具類,參數處理/META文件處理/image生成/signcertification/patch file操作等等;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-binary。摘取部分代碼片段如下:
1.script.FormatPartition("/system")
2.script.FormatPartition("/system")
3.script.UnpackPackageDir("recovery","/system")
4.script.UnpackPackageDir("system","/system")
5.(symlinks,retouch_files)=CopySystemFiles(input_zip,output_zip)
6.script.MakeSymlinks(symlinks)
7.ifOPTIONS.aslr_mode:
8.script.RetouchBinaries(retouch_files)
9.else:
10.script.UndoRetouchBinaries(retouch_files)
代碼2WriteFullOTAPackage代碼片段
其中的script為edify_generator對象,其FormatPartition、UnpackPackageDir等方法分別是向腳本文件update-script中寫入格式化分區、解壓包等指令
1.defAddToZip(self,input_zip,output_zip,input_path=None):
2."""Writetheaccumulatedscripttotheoutput_zipfile.input_zip
3.isusedasthesourceforthe'updater'binaryneededtorun
4.script.Ifinput_pathisnotNone,itwillbeusedasalocal
5.pathforthebinaryinsteadofinput_zip."""
6.
7.self.UnmountAll()
8.common.ZipWriteStr(output_zip,"META-INF/com/google/android/updater-script",
9."
".join(self.script)+"
")
10.ifinput_pathisNone:
11.data=input_zip.read("OTA/bin/updater")
12.else:
13.data=open(os.path.join(input_path,"updater")).read()
14.common.ZipWriteStr(output_zip,"META-INF/com/google/android/update-binary",
15.data,perms=0755)
代碼段3edify_generator中的AddToZip方法
WriteFullOTAPackage執行的最后會調用此方法。將資源差分包中OTA/bin/updater文件copy到升級包中META-INF/com/google/android/update-binary。此文件是OTA升級的關鍵,其將在recovery模式下被執行,用來將代碼段2中生成的指令轉換為相應的函數去執行,從而完成對系統數據的重寫。
2.3.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。見如下代碼:
1.constchar**args=(constchar**)malloc(sizeof(char*)*5);
2.args[0]=binary;
3.args[1]=EXPAND(RECOVERY_API_VERSION);//definedinAndroid.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_tpid=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返回的值來判斷當前進程是子進程還是父進程。
int execv(const char *progname, char *constargv[])
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目錄下,存在著如下幾個文件:
通過查看Android.mk代碼可知,文件install.c、updater.c將會被編譯為可執行文件updater存放到目錄out/target/product/<product-name>/obj/EXECUTABLES/updater_intermediates/中;而在生成差分資源包(target-fileszipfile)時,會將此文件添加到壓縮包中。
1.built_ota_tools:=
2.$(callintermediates-dir-for,EXECUTABLES,applypatch)/applypatch
3.$(callintermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static
4.$(callintermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq
5.$(callintermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3
6.$(callintermediates-dir-for,EXECUTABLES,updater)/updater
7.$(hide)mkdir-p$(zip_root)/OTA/bin
8.$(hide)$(ACP)$(INSTALLED_ANDROID_INFO_TXT_TARGET)$(zip_root)/OTA/
9.$(hide)$(ACP)$(PRIVATE_OTA_TOOLS)$(zip_root)/OTA/bin/
代碼段5 Makefile中定義的變量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函數)。
3、OTA升級過程分析
3.1 Recovery模式簡介
所謂 Recovery 是智能手機的一種特殊工作模式,有點類似于 Windows 下的 DOS 工具箱,系統進入到這種模式下時,可以通過按鍵選擇相應的操作菜單,從而實現相應的功能,比如 android 系統數據區的快速格式化(即恢復出廠設置);通過 SD 卡進行OTA系統升級及固件(firmware)升級等。我們公司的手機一般進入 recovery 模式的方法是電源鍵加音量上鍵。后面會詳細分析系統進入Recovery模式的過程以及在Recovery模式下進行OTA升級的過程。
設置模塊中進行恢復出廠設置,OTA升級,patch升級及firmware升級等操作,系統一共做了兩件事:
1. 往 /cache/recovery/command 文件中寫入命令字段;
2. 重啟系統
重啟系統后必須進入Recovery模式,接下來分析進入Recovery模式的流程。
3.2 Recovery模式解析
3.2.1 如何進入Recovery模式
手機開機后,硬件系統上電,首先是完成一系列的初始化過程,如 CPU、串口、中斷、timer、DDR 等硬件設備,然后接著加載 bootloader,為后面內核的加載作好準備。在一些系統啟動必要的初始完成之后,系統會通過檢測三個條件來判斷要進入何種工作模式,流程如圖。這一部分代碼的源文件在ootableootloaderlkappabootaboot.c 文件的aboot_init()函數中:
從源代碼中可以看出,開機時按住HOME鍵或音量上鍵(不同手機對此會有修改),會進入Recovery模式;按著返回鍵或音量下鍵會進入fastboot模式。如果沒有組合鍵(代碼中成為magic key)按下,則會檢測SMEM中reboot_mode變量的值,代碼如下:
reboot_mode可取的值為RECOVERY_MODE和FASTBOOT_MODE的宏定義,分別為:
reboot_mode的取值由check_reboot_mode()函數返回,接下來我們看該函數在ootableootloaderlk argetmsm8660_surfinit.c中的定義:
可以看到該函數先讀取地址restart_reason_addr處的值,最后返回該值,restart_reason_addr地址定義為0x2A05F65C,從此處讀完值后會向改地址寫入0x00,即將其內容擦除,這樣做是為了防止下次進入時又進入Recovery模式。
如果restart_reason_addr處沒有值被讀到,則會繼續讀取MISC分區的BCB段進行判斷,調用的函數為recovery_init()。這里解釋一下BCB(BootloaderControl Block),BCB 是 bootloader 與 Recovery 的通信接口,也是 Bootloader 與 Main system 之間的通信接口。存儲在flash 中的 MISC 分區,占用三個page,其本身就是一個結構體,具體成員以及各成員含義如下:
command 字段:該字段的值會在Android系統需要進入recovery模式的時候被Android更新。另外在固件更新成功時,也會被更新,以進入 recovery 模式來做一些收尾的清理工作。在更新成功后結束 Recovery時,會清除這個字段的值,防止重啟時再次進入 Recovery 模式。
status 字段:在完成"update-radio" 或者 "update-hboot"更新后,bootloader會將執行結果寫入到這個字段。
recovery 字段:僅可以被Main System寫入,用來向recovery發送消息。該文件的內容格式為:
recovery
<recovery command>
<recovery command>
該文件存儲的是一個字符串,必須以“recovery
”開頭,否則這個字段的所有內容域會被忽略。“recovery
”之后的部分,是/cache/recovery/command文件支持的命令。可以將其理解為Recovery操作過程中對命令操作的備份。Recovery會先讀取BCB,然后讀取/cache/recovery/command,然后將二者重新寫回BCB,這樣在進入 Mainsystem 之前,確保操作被執行。在操作之后進入 Main system 之前,Recovery 又會清空 BCB 的 command 域和 recovery 域,這樣確保重啟后不再進入 Recovery 模式。
解釋完BCB字段的內容,我們再回過頭來,調用recovery_init()的代碼如下:
該函數的定義在bootloaderlkappabootecovery.c中。
recovery_init()函數會先通過get_recovery_message(&msg)函數把BCB段的內容讀取到recovery_message結構體中,再讀取其command字段,如果字段是boot-recovery,則進入recovery模式;如果是update-radio,則進入固件升級流程。get_recovery_message(&msg)函數的代碼如下。
系統判斷進入哪種工作模式的流程如下圖所示。如果以上條件皆不滿足,則進入正常啟動序列,系統會加載 boot.img 文件,然后加載 kernel,在內核加載完成之后,會根據內核的傳遞參數尋找 android 的第一個用戶態進程,即 init 進程,該進程根據 init.rc以及 init.$(hardware).rc 腳本文件來啟動 android 的必要的服務,直到完成 android 系統的啟動。
當進入 recovery 模式時,系統加載的是recovery.img 文件,該文件內容與 boot.img 類似,也包含了標準的內核和根文件系統。但是 recovery.img 為了具有恢復系統的能力,比普通的 boot.img 目錄結構中:
1、多了/res/images 目錄,在這個目錄下的圖片是恢復時我們看到的背景畫面。
2、多了/sbin/recovery 二進制程序,這個就是恢復用的程序。
3、/sbin/adbd 不一樣,recovery 模式下的 adbd 不支持 shell。
4、初始化程序(init)和初始化配置文件(init.rc)都不一樣。這就是系統沒有進入圖形界面而進入了類似文本界面,并可以通過簡單的組合鍵進行恢復的原因。與正常啟動系統類似,也是啟動內核,然后啟動文件系統。在進入文件系統后會執行/init,init 的配置文件就是 /init.rc。這個配置文件位于bootable/recovery/etc/init.rc。查看這個文件我們可以看到它做的事情很簡單:
1) 設置環境變量。
2) 建立 etc 連接。
3) 新建目錄,備用。
4) 掛載文件系統。
5) 啟動 recovery(/sbin/recovery)服務。
6) 啟動 adbd 服務(用于調試)。
上文所提到的fastboot 模式,即命令或 SD 卡燒寫模式,不加載內核及文件系統,此處可以進行工廠模式的燒寫。
綜上所述,有三種進入recovery 模式的方法,分別是開機時按組合鍵,寫 SMEM 中的 reboot_mode變量值,以及寫位于 MISC 分區的 BCB 中的 command 字段。
3.2.2Recovery模式的三個組成部分
Recovery 的工作需要整個軟件平臺的配合,從通信架構上來看,主要有以下三個部分:
1.Main System:即上面提到的正常啟動模式(BCB 中無命令),是用 boot.img 啟動的系統, Android的正常工作模式。更新時,在這種模式中我們的上層操作就是使用 OTA 或則從 SD 卡中升級 update.zip升級包。
2.Recovery:系統進入 Recovery 模式后會裝載 Recovery 分區,該分區包含 recovery.img (同 boot.img相同,包含了標準的內核和根文件系統)。進入該模式后主要是運行 Recovery 服務(/sbin/recovery)來做相應的操作。
3.Bootloader:除了正常的加載啟動系統之外,還會通過讀取 MISC 分區(BCB)獲得來自 Main System和 Recovery 的消息。
這三個實體之間的通信是必不可少的,他們相互之間有如下兩個通信接口:一個是通過 CACHE 分區中的三個文件(command、log、intent);另一個是前面提到的MISC分區的BCB段。
Recovery 的服務內容主要有三類:
①FACTORYRESET,恢復出廠設置。
②OTA INSTALL,即我們的update.zip 包升級。
③ENCRYPTEDFILE SYSTEM ENABLE/DISABLE,使能/關閉加密文件系統。
本文主要關心OTA升級的流程,所以下面的內容主要解釋從上層應用點擊進行OTA升級到重啟進入Recovery模式進行升級包安裝的過程。
我們只看從MainSystem如何進入Recovery模式,其他的通信暫不討論。先從Main System開始看,當我們在Main System使用update.zip包進行升級時,系統會重啟并進入Recovery模式。在系統重啟之前,我們可以看到,Main System一定會向BCB中的command域寫入boot-recovery(粉紅色線),用來告知Bootloader重啟后進入recovery模式。這一步是必須的。至于Main System是否向recovery域寫入值我們在源碼中不能肯定這一點。即便如此,重啟進入Recovery模式后Bootloader會從/cache/recovery/command中讀取值并放入到BCB的recovery域。而MainSystem在重啟之前肯定會向/cache/recovery/command中寫入Recovery將要進行的操作命令。
3.2.3 從上層進入Recovery服務流程細節
Ø從SystemUpdate到Reboot
假設我們進入系統更新應用后,已下載完OTA包到SD卡,會彈出一個對話框,提示已有update.zip包是否現在更新,我們從這個地方跟蹤。這個對話框的源碼是SystemUpdateInstallDialog.java。
① 在mNowButton按鈕的監聽事件里,會調用mService.rebootAndUpdate(newFile(mFile))。這個mService就是SystemUpdateService的實例。這個類所在的源碼文件是SystemUpdateService.java。這個函數的參數是一個文件。它肯定就是我們的update.zip包了。我們可以證實一下這個猜想。
②mFile的值:在SystemUpdateInstallDialog.java中的ServiceConnection中我們可以看到這個mFile的值有兩個來源。
來源一:
mFile的一個來源是這個是否立即更新提示框接受的上一個Activity以“file”標記傳來的值。這個Activity就是SystemUpdate.java。它是一個PreferenceActivity類型的。在其onPreferenceChange函數中定義了向下一個Activity傳送的值,這個值是根據我們不同的選擇而定的。如果我們在之前選擇了從SD卡安裝,則這個傳下去的“file”值為“/sdcard/update.zip”。如果選擇了從NAND安裝,則對應的值為“/nand/update.zip”。
來源二:
另個一來源是從mService.getInstallFile()獲得。我們進一步跟蹤就可發現上面這個函數獲得的值就是“/cache”+mUpdateFileURL.getFile();這就是OTA在線下載后對應的文件路徑。不論參數mFile的來源如何,我們可以發現在mNowButton按鈕的監聽事件里是將整個文件,也就是我們的update.zip包作為參數往rebootAndUpdate()中傳遞的。
③rebootAndUpdate:在這個函數中MainSystem做了重啟前的準備。繼續跟蹤下去會發現,在SystemUpdateService.java中的rebootAndUpdate函數中新建了一個線程,在這個線程中最后調用的就是RecoverySystem.installPackage(mContext,mFile),我們的update.zip包也被傳遞進來了。
④RecoverySystem類:RecoverySystem類的源碼所在文件路徑為: *****/frameworks/base/core/java/android/os/RecoverySystem.java。我們關心的是installPackage(Contextcontext,FilepackageFile)函數。這個函數首先根據我們傳過來的包文件,獲取這個包文件的絕對路徑filename。然后將其拼成arg=“--update_package=”+filename。它最終會被寫入到BCB中。這個就是重啟進入Recovery模式后,Recovery服務要進行的操作。它被傳遞到函數bootCommand(context,arg)。
⑤bootCommand():在這個函數中才是MainSystem在重啟前真正做的準備。主要做了以下事情,首先創建/cache/recovery/目錄,刪除這個目錄下的command和log(可能不存在)文件在sqlite數據庫中的備份。然后將上面④步中的arg命令寫入到/cache/recovery/command文件中。下一步就是真正重啟了。接下來看一下在重啟函數reboot中所做的事情。
⑥pm.reboot():重啟之前先獲得了PowerManager(電源管理)并進一步獲得其系統服務。然后調用了pm.reboot(“recovery”)函數。該函數最后找到是E:MTK6592(Original)alpssystemcorelibcutilsandroid_reboot.c中的reboot函數。這個函數實際上是一個系統調用,即__reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,LINUX_REBOOT_CMD_RESTART2, arg);從這個函數我們可以看出前兩個參數就代表了我們的組合鍵,LINUX_REBOOT_CMD_RESTART2就是我們傳過來的“recovery”。再進一步跟蹤就到了匯編代碼了,我們無法直接查看它的具體實現細節。但可以肯定的是這個函數只將“recovery”參數傳遞過去了,之后將“boot-recovery”寫入到了MISC分區的BCB數據塊的command域中。這樣在重啟之后Bootloader才知道要進入Recovery模式。
在這里我們無法肯定MainSystem在重啟之前對BCB的recovery域是否進行了操作。其實在重啟前是否更新BCB的recovery域是不重要的,因為進入Recovery服務后,Recovery會自動去/cache/recovery/command中讀取要進行的操作然后寫入到BCB的recovery域中。
至此,MainSystem就開始重啟并進入Recovery模式。在這之前Main System做的最實質的就是兩件事,一是將“boot-recovery”寫入BCB的command域,二是將--update_package=/cache/update.zip”或則“--update_package=/sdcard/update.zip”寫入/cache/recovery/command文件中。下面的部分就開始重啟并進入Recovery服務了。
Ø從reboot到Recovery服務
這個過程我們在上文(對照第一個圖)已經講過了。從Bootloader開始如果沒有組合鍵按下,就從MISC分區讀取BCB塊的command域(在主系統時已經將“boot-recovery”寫入)。然后就以Recovery模式開始啟動。與正常啟動不同的是Recovery模式下加載的鏡像是recovery.img。這個鏡像同boot.img類似,也包含了標準的內核和根文件系統。其后就與正常的啟動系統類似,也是啟動內核,然后啟動文件系統。在進入文件系統后會執行/init,init的配置文件就是/init.rc。這個配置文件來自bootable/recovery/etc/init.rc。查看這個文件我們可以看到它做的事情很簡單:
①設置環境變量。
②建立etc連接。
③新建目錄,備用。
④掛載/tmp為內存文件系統tmpfs
⑤啟動recovery(/sbin/recovery)服務。
⑥啟動adbd服務(用于調試)。
這里最重要的就是當然就recovery服務了。在Recovery服務中將要完成我們的升級工作。
3.3 Recovery服務流程細節
從/bootable/recovery/recovery.c的代碼注釋中我們可以看到Recovery的服務內容主要有三類:
① FACTORY RESET,恢復出廠設置。
②OTA INSTALL,即我們的update.zip包升級。
③ENCRYPTED FILE SYSTEMENABLE/DISABLE,使能/關閉加密文件系統。
具體的每一類服務的大概工作流程,注釋中都有,下文中會詳細說下OTA INSTALL的工作流程。這三類服務的大概的流程都是通用的,只是不同操作體現與不同的操作細節。下面我們看Recovery服務的通用流程。
本文中會以OTA INSTALL的流程為例具體分析,相關函數的調用過程如下圖所示。我們順著流程圖分析,從recovery.c的main函數開始:
1. ui_init():Recovery服務使用了一個基于framebuffer的簡單ui(miniui)系統。這個函數對其進行了簡單的初始化。在Recovery服務的過程中主要用于顯示一個背景圖片(正在安裝或安裝失敗)和一個進度條(用于顯示進度)。另外還啟動了兩個線程,一個用于處理進度條的顯示(progress_thread),另一個用于響應用戶的按鍵(input_thread)。
2. get_arg():這個函數主要做了上圖中get_arg()往右往下直到parsearg/v的工作。我們對照著流程一個一個看。
①get_bootloader_message():主要工作是根據分區的文件格式類型(mtd或emmc)從MISC分區中讀取BCB數據塊到一個臨時的變量中。
②然后開始判斷Recovery服務是否有帶命令行的參數(/sbin/recovery,根據現有的邏輯是沒有的),若沒有就從BCB中讀取recovery域。如果讀取失敗則從/cache/recovery/command中讀取然后。這樣這個BCB的臨時變量中的recovery域就被更新了。在將這個BCB的臨時變量寫回真實的BCB之前,又更新的這個BCB臨時變量的command域為“boot-recovery”。這樣做的目的是如果在升級失敗(比如升級還未結束就斷電了)時,系統在重啟之后還會進入Recovery模式,直到升級完成。
③在這個BCB臨時變量的各個域都更新完成后使用set_bootloader_message()寫回到真正的BCB塊中。
這個過程可以用一個簡單的圖來概括,這樣更清晰:
3. parserargc/argv:解析我們獲得參數。注冊所解析的命令(register_update_command),在下面的操作中會根據這一步解析的值進行一步步的判斷,然后進行相應的操作。
4. if(update_package):判斷update_package是否有值,若有就表示需要升級更新包,此時就會調用install_package()(即圖中紅色的第二個階段)。在這一步中將要完成安裝實際的升級包。這是最為復雜,也是升級update.zip包最為核心的部分。我們在下一節詳細分析這一過程。為從宏觀上理解Recovery服務的框架,我們將這一步先略過,假設已經安裝完成了。我們接著往下走,看安裝完成后Recovery怎樣一步步結束服務,并重啟到新的主系統的。
5. if(wipe_data/wipe_cache):這一步判斷實際是兩步,在源碼中是先判斷是否擦除data分區(用戶數據部分)的,然后再判斷是否擦除cache分區。值得注意的是在擦除data分區的時候必須連帶擦除cache分區。在只擦除cache分區的情形下可以不擦除data分區。
6. maybe_install_firmware_update():如果升級包中包含/radio/hbootfirmware的更新,則會調用這個函數。查看源碼發現,在注釋中(OTA INSTALL)有這一個流程。但是main函數中并沒有顯示調用這個函數。目前尚未發現到底是在什么地方處理。但是其流程還是向上面的圖示一樣。即,① 先向BCB中寫入“boot-recovery”和“—wipe_cache”之后將cache分區格式化,然后將firmwareimage 寫入原始的cache分區中。②將命令“update-radio/hboot”和“—wipe_cache”寫入BCB中,然后開始重新安裝firmware并刷新firmware。③之后又會進入圖示中的末尾,即finish_recovery()。
7. prompt_and_wait():這個函數是在一個判斷中被調用的。其意義是如果安裝失敗(update.zip包錯誤或驗證簽名失敗),則等待用戶的輸入處理(如通過組合鍵reboot等)。
8. finish_recovery():這是Recovery關閉并進入MainSystem的必經之路。其大體流程如下:
①將intent(字符串)的內容作為參數傳進finish_recovery中。如果有intent需要告知Main System,則將其寫入/cache/recovery/intent中。這個intent的作用尚不知有何用。
②將內存文件系統中的Recovery服務的日志(/tmp/recovery.log)拷貝到cache(/cache/recovery/log)分區中,以便告知重啟后的Main System發生過什么。
③擦除MISC分區中的BCB數據塊的內容,以便系統重啟后不在進入Recovery模式而是進入更新后的主系統。
④刪除/cache/recovery/command文件。這一步也是很重要的,因為重啟后Bootloader會自動檢索這個文件,如果未刪除的話又會進入Recovery模式。原理在上面已經講的很清楚了。
9. reboot():這是一個系統調用。在這一步Recovery完成其服務重啟并進入Main System。這次重啟和在主系統中重啟進入Recovery模式調用的函數是一樣的,但是其方向是不一樣的。所以參數也就不一樣。查看源碼發現,其重啟模式是RB_AUTOBOOT。這是一個系統的宏。
至此,我們對Recovery服務的整個流程框架已有了大概的認識。下面就是升級update.zip包時特有的也是Recovery服務中關于安裝升級包最核心的第二個階段。即我們圖例中的紅色2的那個分支。
3.4 OTA升級過程分析(install_package)
3.4.1 OTA升級包安裝過程
安裝升級包所調用的函數為install_package(),源碼位于/bootable/recovery/install.cpp。該函數調用really_install_package(path,wipe_cache),該函數的流程為:
根據源代碼和上面的流程圖總結有如下步驟:
①ensure_path_mount():先判斷所傳的update.zip包路徑所在的分區是否已經掛載。如果沒有則先掛載。
②load_keys():加載公鑰源文件,路徑位于/res/keys。這個文件在Recovery鏡像的根文件系統中。
③verify_file():對升級包update.zip包進行簽名驗證。
④mzOpenZipArchive():打開升級包,并將相關的信息拷貝到一個臨時的ZipArchinve變量中。這一步并未對我們的update.zip包解壓。
⑤try_update_binary():在這個函數中才是對我們的update.zip升級的地方。這個函數一開始先根據我們上一步獲得的zip包信息,以及升級包的絕對路徑將update_binary文件拷貝到內存文件系統的/tmp/update_binary中。以便后面使用。
⑥pipe():創建管道,用于下面的子進程和父進程之間的通信。
⑦fork():創建子進程。其中的子進程主要負責執行binary(execv(binary,args),即執行我們的安裝命令腳本),父進程負責接受子進程發送的命令去更新ui顯示(顯示當前的進度)。子父進程間通信依靠管道。
⑧其中,在創建子進程后,父進程有兩個作用。一是通過管道接受子進程發送的命令來更新UI顯示。二是等待子進程退出并返回INSTALLSUCCESS。其中子進程在解析執行安裝腳本的同時所發送的命令有以下幾種:
progress <frac><secs>:根據第二個參數secs(秒)來設置進度條。
set_progress <frac>:直接設置進度條,frac取值在0.0到0.1之間。
firmware<”hboot”|”radio”><filename>:升級firmware時使用,在API V3中不再使用。
ui_print<string>:在屏幕上顯示字符串,即打印更新過程。
execv(binary,args)的作用就是去執行binary程序,這個程序的實質就是去解析update.zip包中的updater-script腳本中的命令并執行。由此,Recovery服務就進入了實際安裝update.zip包的過程。
上述的子進程所執行的程序binary實際上就是update.zip包中的update-binary。實際上Recovery服務在做這一部分工作的時候是先將包中update-binary拷貝到內存文件系統中的/tmp/update_binary,然后再執行的。升級包中update-binary的在升級包制作那一小節中已有說明。
通過install.c源碼來分析下update-binary程序的執行過程:
①函數參數以及版本的檢查:當前updater binary API所支持的版本號有1,2,3這三個。
②獲取管道并打開:在執行此程序的過程中向該管道寫入命令,用于通知其父進程根據命令去更新UI顯示。
③讀取updater-script腳本:從update.zip包中將updater-script腳本讀到一塊動態內存中,供后面執行。
④Configureedify’s functions:注冊腳本中的語句處理函數,即識別腳本中命令的函數。主要有以下幾類RegisterBuiltins():注冊程序中控制流程的語句,如ifelse、assert、abort、stdout等。RegisterInstallFunctions():實際安裝過程中安裝所需的功能函數,比如mount、format、set_progress、set_perm等等。RegisterDeviceExtensions():與設備相關的額外添加項,在源碼中并沒有任何實現。FinishRegistration():結束注冊。
⑤Parsethescript:調用yy*庫函數解析腳本,并將解析后的內容存放到一個Expr類型的python類中。主要函數是yy_scan_string()和yyparse()。
⑥執行腳本:核心函數是Evaluate(),它會調用其他的callback函數,而這些callback函數又會去調用Evaluate去解析不同的腳本片段,從而實現一個簡單的腳本解釋器。
⑦錯誤信息提示:最后就是根據Evaluate()執行后的返回值,給出一些打印信息。這一執行過程非常簡單,最主要的函數就是Evaluate。它負責最終執行解析的腳本命令。而安裝過程中的命令就是updater-script。
3.4.2update-script腳本語法簡介
常用修改權限的命令:
Set_perm 0 0 0600 ×××(只有所有者有讀和寫的權限)
Set_perm 0 0 0644 ×××(所有者有讀和寫的權限,組用戶只有讀的權限)
Set_perm 0 0 0700 ×××(只有所有者有讀和寫以及執行的權限)
Set_perm 0 0 0666 ×××(每個人都有讀和寫的權限)
Set_perm 0 0 0777 ×××(每個人都有讀和寫以及執行的權限)
1.copy_dir
語法:copy_dir <src-dir><dst-dir> [<times**p>]
<src-dir>表示原文件夾,<dst-dir>表示目的文件夾,[<times**p>]表示時間戳
作 用:將<src-dir>文件夾中的內容復制到<dst-dir>文件夾中。<dst-dir>文件夾中的原始內容 將會保存不變,除非<src-dir>文件夾中有相同的內容,這樣<dst-dir>中的內容將被覆蓋
舉例:copy_dir PACKAGE:system SYSTEM:(將升級包中的system文件夾復制到手機中)
2.format
語法:format <root>
<root>表示要格式化的分區
作用:格式化一個分區
舉例:format SYSTEM:(將手機/system分區完全格式化)
注意:格式化之后的數據是不可以恢復的
3.delete
語法:delete <file1> [... <fileN>]
<file1> [... <fileN>]表示要格式化的文件,可以是多個文件用空格隔開
作用:刪除文件1,2到n
舉例:delete SYSTEM:app/Calculator.apk(刪除手機systen文件夾中app中的Calculator.apk文件)
4.delete_recursive
語法:delete_recursive <file-or-dir1> [... <file-or-dirN>]
<file-or-dir1> [... <file-or-dirN>]表示要刪除的文件或文件夾,可以使多個,中間用空格隔開
作用:刪除文件或者目錄,刪除目錄時會將目錄中的所有內容全部刪除
舉例:delete_recursive DATA:dalvik-cache(刪除/data/dalvik-cache文件夾下的所有內容)
5.run_program
語法:run_program <program-file> [<args> ...]
<program-file>表示要運行的程序,[<args> ...]表示運行程序所加的參數
作用:運行終端程序
舉例:run_program PACKAGE:install_busybox.sh(執行升級包中的install_busybox.sh腳本)
6.set_perm
語法:set_perm <uid> <gid> <mode> <path> [...<pathN>]
<uid>表示用戶名稱,<gid>表示用戶組名稱,<mode>,表示權限模式,<path>[... <pathN>]表示文件路徑,可以使多個,用空格隔開
作用:設置單個文件或目錄的所有者和權限,像linux中的chmod、chown或chgrp命令一樣,只是集中在了一個命令當中
舉 例:set_perm 0 2000 0550 SYSTEM:etc/init.goldfish.sh(設置手機system中的etc/init.goldfish.sh的用戶為root,用戶組為shell,所有者以及所屬用戶組成員可以進行讀取和執行操作,其他用戶無操作權限)
7.set_perm_recursive
語法:set_perm_recursive <uid> <gid> <dir-mode><file-mode> <path> [... <pathN>]
<uid> 表示用戶,<gid>表示用戶組,<dir-mode>表示文件夾的權限,<file-mode>表示文件的權限,<path> [... <pathN>]表示文件夾的路徑,可以多個,用空格分開
作用:設置文件夾及文件夾中的文件的所有者和用戶組
舉 例:set_perm_recursive 0 0 0755 0644 SYSTEM:app(設置手機system/app文件夾及其中文件的用戶為root,用戶組為root,app文件夾權限為所有者可以進行讀、寫、執行操作,其他用戶可以進行讀取和執行操作,其中的文件的權限為所有者可以進行讀寫操作,其他用戶可以進行讀取操作)
8.show_progress
語法:show_progress <fraction> <duration>
<表示一個小部分> <表示一個小部分的持續時間>
作用:為下面進行的程序操作顯示進度條,進度條會根據<duration>進行前進,當操作時間是確定的時候會更快
舉例:show_progress 0.1 0(顯示進度條當操作完成后前進10%)
9.symlink
語法:symlink <link-target> <link-path>
<link-target>表示鏈接到的目標,<link-path>表示快捷方式的路徑
作 用:相當于linux中的ln命令,將<link-target>在<link-path>處創建一個軟鏈 接,<link-target>的格式應為絕對路徑(或許相對路徑也可以),<link-path>為“根目錄:路徑”的形式
舉例:symlink /system/bin/su SYSTEM:xbin/su(在手機中system中的xbin中建立一個/system/bin/su的快捷方式)
10.assert
語法:assert <boolexpr>
作用:此命令用來判斷表達式boolexpr的正確與否,當表達式錯誤時程序終止執行※此作用有待驗證
11.package_extract_file/dir語法:package_extract_file(file/dir,file/dir)
作用:提取包中文件/路徑
舉例:package_extract_dir("system", "/system");
package_extract_file("system/bin/modelid_cfg.sh","/tmp/modelid_cfg.sh");
12.write_radio_image
語法:write_radio_image<src-image>
作用:將基帶部分的鏡像寫入手機,<src-image>表示鏡像文件
舉例:write_radio_imagePACKAGE:radio.img
13.write_hboot_image
語法:write_hboot_image<src-image>
作用:將系統bootloader鏡像寫入手機,<src-image>表示鏡像位置,此命令在直到在所有的程序安裝結束之后才會起作用
舉例:write_hboot_imagePACKAGE:hboot.img
14.write_raw_image語法:write_raw_image<src-image> <dest-root>
作用:將boot.img寫入手機,里面包含了內核和ram盤
舉例:write_raw_image PACKAGE:boot.img BOOT:
15.函數名稱: apply_patch
函數語法: apply_patch(srcfile, tgtfile, tgtsha1,tgtsize, sha1_1, patch_1, ..., sha1_x, patch1_x)
參數詳解:srcfile-------------------字符串,要打補丁的源文件(要讀入的文件)
Tgtfile-------------------字符串,補丁文件要寫入的目標文件
tgtsha1-----------------字符串,寫入補丁文件的目標文件的sha1哈希值
sha1_x------------------字符串,要寫入目標文件的補丁數據的sha1哈希值patch1_x----------------字符串,實際上應用到目標文件的補丁
作用解釋: 這個函數是用來打補丁到文件。
16.函數名稱: apply_patch_check
函數語法: apply_patch_check(file, sha1_1, ..., sha1_x)
參數詳解:file----------------------字符串,要檢查的文件
sha1_x------------------要檢查的哈希值
作用解釋: 檢查文件是否已經被打補丁,或者能不能被打補丁。需要檢查“applypatch_check ”函數調用的源代碼。
17.函數名稱: apply_patch_space
函數語法: apply_patch_space(bytes)
參數詳解:bytes-------------------檢查的字節的數字
作用解釋: 檢查緩存來確定是否有足夠的空間來寫入補丁文件并返回一些數據。
18.函數名稱: read_file
函數語法: read_file(filename)
參數詳解: filename----------------字符串,要讀取內容的文件名
作用解釋: 這個函數返回文件的內容
19.函數名稱: sha1_check
函數語法: sha1_check(data) 或 sha1_check(data, sha1_hex, ..., sha1_hexN)
參數詳解:data------要計算sha1哈希值的文件的內容-必須是只讀文件格式;
sha1_hexN------文件數據要匹配的特定的十六進制sha1_hex哈希值字符串
作用解釋: 如果只指定data參數,這個函數返回data參數的十六進制sha1_hex哈希值字符串。其他參數用來確認你檢查的文件是不是列表中的哈希值的一個,它返回匹配的哈希值,或者在沒有匹配任何哈希值時返回空。
3.4.3update-script腳本執行流程
OTA升級包中有兩個非常重要的腳本,分別是:
META-INF/com/google/android/updater-script
recovery/etc/install-recovery.sh
升級來源文件有如下三個:
boot.img
/system
recovery/recovery-from-boot.p
另一個很重要的文件是/etc/recovery.fstab,內容由EMMC分區方案確定。
-------- /etc/recovery.fstab -----------
/boot emmc /dev/block/mmcblk0p1
/sdcard vfat /dev/block/mmcblk0p4
/recovery emmc /dev/block/mmcblk0p2
/system ext4 /dev/block/mmcblk0p5
/cache ext4 /dev/block/mmcblk0p6
/data ext4 /dev/block/mmcblk0p7
/misc emmc /dev/block/mmcblk0p9
--------------------------------------------
otgpackage編譯腳本會根據這個文件填充updater-script,后面可以看到。這個文件存在于recovery分區中,進入recovery模式后,可以訪問到它。進入recovery模式的方式多種多樣,但每種方式都需要bootloader的配合。進入recovery模式后會對升級包進行驗證,過程不表,失敗退出。進入recovery流程后,主要關心updater-script的工作。
首先是updater-script,代碼中可以很容易分析出他的工作流程,如下:
---------updater-script ----------------
.... //省略若干
format("ext4","EMMC", "/dev/block/mmcblk0p5", "0");
mount("ext4","EMMC", "/dev/block/mmcblk0p5", "/system"); //掛載system分區。這里有"/dev/block/mmcblk0p5"和"/system"的對應關系,來源于前文提到的recovery.fstab。
package_extract_dir("recovery","/system");//將zip包中的recovery目錄解壓到系統/system目錄,將來升級recovery分區時使用(install-recovery.sh,recovery-from-boot.p)
package_extract_dir("system","/system"); //將zip包中的system目錄解壓到系統/system目錄,完成system分區的升級
...... //省略若干
symlink("mksh","/system/bin/sh");
symlink("toolbox","/system/bin/cat", ....); //創建軟鏈接,省略若干
retouch_binaries("/system/lib/libbluedroid.so",.....); //各種動態庫,省略若干
set_perm_recursive(0,0, 0755, 0644, "/system");
...... //修改權限,省略若干
show_progress(0.200000,0); //顯示升級進度
...... //修改權限,省略若干
package_extract_file("boot.img","/dev/block/mmcblk0p1"); //將boot.img解壓到相應block設備,完成boot分區的升級。boot分區包含了kernel + ramdisk
show_progress(0.100000,0);
unmount("/system"); //卸載system分區
---------------------------------------------
system分區和boot升級完成,接下來重啟,進入正常系統。正常啟動的系統init.rc中定義了一個用于燒寫recovery分區的服務,也就是執行install-recovery.sh,每次啟動都要執行一次。
----- /init.rc------
...
service flash_recovery /system/etc/install-recovery.sh
class main
oneshot
...
--------------------
install-recovery.sh是recovery模式中updater-script解壓出來的,內容如下:
-------/system/etc/install-recovery.sh ----
#!/system/bin/sh
log -t recovery "Before sha1....Simba...."
if ! applypatch-c EMMC:/dev/block/mmcblk0p2:4642816:c125924fef5a1351c9041ac9e1d6fd1f9738ff77;then
log -t recovery "Installing new recoveryimage__From Simba..."
applypatchEMMC:/dev/block/mmcblk0p1:3870720:aee24fadd281e9e2bd4883ee9962a86fc345dcabEMMC:/dev/block/mmcblk0p2 c125924fef5a1351c9041ac9e1d6fd1f9738ff77 4642816aee24fadd281e9e2bd4883ee9962a86fc345dcab:/system/recovery-from-boot.p
else
log -t recovery "Recovery image alreadyinstalled__From Simba..."
fi
-------------------------------------------
執行 make otapackage命令時,編譯腳本比較boot.img和recovery.img得出patch文件recovery-from-boot.p。recovery-from-boot.p也是在recovery模式中updater-script解壓到system目錄的。install-recovery.sh腳本就是使用這個patch加上boot分區,更新recovery分區。應用patch前,install-recovery.sh會計算當前recovery分區的sha1。若計算結果與腳本中記錄的相同(c125924fef5a1351c9041ac9e1d6fd1f9738ff77),說明已經更新過了,不再操作。這樣就完成了/system目錄,boot分區(kernel + ramdisk),recovery分區(kernel +ramdisk-recovery)的升級。
以上是標準的Android升級流程,我們自己添加的分區可以參考以上幾種方式實現。自定義的分區采用何種升級方式需要細細考量,關系到升級包的內容結構和簽名過程。
4、總結
本文檔參考了CSDN上和參考文獻中關于Recovery模式及OTA升級的博客和文檔,按照自己的理解思路重新梳理,可能會有很多理解的偏差,歡迎大家批評指正。基本的思路就是從OTA包的制作到下載后點擊升級如何進入Recovery模式以及在Recovery模式下是怎樣實現OTA包的安裝升級的。
5、OTA升級常見問題
問題現象:在進行 OTA 升級測試時,下載成功了升級包,在點擊立即更新后,手機一直處于提示“正在更新中”,沒能重啟進行升級。
問題分析:經過分析發現,因為OTA 應用不具備系統權限。導致其無法在目錄/cache/recovery 中創建command 文件并在該文件中寫入命令,從而導致 OTA 應用無法通過這種預定的方式重啟機器并進入recovery 模式,無法實現正常 OTA 升級。
解決方案:通過在 init.rc 文件中增加 mkdircache/recovery 命令,使該目錄默認具備寫權限,確保 OTA應用可以正常進行系統升級。
問題現象: 下載完升級包后,進入 recovery 模式進行升級時, 系統提示升級失敗,手機無法成功升級。
問題分析:通過分析日志,升級失敗系在對系統文件進行校驗時無法通過校驗。跟蹤編譯流程,發現生成的版本文件和用于生成 OTA 升級包的目錄文件不一致。根本原因是在生成版本文件后的編譯目標文件的過程中,許多模塊重新進行了編譯。從而導致版本文件和目標文件中存在有著差異的文件。從而導致升級因校驗失敗而無法正常升級。
解決方案:針對這種情況,在編譯完目標文件后重新打包生成版本文件,就可以解決兩者不一致的問題。
問題現象:差分包簽名校驗失敗,報錯提示:signature verification failed,Installation aborted。
解決方案:(有三種情況):
1. 差分包簽名和版本中簽名不一致。開發流版本使用 google 原生簽名,故差分包也必須使用
google 原生簽名。集成流和發布流版本使用公司簽名,故差分包也必須使用公司簽名。
2.差分包導入到sd卡時,有時會出現導入失敗,原因是從命令提示符中看到已經導入成功,實際上差分包的部分數據還在緩存中,沒有完全導入SD卡,所以會出現SD卡的數據不完整而校驗失敗,解決方法:將升級包(update.zip包)導入SD卡后,需要執行adb shell sync。
3. 在制作差分包過程中,差分包的壓縮文件損壞,CRC 校驗失敗。驗證方法:將差分包解壓,此時會提示解壓失敗,正常的差分包應該是能正常解壓的。
問題現象:升級過程中失敗,報錯提示:assert failed: getprop("ro.product.device")
問題分析:由于升級過程中需要校驗ro.product.device,若新版本中修改了該屬性值,則使用前向版本升級時,由于 ro.product.device 不一致,則將會導致升級認為機器手機類型不同而升級失敗。
解決方案:將assert(getprop("ro.product.device")的腳本語句屏蔽。
問題現象:版本號不對應,報錯提示:assertfailed: file_getprop("/system/build.prop", "ro.build.fingerprint")
問題分析:由于差分包是基于前后兩個版本進行差分后升級,若使用的源版本不對應,便會導致差分包不匹配而升級失敗。
解決方案: 進入系統設置,查看手機版本是否與差分包的ro.build.fingerprint 對應,重新使用正確的版本進行升級。
問題現象:版本的文件被手動修改,報錯提示:script aborted: assert failed: apply_patch_check
問題分析: 可能開發人員或中試人員對源版本獲取了root 權限,對手機中的文件進行了修改,而升級中剛好會升級這些文件,便會出現升級被改動文件失敗的情況。
解決方案: 獲取手機版本中 system 目錄所有文件和用于制作差分包的源版本包中的文件進行比對,找出該文件為何被修改的原因。如果是版本集成問題,需要重新編譯版本。
問題現象:cache 分區空間不足,報錯提示:scriptaborted: assert failed: apply_patch_space
問題分析:由于差分包升級過程中是需要將需差分包的文件放置在cache分區下,若需差分的最大文件容量大于 cache 分區的最大容量,則會導致無法放置而升級失敗。
解決方案:查看差分包中updater-script 腳本中的以下語句:assert(apply_patch_space(number)),通過計算 cache 分區容量<number>,則是原因版本中某個被修改的文件很大,該大文件一般是版本中的 iso影像,因此在項目中若產品量產后,是不允許修改 iso 影像的。
問題現象:內核升級失敗,報錯提示:scriptaborted: assert failed: apply_patch("EMMC:…
問題分析:多種情況下都可能導致內核升級失敗:
1. 由于版本中若修改了內核的起始地址,將會導致制作出來的差分包在校驗內核時 sha 校驗失敗。
2. 在制作差分包時,若需要升級modem 文件,其正確順序為先做 AP 側的差分包和整包,然后把要升級的 MP 側文件放進去,再簽名。若順序反了:如先放置 MP 側文件,再制作 AP 側的差分包和整包,這種也會導致升級內核失敗。
解決方案:對于第一種情況,則對內核不能使用差分的形式,而要使用整體的形式進行升級,即將對內核的 apply_patch 語句去除,而使用以下方法。emmc 文件系統:package_extract_file("boot.img","/dev/block/mmcblk0p8")或 MTD 文件系統:assert(package_extract_file("boot.img","/tmp/boot.img"),write_raw_image("/tmp/boot.img","boot"),delete("/tmp/boot.img"));
問題現象:升級 boot.img 時,拔電池重啟后,會一直進入 recovery 模式,并且不能正常升級。
問題分析:由于差分包升級過程中是需要校驗的,恢復到一半的時候斷電,會導致差分包與源文件對不上號而導致升級失敗。
解決方案:升級中提示用戶不能拔電池,或者使用整包升級而不是差分升級包。
OTA制作及升級過程筆記
總結
以上是生活随笔為你收集整理的OTA制作及升级过程笔记【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 「重点」茅以升的故事写了茅以升的几件事
- 下一篇: boost是什么意思(Boost 到底有