OTA制作及升级过程笔记

Posted 请给我倒杯茶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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/system bin/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是在编译时从alps\mediatek\custom\$PROJECT\security\recovery下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: [email protected]"

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

           -n \

           -p$(HOST_OUT) \

           -k$(KEY_CERT_PAIR) \

          $(ota_extra_flag) \

          $(BUILT_TARGET_FILES_PACKAGE) [email protected]

.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. # A zip of the directories that map to the target filesystem.  

3. # This zip can be used to create an OTA package or filesystem image  

4. # as a post-build step.  

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 := $(call intermediates-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): Directory to copy  

19.# $(2): Location to copy it to  

20.# The "ls -A" is to prevent "acp s/* d" from failing if s is empty.  

21.define package_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.    $(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \  

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

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

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

33.    $(call intermediates-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.# default to common dir for device vendor  

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.# Depending on the various images guarantees that the underlying  

46.# directories are up-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 "Package target files: [email protected]"  

67.    $(hide) rm -rf [email protected] $(zip_root)  

68.    $(hide) mkdir -p $(dir [email protected]) $(zip_root)  

69.     @# Components of the recovery image  

70.    $(hide) mkdir -p $(zip_root)/RECOVERY  

71.    $(hide) $(call package_files-copy-root, \  

72.        $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)  

73.ifdef INSTALLED_KERNEL_TARGET  

74.    $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel  

75.    $(hide) $(ACP) $(recovery_ramdisk) $(zip_root)/RECOVERY/ramdisk  

76.endif  

77.ifdef INSTALLED_2NDBOOTLOADER_TARGET  

78.    $(hide) $(ACP) \  

79.        $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second  

80.endif  

81.ifdef BOARD_KERNEL_CMDLINE  

82.    $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/RECOVERY/cmdline  

83.endif  

84.ifdef BOARD_KERNEL_BASE  

85.    $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/RECOVERY/base  

86.endif  

87.     @# Components of the factory image  

88.    $(hide) mkdir -p $(zip_root)/FACTORY  

89.    $(hide) $(call package_files-copy-root, \  

90.        $(TARGET_FACTORY_ROOT_OUT),$(zip_root)/FACTORY/RAMDISK)  

91.ifdef INSTALLED_KERNEL_TARGET  

92.    $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/FACTORY/kernel  

93.endif  

94.ifdef BOARD_KERNEL_PAGESIZE  

95.    $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/RECOVERY/pagesize  

96.endif  

97.ifdef INSTALLED_2NDBOOTLOADER_TARGET  

98.    $(hide) $(ACP) \  

99.        $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/FACTORY/second  

100.endif  

101.ifdef BOARD_KERNEL_CMDLINE  

102.    $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/FACTORY/cmdline  

103.endif  

104.ifdef BOARD_KERNEL_BASE  

105.    $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/FACTORY/base  

106.endif  

107.    @# Components of the boot image  

108.    $(hide) mkdir -p $(zip_root)/BOOT  

109.    $(hide) $(call package_files-copy-root, \  

110.        $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)  

111.ifdef INSTALLED_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.ifdef INSTALLED_2NDBOOTLOADER_TARGET  

116.    $(hide) $(ACP) \  

117.        $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second  

118.endif  

119.ifdef BOARD_KERNEL_CMDLINE  

120.    $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline  

121.endif  

122.ifdef BOARD_KERNEL_BASE  

123.    $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/BOOT/base  

124.endif  

125.ifdef BOARD_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.#[eton begin]: added by LiuDekuan for u-boot update  

134.    $(hide) $(ACP) $(PRODUCT_OUT)/uboot_eyang77_ics2.bin $(zip_root)/uboot.bin  

135.#[eton end]  

136.  

137.    $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),\  

138.                mkdir -p $(zip_root)/RADIO; \  

139.                $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));)  

140.    @# Contents of the system image  

141.    $(hide) $(call package_files-copy-root, \  

142.        $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)  

143.    @# Contents of the data image  

144.    $(hide) $(call package_files-copy-root, \  

145.        $(TARGET_OUT_DATA),$(zip_root)/DATA)  

146.    @# Extra contents of the OTA package  

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.    @# Security information of the OTA package  

151.    @echo "[SEC OTA] Adding Security information to OTA package"  

152.    @echo "[SEC OTA] 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.    @# Files that do not end up in any images, but are necessary to  

155.    @# build them.  

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.ifdef BOARD_FLASH_BLOCK_SIZE  

161.    $(hide) echo "blocksize=$(BOARD_FLASH_BLOCK_SIZE)" >> $(zip_root)/META/misc_info.txt  

162.endif  

163.ifdef BOARD_BOOTIMAGE_PARTITION_SIZE  

164.    $(hide) echo "boot_size=$(BOARD_BOOTIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  

165.endif  

166.ifdef BOARD_RECOVERYIMAGE_PARTITION_SIZE  

167.    $(hide) echo "recovery_size=$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  

168.endif  

