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