日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

OTA制作及升级过程笔记【转】

發布時間:2023/12/25 综合教程 35 生活家
生活随笔 收集整理的這篇文章主要介紹了 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制作及升级过程笔记【转】的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。