原文:http://blog.csdn.net/guyongqiangx/article/details/71516768,感謝作者的辛勤付出
Android從7.0開始引入新的OTA更新方式,
A/B System Updates
,這裡将其叫做
A/B
系統,涉及的内容較多,分多篇對
A/B
系統的各個方面進行分析。本文為第二篇,系統image的生成。
image
這個詞的含義很多,這裡指編譯後可以燒寫到裝置的檔案,如
boot.img
,
system.img
等,統稱為鏡像檔案吧。
我一直覺得将 image
翻譯成鏡像檔案怪怪的,如果有更貼切詞彙,請一定要告訴我啊,十分感謝。
本文基于
AOSP 7.1.1_r23 (NMF27D)
代碼進行分析。
1. A/B
系統和傳統方式下鏡像内容的比較
A/B
傳統OTA方式下:
- boot.img内有一個boot ramdisk,用于系統啟動時加載system.img;
- recovery.img内有一個recovery ramdisk,作為recovery系統運作的ramdisk;
- system.img隻包含android系統的應用程式和庫檔案;
A/B
系統下:
- system.img除了包含android系統的應用程式和庫檔案還,另外含有boot ramdisk,相當于傳統OTA下boot.img内的ramdisk存放到system.img内了;
- boot.img内包含的是recovery ramdisk,而不是boot ramdisk。Android系統啟動時不再加載boot.img内的ramdisk,而是通過device mapper機制選擇system.img内的ramdisk進行加載;
- 沒有recovery.img檔案
要想知道系統的各個分區到底有什麼東西,跟傳統OTA的鏡像檔案到底有什麼差別,需要閱讀Makefile,看看每個鏡像裡面到底打包了哪些檔案。
在看系統編譯打封包件生成鏡像之前,先看看跟
A/B
相關的到底有哪些變量,以及這些變量有什麼作用。
2. A/B
系統相關的Makefile變量
A/B
這些變量主要有三類:
-
系統必須定義的變量A/B
-
AB_OTA_UPDATER := true
-
AB_OTA_PARTITIONS := boot system vendor
-
BOARD_BUILD_SYSTEM_ROOT_IMAGE := true
-
TARGET_NO_RECOVERY := true
-
BOARD_USES_RECOVERY_AS_BOOT := true
-
PRODUCT_PACKAGES += update_engine update_verifier
-
-
系統可標明義的變量A/B
-
PRODUCT_PACKAGES_DEBUG += update_engine_client
-
-
系統不能定義的變量A/B
-
BOARD_RECOVERYIMAGE_PARTITION_SIZE
-
BOARD_CACHEIMAGE_PARTITION_SIZE
-
BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
-
以下詳細說明這些變量。
- 必須定義的變量
-
AB_OTA_UPDATER := true
系統的主要開關變量,設定後:A/B
-
系統内不再具有操作recovery
分區的功能,cache
;bootable\recovery\device.cpp
-
系統使用不同的方式來解析更新檔案,recovery
bootable\recovery\install.cpp
- 生成
系統相關的META檔案A/B
-
-
AB_OTA_PARTITIONS := boot system vendor
- 将
系統可更新的分區寫入檔案A/B
$(zip_root)/META/ab_partitions.txt
- 将
-
将boot ramdisk放到system分區内BOARD_BUILD_SYSTEM_ROOT_IMAGE := true
-
不再生成TARGET_NO_RECOVERY := true
鏡像recovery.img
-
将recovery ramdisk放到boot.img檔案内BOARD_USES_RECOVERY_AS_BOOT := true
-
編譯PRODUCT_PACKAGES += update_engine update_verifier
和update_engine
子產品,并安裝相應的應用update_verifier
-
- 可選的變量
-
系統自帶了一個PRODUCT_PACKAGES_DEBUG += update_engine_client
應用,可以根據需要選擇是否編譯并安裝update_engine_client
-
- 不能定義的變量
-
系統沒有recovery分區,不需要設定BOARD_RECOVERYIMAGE_PARTITION_SIZE
分區的recovery
SIZE
-
系統沒有BOARD_CACHEIMAGE_PARTITION_SIZE
分區,不需要設定cache
分區的cache
SIZE
-
系統沒有BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
分區,不需要設定cache
分區的cache
TYPE
-
3. A/B
系統鏡像檔案的生成
A/B
build\core\Makefile
定義了所需生成的鏡像目标和規則,各鏡像規則如下,我直接在代碼裡進行注釋了。
-
recovery.img
由于# A/B系統中,"TARGET_NO_RECOVERY := true",是以條件成立 ifeq (,$(filter true, $(TARGET_NO_KERNEL) $(TARGET_NO_RECOVERY))) INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img else INSTALLED_RECOVERYIMAGE_TARGET := endif
系統定了A/B
,這裡TARGET_NO_RECOVERY := true
被設定為空,是以不會生成INSTALLED_RECOVERYIMAGE_TARGET
recovery.img
-
boot.img
對比# 定義boot.img的名字和存放的路徑 INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img # # 以下error表明: # BOARD_USES_RECOVERY_AS_BOOT和BOARD_BUILD_SYSTEM_ROOT_IMAGE # 在A/B系統中需要同時被定義為true # # BOARD_USES_RECOVERY_AS_BOOT = true must have BOARD_BUILD_SYSTEM_ROOT_IMAGE = true. # BOARD_USES_RECOVERY_AS_BOOT 已經定義為true ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true) # 如果沒有定義BOARD_BUILD_SYSTEM_ROOT_IMAGE 則編譯終止,并顯示錯誤資訊 $(error BOARD_BUILD_SYSTEM_ROOT_IMAGE must be enabled for BOARD_USES_RECOVERY_AS_BOOT.) endif endif ... # 好吧,這裡才是生成boot.img的地方 ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) # 對boot.img添加依賴:boot_signer,這裡不關心 ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_BOOT_SIGNER)) $(INSTALLED_BOOTIMAGE_TARGET) : $(BOOT_SIGNER) endif # 對boot.img添加依賴:vboot_signer.sh,這裡不關心 ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT)) $(INSTALLED_BOOTIMAGE_TARGET) : $(VBOOT_SIGNER) endif # boot.img的其它依賴,并通過宏build-recoveryimage-target來生成boot.img $(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \ $(INSTALLED_RAMDISK_TARGET) \ $(INTERNAL_RECOVERYIMAGE_FILES) \ $(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \ $(INSTALLED_2NDBOOTLOADER_TARGET) \ $(recovery_build_prop) $(recovery_resource_deps) \ $(recovery_fstab) \ $(RECOVERY_INSTALL_OTA_KEYS) $(call pretty,"Target boot image from recovery: [email protected]") $(call build-recoveryimage-target, [email protected]) endif # # 上面的規則中: # INSTALLED_BOOTIMAGE_TARGET = $(PRODUCT_OUT)/boot.img # 其依賴的是recovery系統檔案,最後通過build-recoveryimage-target打包成boot.img # 這不就是把recovery.img換個名字叫boot.img麼? # # # 再來看看原本的recovery.img的生成規則: # - A/B 系統下,INSTALLED_RECOVERYIMAGE_TARGET已經定義為空,什麼都不做 # - 非A/B 系統下,以下規則會生成recovery.img # $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \ $(INSTALLED_RAMDISK_TARGET) \ $(INSTALLED_BOOTIMAGE_TARGET) \ $(INTERNAL_RECOVERYIMAGE_FILES) \ $(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \ $(INSTALLED_2NDBOOTLOADER_TARGET) \ $(recovery_build_prop) $(recovery_resource_deps) \ $(recovery_fstab) \ $(RECOVERY_INSTALL_OTA_KEYS) $(call build-recoveryimage-target, [email protected])
系統下A/B
生成方式和非boot.img
系統下A/B
的生成方式,基本上是一樣的,是以recovery.img
系統下的A/B
相當于非boot.img
系統下的A/B
。recovery.img
-
system.img
# # build-systemimage-target宏函數定義 # 宏函數内部調用build_image.py,從$(TARGET_OUT)目錄,即$(PRODUCT_OUT)/system目錄建立鏡像檔案 # # $(1): output file define build-systemimage-target @echo "Target system fs image: $(1)" $(call create-system-vendor-symlink) @mkdir -p $(dir $(1)) $(systemimage_intermediates) && rm -rf $(systemimage_intermediates)/system_image_info.txt $(call generate-userimage-prop-dictionary, $(systemimage_intermediates)/system_image_info.txt, \ skip_fsck=true) $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \ ./build/tools/releasetools/build_image.py \ $(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT) \ || ( echo "Out of space? the tree size of $(TARGET_OUT) is (MB): " 1>&2 ;\ du -sm $(TARGET_OUT) 1>&2;\ if [ "$(INTERNAL_USERIMAGES_EXT_VARIANT)" == "ext4" ]; then \ maxsize=$(BOARD_SYSTEMIMAGE_PARTITION_SIZE); \ if [ "$(BOARD_HAS_EXT4_RESERVED_BLOCKS)" == "true" ]; then \ maxsize=$$((maxsize - 4096 * 4096)); \ fi; \ echo "The max is $$(( maxsize / 1048576 )) MB." 1>&2 ;\ else \ echo "The max is $$(( $(BOARD_SYSTEMIMAGE_PARTITION_SIZE) / 1048576 )) MB." 1>&2 ;\ fi; \ mkdir -p $(DIST_DIR); cp $(INSTALLED_FILES_FILE) $(DIST_DIR)/installed-files-rescued.txt; \ exit 1 ) endef # # 調用build-systemimage-target,生成目标檔案$(BUILT_SYSTEMIMAGE) # 即:obj\PACKAGING\systemimage_intermediates\system.img檔案 # $(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE) # 站住,生成system.img的入口就在這裡了 $(call build-systemimage-target,[email protected]) # 定義system.img的名字和存放的路徑 INSTALLED_SYSTEMIMAGE := $(PRODUCT_OUT)/system.img SYSTEMIMAGE_SOURCE_DIR := $(TARGET_OUT) ...
看完這段代碼我開始有點崩潰了~~
此前boot.img裡面的ramdisk是recovery系統的recovery ramdisk,這裡生成system.img也不見哪裡添加了ramdisk啊,那系統啟動時用recovery的ramdisk挂載system分區麼?顯然不是啊~~那boot ramdisk到底藏到哪裡去了啊?
木有了辦法,那就老老實實看看宏
的過程吧,調用指令:build-systemimage-target
- 第一步,調用
建立符号連結$(call create-system-vendor-symlink)
- 第二步,建立檔案夾
,并删除其中的檔案$(systemimage_intermediates)
system_image_info.txt
- 第三步,調用
,重新生成系統屬性檔案call generate-userimage-prop-dictionary
system_image_info.txt
- 第四步,調用
,根據系統屬性檔案build_image.py
和system_image_info.txt
目錄system
建立$(PRODUCT_OUT)/system
檔案system.img
到底是如何生成build_image.py
的。system.img
的主程式比較簡單:build_image.py
- 腳本入口
# 運作build_image.py腳本的入口,轉到main函數 if __name__ == '__main__': main(sys.argv[1:])
- 主程式
函數main
# 主程式 def main(argv): if len(argv) != 4: print __doc__ sys.exit(1) """ * build_image.py的調用指令為: * ./build/tools/releasetools/build_image.py \ * $(TARGET_OUT) \ * $(systemimage_intermediates)/system_image_info.txt \ * $(systemimage_intermediates)/system.img \ * $(TARGET_OUT) * """ in_dir = argv[0] # 參數0:in_dir=$(TARGET_OUT) glob_dict_file = argv[1] # 參數1:glob_dict_file=$(systemimage_intermediates)/system_image_info.txt out_file = argv[2] # 參數2:outfile=$(systemimage_intermediates)/system.img target_out = argv[3] # 參數3:target_out=$(TARGET_OUT) # 解析系統屬性的字典檔案system_image_info.txt glob_dict = LoadGlobalDict(glob_dict_file) if "mount_point" in glob_dict: # The caller knows the mount point and provides a dictionay needed by # BuildImage(). image_properties = glob_dict else: image_filename = os.path.basename(out_file) mount_point = "" # 設定system.img的挂載點為system if image_filename == "system.img": mount_point = "system" ... else: print >> sys.stderr, "error: unknown image file name ", image_filename exit(1) image_properties = ImagePropFromGlobalDict(glob_dict, mount_point) # 調用BuildImage函數來建立檔案 if not BuildImage(in_dir, image_properties, out_file, target_out): print >> sys.stderr, "error: failed to build %s from %s" % (out_file, in_dir) exit(1)
-
函數BuildImage
顯然,def BuildImage(in_dir, prop_dict, out_file, target_out=None): ... # 關鍵!!!前面改動過了in_dir,是以條件成立 if in_dir != origin_in: # Construct a staging directory of the root file system. ramdisk_dir = prop_dict.get("ramdisk_dir") if ramdisk_dir: shutil.rmtree(in_dir) # 将字典system_image_info.txt裡"ramdisk_dir"指定的内容複制到臨時檔案夾in_dir中,并保持原有的符号連結 shutil.copytree(ramdisk_dir, in_dir, symlinks=True) staging_system = os.path.join(in_dir, "system") # 删除in_dir/system目錄,即删除ramdisk_dir下system目錄 shutil.rmtree(staging_system, ignore_errors=True) # 将origin_in目錄的内容複制到ramdisk_dir/system目錄下 # 原來的origin_in是指定的$(PRODUCT_OUT)/system目錄 # 是以這裡的操作是将ramdisk和system的内容合并生成一個完整的檔案系統 shutil.copytree(origin_in, staging_system, symlinks=True) reserved_blocks = prop_dict.get("has_ext4_reserved_blocks") == "true" ext4fs_output = None # 繼續對合并後完整的檔案系統進行其它操作,最後打包為system.img ... return exit_code == 0
腳本将ramdisk和system檔案夾下的内容合并成一個完整的檔案系統,最終輸出為system.img,再也不用擔心system.img沒有rootfs了。build_image.py
- 第一步,調用
-
userdata.img
這裡的步驟跟生成system.img基本一緻,宏函數# Don't build userdata.img if it's extfs but no partition size skip_userdata.img := # 如果TARGET_USERIMAGES_USE_EXT4定義為true,則會進行以下定義: # INTERNAL_USERIMAGES_USE_EXT := true # INTERNAL_USERIMAGES_EXT_VARIANT := ext4 # 在vendor相關的deivce下,BoradConfig.mk中會定義BOARD_USERDATAIMAGE_PARTITION_SIZE # 是以這裡最終skip_userdata.img仍然為空 ifdef INTERNAL_USERIMAGES_EXT_VARIANT ifndef BOARD_USERDATAIMAGE_PARTITION_SIZE skip_userdata.img := true endif endif # skip_userdata.img為空,是以這裡會定義userdata.img并生成這個檔案 ifneq ($(skip_userdata.img),true) userdataimage_intermediates := \ $(call intermediates-dir-for,PACKAGING,userdata) BUILT_USERDATAIMAGE_TARGET := $(PRODUCT_OUT)/userdata.img # 具體生成userdata.img的宏函數 define build-userdataimage-target $(call pretty,"Target userdata fs image: $(INSTALLED_USERDATAIMAGE_TARGET)") @mkdir -p $(TARGET_OUT_DATA) @mkdir -p $(userdataimage_intermediates) && rm -rf $(userdataimage_intermediates)/userdata_image_info.txt $(call generate-userimage-prop-dictionary, $(userdataimage_intermediates)/userdata_image_info.txt, skip_fsck=true) $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \ ./build/tools/releasetools/build_image.py \ $(TARGET_OUT_DATA) $(userdataimage_intermediates)/userdata_image_info.txt $(INSTALLED_USERDATAIMAGE_TARGET) $(TARGET_OUT) $(hide) $(call assert-max-image-size,$(INSTALLED_USERDATAIMAGE_TARGET),$(BOARD_USERDATAIMAGE_PARTITION_SIZE)) endef # 好吧,這裡才是真正調用build-userdataimage-target去生成userdata.img的規則 # We just build this directly to the install location. INSTALLED_USERDATAIMAGE_TARGET := $(BUILT_USERDATAIMAGE_TARGET) $(INSTALLED_USERDATAIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) \ $(INTERNAL_USERDATAIMAGE_FILES) # 生成userdata.img的入口就這裡了 $(build-userdataimage-target)
内通過build-userdataimage-target
來将build_image.py
目錄内容打包生成$(PRODUCT_OUT)/data
,不同的是,這裡不再需要放入userdata.img
ramdisk
的内容。
顯然,userdata.img的生成跟是否是
系統沒有關系。A/B
-
cache.img
于A/B系統定了沒有定義# cache partition image # `A/B`系統中 BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE 沒有定義,這裡條件不能滿足,是以不會生成cache.img ifdef BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE INTERNAL_CACHEIMAGE_FILES := \ $(filter $(TARGET_OUT_CACHE)/%,$(ALL_DEFAULT_INSTALLED_MODULES)) cacheimage_intermediates := \ $(call intermediates-dir-for,PACKAGING,cache) BUILT_CACHEIMAGE_TARGET := $(PRODUCT_OUT)/cache.img ... # We just build this directly to the install location. # 這裡是真正去生成cache.img的地方,可惜`A/B`系統下不會再有調用了 INSTALLED_CACHEIMAGE_TARGET := $(BUILT_CACHEIMAGE_TARGET) $(INSTALLED_CACHEIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_CACHEIMAGE_FILES) $(build-cacheimage-target) ... else # BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE # we need to ignore the broken cache link when doing the rsync IGNORE_CACHE_LINK := --exclude=cache endif # BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
,這裡BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
也不會定義,是以不會生成cache.imgBUILT_CACHEIMAGE_TARGET
-
vendor.img
顯然,vendor.img跟是否是# vendor partition image # 如果系統内有定義BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE,則這裡會生成vendor.img ifdef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE # 定義vendor系統内包含的所有檔案 INTERNAL_VENDORIMAGE_FILES := \ $(filter $(TARGET_OUT_VENDOR)/%,\ $(ALL_DEFAULT_INSTALLED_MODULES)\ $(ALL_PDK_FUSION_FILES)) # platform.zip depends on $(INTERNAL_VENDORIMAGE_FILES). $(INSTALLED_PLATFORM_ZIP) : $(INTERNAL_VENDORIMAGE_FILES) # vendor的檔案清單:installed-files-vendor.txt INSTALLED_FILES_FILE_VENDOR := $(PRODUCT_OUT)/installed-files-vendor.txt $(INSTALLED_FILES_FILE_VENDOR) : $(INTERNAL_VENDORIMAGE_FILES) @echo Installed file list: [email protected] @mkdir -p $(dir [email protected]) @rm -f [email protected] $(hide) build/tools/fileslist.py $(TARGET_OUT_VENDOR) > [email protected] # vendor.img目标 vendorimage_intermediates := \ $(call intermediates-dir-for,PACKAGING,vendor) BUILT_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img # 定義生成vendor.img的宏函數build-vendorimage-target define build-vendorimage-target $(call pretty,"Target vendor fs image: $(INSTALLED_VENDORIMAGE_TARGET)") @mkdir -p $(TARGET_OUT_VENDOR) @mkdir -p $(vendorimage_intermediates) && rm -rf $(vendorimage_intermediates)/vendor_image_info.txt $(call generate-userimage-prop-dictionary, $(vendorimage_intermediates)/vendor_image_info.txt, skip_fsck=true) $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH \ ./build/tools/releasetools/build_image.py \ $(TARGET_OUT_VENDOR) $(vendorimage_intermediates)/vendor_image_info.txt $(INSTALLED_VENDORIMAGE_TARGET) $(TARGET_OUT) $(hide) $(call assert-max-image-size,$(INSTALLED_VENDORIMAGE_TARGET),$(BOARD_VENDORIMAGE_PARTITION_SIZE)) endef # We just build this directly to the install location. # 生成vendor.img的依賴和規則 INSTALLED_VENDORIMAGE_TARGET := $(BUILT_VENDORIMAGE_TARGET) $(INSTALLED_VENDORIMAGE_TARGET): $(INTERNAL_USERIMAGES_DEPS) $(INTERNAL_VENDORIMAGE_FILES) $(INSTALLED_FILES_FILE_VENDOR) $(build-vendorimage-target) .PHONY: vendorimage-nodeps vendorimage-nodeps: | $(INTERNAL_USERIMAGES_DEPS) $(build-vendorimage-target) # 如果定義了BOARD_PREBUILT_VENDORIMAGE,說明已經預備好了vendor.img,那就直接複制到目标位置 else ifdef BOARD_PREBUILT_VENDORIMAGE INSTALLED_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img $(eval $(call copy-one-file,$(BOARD_PREBUILT_VENDORIMAGE),$(INSTALLED_VENDORIMAGE_TARGET))) endif
系統沒有關系,主要看系統是否定義了A/B
。BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE
到此為止,我們已經分析了除更新包
update.zip
外的主要檔案的生成,包括
recovery.img
,
boot.img
,
system.img
,
userdata.img
,
cache.img
和
vendor.img
。
總結:
-
,不再單獨生成,傳統方式的recovery.img
現在叫做recovery.img
boot.img
-
,包含boot.img
和kernel
模式的recovery
ramdisk
-
,傳統方式下system.img
由system.img
檔案夾打包而成,$(PRODUCT_OUT)/system
系統下,制作時将A/B
和$(PRODUCT_OUT)/root
合并到一起,生成一個完整的帶有$(PRODUCT_OUT)/system
的rootfs
system.img
-
,跟原來一樣,打包userdata.img
檔案夾而成$(PRODUCT_OUT)/data
-
,cache.img
系統下不再單獨生成A/B
cache.img
-
,檔案的生成跟是否vendor.img
系統無關,主要有廠家決定A/B
現在的情況是,裝置啟動後
bootloader
解析
boot.img
得到
kernel
檔案,啟動
Linux
進入系統,然後加載
android
主系統
system
,但是
boot.img
和
system.img
兩個鏡像内都有
rootfs
,這個啟動是如何啟動,那這個到底是怎麼搞的呢?下一篇會對這個啟動流程詳細分析。