多软件网站下载安装,福州网站设计费用,wordpress建站,免费自动建站1、概述
1.1 文档概要
前段时间学习了AndroidRecovery模式及OTA升级过程#xff0c;为加深理解和防止以后遗忘#xff0c;所以写这篇文档进行一个总结和梳理#xff0c;以便日后查阅回顾。文档主要包括两部分#xff0c;第一部分为OTA升级包的制作过程分析#xff0c;…1、概述
1.1 文档概要
前段时间学习了AndroidRecovery模式及OTA升级过程为加深理解和防止以后遗忘所以写这篇文档进行一个总结和梳理以便日后查阅回顾。文档主要包括两部分第一部分为OTA升级包的制作过程分析第二部分为Recovery模式下OTA升级包安装过程的分析其中包括Recovery模式分析及服务流程。
1.2 参考文献
《Recovery 开发指导》
《Android系统Recovery工作原理之使用update.zip升级过程分析》
《OTA本质与实现流程分析》
《Android系统启动过程分析》
2、OTA升级包制作工程
2.1 OTA升级简介
所谓OTAOver-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主要包括kernelramdisk包括应用会用到的一些库等等。可以将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。
我们用命令make otapackage制作生成的update.zip包是已签过名的如果自己做update.zip包时必须手动对其签名。具体的加密方法
$ java –jar -Xmx2048m -jar out/host/linux/framework/signapk.jar –w build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8 update.zip update_signed.zip
以上命令在update.zip包所在的路径下执行其中signapk.jar testkey.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.txtscatter.txt分散加载文件将可执行映像文件分散加载到不同的内存段文件内容指定不同内存段的起始地址。
type.txt是build升级包过程生成的里面的值1代表FullOTA0代表DiffOTAandroid的上层的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.mk218然后再build/tools/makeMtk.mk中的142以及 692会生成ota_scatter.txt。
2.3 OTA升级包制作工程分析
升级包有整包与差分包之分。顾名思义所谓整包即包含整个system分区中的数据文件而差分包则仅包含两个版本之间改动的部分。利用整包升级好比对电脑进行重作系统格式化系统分区并将新系统数据写入分区而利用差分包升级不会格式化system分区只是对其中部分存储段的内容进行重写。除升级包之外制作过程中还会涉及到另一种zip包代码中称之为8675-cota-target_files-xxx.zip我称之为差分资源包。首先阐述下整包的制作过程。
2.3.1 整包的制作
系统经过整编后执行make otapackage命令即可完成整包的制作而此命令可分为两个阶段进行。首先执行./build/core/Makefile中的代码
#-----------------------------------------------------------------
# OTA update packagename : $(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)echoPackage 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文件系统......)、解压缩、差分(bsdiffimgdiff)、签名(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: $
67. $(hide) rm -rf $ $(zip_root)
68. $(hide) mkdir -p $(dir $) $(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 $) .)
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 $ | awk BEGIN { FSSYSTEM/ } /^SYSTEM\// {print system/ $$2} | $(HOST_OUT_EXECUTABLES)/fs_config $(zip_root)/META/filesystem_config.txt
196. $(hide) zipinfo -1 $ | awk BEGIN { FSBOOT/RAMDISK/ } /^BOOT\/RAMDISK\// {print $$2} | $(HOST_OUT_EXECUTABLES)/fs_config $(zip_root)/META/boot_filesystem_config.txt
197. $(hide) zipinfo -1 $ | awk BEGIN { FSRECOVERY/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 ($(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-files zipfile目录结构
其中OTA目录值得关注因为在此目录下存在着一个至关重要的文件OTA/bin/updater后文会有详述。生成的差分资源包被传递给./build/tools/releasetools/
ota_from_target_files执行第二阶段的操作制作升级包。 图3./build/tools/releasetools目录下的文件
图3是./build/tools/releasetools目录下所包含的文件这组文件是Google提供的用来制作升级包的代码工具核心文件为ota_from_target_files和img_from_target_files。其中前者用来制作recovery模式下的升级包后者则用来制作fastboot下的升级包fastboot貌似是一种更底层的刷机操作不太懂。其他文件则是为此二者提供服务的比如common.py中包含有制作升级包所需操作的代码,各种工具类,参数处理/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-script3将上文提到的可执行文件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_pathNone):
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, perms0755) 代码段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, (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. $(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
如代码段5Makefile中定义了执行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()。这里解释一下BCBBootloaderControl BlockBCB 是 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都不一样。这就是系统没有进入图形界面而进入了类似文本界面并可以通过简单的组合键进行恢复的原因。与正常启动系统类似也是启动内核然后启动文件系统。在进入文件系统后会执行/initinit 的配置文件就是 /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.rebootAndUpdatenew 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类似也包含了标准的内核和根文件系统。其后就与正常的启动系统类似也是启动内核然后启动文件系统。在进入文件系统后会执行/initinit的配置文件就是/init.rc。这个配置文件来自bootable/recovery/etc/init.rc。查看这个文件我们可以看到它做的事情很简单 ①设置环境变量。 ②建立etc连接。 ③新建目录备用。 ④挂载/tmp为内存文件系统tmpfs ⑤启动recovery/sbin/recovery服务。 ⑥启动adbd服务(用于调试)。
当然init主程序还会装载属性配置文件 /default.prop, 它包含了很多系统属性设置比如ro.build.* 等等。这里最重要的就是当然就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的简单uiminiui系统。这个函数对其进行了简单的初始化。在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()创建子进程。其中的子进程主要负责执行binaryexecv(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)的作用就是调用META-INF/com/google/android路径下的可执行文件update-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所支持的版本号有123这三个。 ②获取管道并打开在执行此程序的过程中向该管道写入命令用于通知其父进程根据命令去更新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-dirdst-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]表示要格式化的文件可以是多个文件用空格隔开
作用删除文件12到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-modefile-mode path [... pathN]
uid 表示用户gid表示用户组dir-mode表示文件夹的权限file-mode表示文件的权限path [... pathN]表示文件夹的路径可以多个用空格分开
作用设置文件夹及文件夹中的文件的所有者和用户组
举 例set_perm_recursive 0 0 0755 0644 SYSTEM:app设置手机system/app文件夹及其中文件的用户为root用户组为rootapp文件夹权限为所有者可以进行读、写、执行操作其他用户可以进行读取和执行操作其中的文件的权限为所有者可以进行读写操作其他用户可以进行读取操作 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_imagesrc-image
作用将基带部分的镜像写入手机src-image表示镜像文件
举例write_radio_imagePACKAGE:radio.img 13.write_hboot_image
语法write_hboot_imagesrc-image
作用将系统bootloader镜像写入手机src-image表示镜像位置此命令在直到在所有的程序安装结束之后才会起作用
举例write_hboot_imagePACKAGE:hboot.img 14.write_raw_image语法write_raw_imagesrc-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.shrecovery-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命令时编译脚本(ota_from_target_files)比较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升级流程我们自己添加的分区可以参考以上几种方式实现。自定义的分区采用何种升级方式需要细细考量关系到升级包的内容结构和签名过程。
3.4.4 升级相关编译的命令
recovery编译make recoveryimage -j12 -生成recovery.img, 包含dtb kernel ramdisk.img
target包编译: make target-files-package -j12 -生成target包主要是由build/core/Makfile中定义的目标规则生成是生成ota升级包的中间状态包
ota包编译: make otapackage -j12 - 生成board-ota-xxxx.zip包, 主要是先生成target包然后把target包转换为ota包把target包转换为ota包主要实现是在build/tools/releasetools/ota_from_target_files完成。
target包转换为ota包: ./build/tools/releasetools/ota_from_target_files -v -p out/host/linux-x86 -k build/target/product/security/testkey board-target_files-xxxx.zip board-ota-xxxx.zip
ota包重新签名: java -Xmx2048m -jar out/host/linux-x86/framework/signapk.jar -w build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8 no_signed_ota.zip signed_ota.zip
target制作差分ota包: ./build/tools/releasetools/ota_from_target_files -v -i board-target_files-xxx1.zip board-target_files-xxx2.zip increase_update.zip
3.4.5 关于在已使能secure boot上OTA升级 在已使能secure boot 的板子上在make otapackage 后生成的升级包ota.zip后还需对其进行加密签名操作具体流程解压ota.zip- 使用与板子中efuse里一致的秘钥加密bootloader/ boot.img / recovery.img / dtb.img,等 -重新打包ota.zip- 使用ota签名工具signapk.jar重新签名。
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 failedInstallation 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)
问题分析由于差分包是基于前后两个版本进行差分后升级若使用的源版本不对应便会导致差分包不匹配而升级失败。在开发板上可使用getprop ro.product.device查看现在的是什么值
解决方案 进入系统设置查看手机版本是否与差分包的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的文章1 关于ota的文章2 Android OTA升级原理及流程分析一至八 OTA和Recovery系统升级流程介绍 OTA升级签名校验简析
本文基于此文修改