169.ifdef BOARD_SYSTEMIMAGE_PARTITION_SIZE  

170.    $(hide) echo "system_size=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  

171.endif  

172.ifdef BOARD_SECROIMAGE_PARTITION_SIZE  

173.    $(hide) echo "secro_size=$(BOARD_SECROIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  

174.endif  

175.ifdef BOARD_CACHEIMAGE_PARTITION_SIZE  

176.    $(hide) echo "cache_size=$(BOARD_CACHEIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt  

177.endif  

178.ifdef BOARD_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.ifdef mkyaffs2_extra_flags  

183.    $(hide) echo "mkyaffs2_extra_flags=$(mkyaffs2_extra_flags)" >> $(zip_root)/META/misc_info.txt  

184.endif  

185.ifdef INTERNAL_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.ifdef PRODUCT_EXTRA_RECOVERY_KEYS  

190.    $(hide) echo "extra_recovery_keys=$(PRODUCT_EXTRA_RECOVERY_KEYS)" >> $(zip_root)/META/misc_info.txt  

191.endif  

192.     @# Zip everything up, preserving symlinks  

193.    $(hide) (cd $(zip_root) && zip -qry ../$(notdir [email protected]) .)  

194.    @# Run fs_config on all the system, boot ramdisk, and recovery ramdisk files in the zip, and save the output  

195.    $(hide) zipinfo -1 [email protected] | awk ‘BEGIN { FS="SYSTEM/" } /^SYSTEM\// {print "system/" $$2}‘ | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/filesystem_config.txt  

196.    $(hide) zipinfo -1 [email protected] | 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 [email protected] | 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 [email protected]) META/*filesystem_config.txt)  

  

199.target-files-package: $(BUILT_TARGET_FILES_PACKAGE)  

  

200.ifneq ($(TARGET_PRODUCT),sdk)  

201.ifeq ($(filter generic%,$(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.     if OPTIONS.aslr_mode:  

8.       script.RetouchBinaries(retouch_files)  

9.     else:  

10.      script.UndoRetouchBinaries(retouch_files)  

代码2 WriteFullOTAPackage代码片段

其中的script为edify_generator对象,其FormatPartition、UnpackPackageDir等方法分别是向脚本文件update-script中写入格式化分区、解压包等指令

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

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

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

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

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

6.    

7.      self.UnmountAll()  

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

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

10.       if input_path is None:  

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)  

代码段3 edify_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.   const char** args = (const char**)malloc(sizeof(char*) * 5);  

2.   args[0] = binary;  

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

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

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

6.   args[2] = temp;  

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

8.   args[4] = NULL;  

9.     

10.  pid_t pid = fork();  

11.  if (pid == 0) {  

12.      close(pipefd[0]);  

13.      execv(binary, (charconst*)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.       $(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \  

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

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

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

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

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,为后面内核的加载作好准备。在一些系统启动必要的初始完成之后,系统会通过检测三个条件来判断要进入何种工作模式,流程如图。这一部分代码的源文件在\bootable\bootloader\lk\app\aboot\aboot.c 文件的aboot_init()函数中:

 

从源代码中可以看出,开机时按住HOME键或音量上键(不同手机对此会有修改),会进入Recovery模式;按着返回键或音量下键会进入fastboot模式。如果没有组合键(代码中成为magic key)按下,则会检测SMEM中reboot_mode变量的值,代码如下:

 

reboot_mode可取的值为RECOVERY_MODE和FASTBOOT_MODE的宏定义,分别为:

 

reboot_mode的取值由check_reboot_mode()函数返回,接下来我们看该函数在\bootable\bootloader\lk\target\msm8660_surf\init.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\n

<recovery command>\n

<recovery command>

该文件存储的是一个字符串,必须以“recovery\n”开头,否则这个字段的所有内容域会被忽略。“recovery\n”之后的部分,是/cache/recovery/command 文件支持的命令。可以将其理解为 Recovery 操作过程中对命令操作的备份。 Recovery会先读取 BCB,然后读取/cache/recovery/command,然后将二者重新写回 BCB,这样在进入 Mainsystem 之前,确保操作被执行。在操作之后进入 Main system 之前,Recovery 又会清空 BCB 的 command 域和 recovery 域,这样确保重启后不再进入 Recovery 模式。

解释完BCB字段的内容,我们再回过头来,调用recovery_init()的代码如下:

 

该函数的定义在bootloader\lk\app\aboot\recovery.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.2 Recovery模式的三个组成部分

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(new File(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)\alps\system\core\libcutils\android_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.2 update-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.3 update-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制作及升级过程笔记的主要内容,如果未能解决你的问题,请参考以下文章

android OTA升级包制作

ESP32学习笔记(25)——OTA(空中升级)接口使用(简化API)

物联网设备OTA软件升级之:升级包下载过程之旅

物联网设备OTA软件升级之:升级包下载过程之旅

ESP32学习笔记(24)——OTA(空中升级)接口使用(原生API)

CSR8670学习笔记:OTA升级固件