参考资料:Android A/B 系统_洛奇看世界的博客-CSDN博客
一、AB镜像分区
区分了OTA升级镜像的两种方式:
- 传统的升级方式:设备有Android系统和Recovery系统,如果Android需要升级时,把内容存到cache分区。重启后进入recovery系统,从cache分区更新Android系统。
- A/B的升级方式:由Android后台的update_engine和两套slot A和slot B组成,update_engine监测升级信息,并下载升级数据,会把数据更新到非运行的分区,写完后从更新的分区启动。
系统的分区
说明 | 引导linux | Android主系统的linux kernel文件和挂载system和其他分区的ramdisk | Android系统分区(应用程序、库文件) | 厂商定制应用和库文件 | 用户数据分区 | 临时数据分区,存放OTA | 存放Recovery系统的linux kernel和ramdisk | 存放Android主系统和Recovery系统跟bootloader通信数据 | |
---|---|---|---|---|---|---|---|---|---|
① | 传统升级分区 | bootloader | boot | system | vendor | userdata | cache | recovery | misc |
② | A/B升级分区 | bootloader | boot_a boot_b | system_a system_b | vendor_a vendor_b | userdata | / | / | misc |
区别:
系统:①只有一套分区,②由两套分区
bootloader交互方式:①读取misc分区信息来进入主系统或Recovery系统,②通过特定的分区信息来决定进A或B
编译:①会生成boot.img和recovery.img,②只生成boot.img
OTA更新包生成方式:生成方式是一样的,②的内容不一样
系统分区属性
在Android的AB(A/B)分区升级机制中,每个分区确实有一些关键的属性,用于管理分区的状态和升级过程。这些属性通常包括:
- Active:表示当前正在使用的分区。在任何给定时间,只有一个分区是活动的。
- Bootable:表示分区是否可以启动。这通常用于确定分区是否已经准备好被引导加载程序启动。
- Successful:表示分区上次尝试启动是否成功。这个属性用于确定如果设备重启,是否应该继续使用当前分区,或者是否应该回滚到另一个分区。
这些属性通常由引导加载程序(如fastboot)和系统更新机制(如update_engine)管理。它们用于确保系统的稳定性和可靠性,以及在更新失败时能够安全地回滚到之前的版本。
在AB分区升级机制中,这些属性通常存储在分区的元数据中,或者在引导加载程序的配置中。它们用于指导系统的升级和回滚过程,确保设备始终能够启动到一个稳定、可靠的系统版本。
- 普通场景(Normal cases):
A分区:灰色方框,表示当前没有用,设置为bootable和successful。
B分区:绿色方框,表示当前正在使用,设置为active、bootable和successful。
- 升级中(Update in progress):
A分区:灰色方框,表示正在进行升级,设置为unbootable和清除successful标识。
B分区:绿色方框,表示当前正在使用,保持为active、bootable和successful。
- 更新完成,等待重启(Update applied, reboot pending):
A分区:灰色方框,表示升级完成,设置为bootable和active,但没有设置successful。
B分区:灰色方框,表示等待重启,设置为bootable和successful,但没有active。
- 从新系统成功启动(System rebooted into new update):
A分区:绿色方框,表示重启后从A分区启动,设置为active、bootable和successful。
B分区:灰色方框,表示当前没有用,设置为bootable和successful,但没有active。
参考资料:Android A/B System OTA分析(二)系统image的生成_android新建分区如何生成image-CSDN博客
二、A/B镜像相关的Makefile变量
A/B系统必须定义的变量
- AB_OTA_UPDATER := true
A/B系统的主要开关变量,设置后
- recovery系统内不再具有操作cache分区的功能,bootable/recovery/recovery_ui/device.cpp
- recovery系统使用不同的方式来解析升级文件,bootable/recovery/install/install.cpp
- 生成A/B系统相关的META文件
- AB_OTA_PARTITIONS := boot system vendor
将A/B系统可升级的分区写入文件_$(zip_root)/META/ab_partitions.txt_
- BOARD_BUILD_SYSTEM_ROOT_IMAGE := true
将boot ramdisk放到system分区内
- TARGET_NO_RECOVERY := true
不再生成recovery.img镜像
- BOARD_USES_RECOVERY_AS_BOOT := true
将recovery ramdisk放到boot.img文件内
- PRODUCT_PACKAGES += update_engine update_verifier
编译update_engine和update_verifier模块,并安装相应的应用
A/B系统可选定义的变量
- PRODUCT_PACKAGES_DEBUG += update_engine_client
系统自带了一个update_engine_client应用,可以根据需要选择是否编译并安装
A/B系统不能定义的变量
- BOARD_RECOVERYIMAGE_PARTITION_SIZE
系统没有recovery分区,不需要设置recovery分区的SIZE
- BOARD_CACHEIMAGE_PARTITION_SIZE
系统没有cache分区,不需要设置cache分区的SIZE
- BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
系统没有cache分区,不需要设置cache分区的TYPE
A/B系统镜像文件的生成
build/core/Makefile定义了所需生成的镜像目标和规则,各镜像规则如下,我直接在代码里进行注释了。
recovery.img
ifneq ($(TARGET_NO_RECOVERY),true)
$(hide) cp $(TARGET_PREBUILT_RESOURCE) $(zip_root)/RECOVERY/resource.img
endif
由于A/B系统定了TARGET_NO_RECOVERY := true,这里INSTALLED_RECOVERYIMAGE_TARGET被设置为空,所以不会生成recovery.img
boot.img
ifneq ($(strip $(BOARD_KERNEL_BINARIES)),) # 检查BOARD_KERNEL_BINARIES变量是否为空
# 将BOARD_KERNEL_BINARIES变量中的kernel替换为boot
# 如果有多个内核镜像,则为每个内核镜像生成一个boot.img文件。
BUILT_BOOTIMAGE_TARGET := $(foreach k,$(subst kernel,boot,$(BOARD_KERNEL_BINARIES)), $(PRODUCT_OUT)/$(k).img)
else
# 如果没有多个内核镜像,则只生成一个boot.img文件。
BUILT_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
endif
INTERNAL_PREBUILT_BOOTIMAGE :=
# 遍历PRODUCT_PACKAGES变量中的所有包,检查它们是否包含EXTRACTED_BOOT_IMAGE属性,如果有,则将其添加到my_installed_prebuilt_gki_apex变量中
my_installed_prebuilt_gki_apex := $(strip $(foreach package,$(PRODUCT_PACKAGES),$(if $(ALL_MODULES.$(package).EXTRACTED_BOOT_IMAGE),$(package))))
ifdef my_installed_prebuilt_gki_apex
ifneq (1,$(words $(my_installed_prebuilt_gki_apex))) # len(my_installed_prebuilt_gki_apex) > 1
# my_installed_prebuilt_gki_apex中包含预构建GKI APEX镜像数量大于1就报错
$(error More than one prebuilt GKI APEXes are installed: $(my_installed_prebuilt_gki_apex))
endif # len(my_installed_prebuilt_gki_apex) > 1
ifdef BOARD_PREBUILT_BOOTIMAGE
# 如果BOARD_PREBUILT_BOOTIMAGE已经被定义,则提示已经有预构建GKI APEX镜像
$(error Must not define BOARD_PREBUILT_BOOTIMAGE because a prebuilt GKI APEX is installed: $(my_installed_prebuilt_gki_apex))
endif # BOARD_PREBUILT_BOOTIMAGE defined
# 获取已安装的预构建GKI APEX镜像的提取boot.img文件的路径
my_apex_extracted_boot_image := $(ALL_MODULES.$(my_installed_prebuilt_gki_apex).EXTRACTED_BOOT_IMAGE)
# 定义boot.img文件的目标路径
INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
# 将提取的boot.img文件复制到目标路径
$(eval $(call copy-one-file,$(my_apex_extracted_boot_image),$(INSTALLED_BOOTIMAGE_TARGET)))
# 将提取的boot.img文件设置为内部预构建的boot.img文件
INTERNAL_PREBUILT_BOOTIMAGE := $(my_apex_extracted_boot_image)
else # my_installed_prebuilt_gki_apex not defined
# $1: boot image target
# returns the kernel used to make the bootimage
define bootimage-to-kernel
$(if $(BOARD_KERNEL_BINARIES),\
$(PRODUCT_OUT)/$(subst .img,,$(subst boot,kernel,$(notdir $(1)))),\ #获取用于制作boot.img文件的内核的路径
$(INSTALLED_KERNEL_TARGET)) # 如果BOARD_KERNEL_BINARIES变量没有被定义,则使用INSTALLED_KERNEL_TARGET变量中的内核路径
endef
ifdef BOARD_BOOTIMAGE_PARTITION_SIZE
BOARD_KERNEL_BOOTIMAGE_PARTITION_SIZE := $(BOARD_BOOTIMAGE_PARTITION_SIZE)
endif
代码用于处理预构建的GKI APEX镜像的安装和配置。如果安装了预构建的GKI APEX镜像,则将其提取的boot.img文件复制到目标路径,并设置为内部预构建的boot.img文件。如果没有安装预构建的GKI APEX镜像,则使用INSTALLED_KERNEL_TARGET变量中的内核路径来制作boot.img文件
# ....如果没有定义BOARD_PREBUILT_BOOTIMAGE则INSTALLED_BOOTIMAGE_TARGET为空
else # BOARD_PREBUILT_BOOTIMAGE则 not defined
INSTALLED_BOOTIMAGE_TARGET :=
endif # BOARD_PREBUILT_BOOTIMAGE
endif # TARGET_NO_KERNEL
endif # my_installed_prebuilt_gki_apex not defined
# ...
# 这段代码用于处理当设备使用恢复模式作为启动模式时的构建过程。
# 它为boot.img文件添加依赖关系,并使用recoveryimage-deps变量中的文件作为依赖关系。
# 然后,它调用build-recoveryimage-target宏来构建boot.img文件,
# 并将用于制作boot.img文件的内核路径作为参数传递给宏。
ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
$(foreach b,$(INSTALLED_BOOTIMAGE_TARGET), $(eval $(call add-dependency,$(b),$(call bootimage-to-kernel,$(b)))))
$(INSTALLED_BOOTIMAGE_TARGET): $(recoveryimage-deps)
$(call pretty,"Target boot image from recovery: $@")
$(call build-recoveryimage-target, $@, $(PRODUCT_OUT)/$(subst .img,,$(subst boot,kernel,$(notdir $@))))
endif # BOARD_USES_RECOVERY_AS_BOOT
# 再来看看原本的recovery.img的生成规则:
# - A/B 系统下,INSTALLED_RECOVERYIMAGE_TARGET已经定义为空,什么都不做
# - 非A/B 系统下,以下规则会生成recovery.img
$(INSTALLED_RECOVERYIMAGE_TARGET): $(recoveryimage-deps)
$(call build-recoveryimage-target, $@, \
$(if $(filter true, $(BOARD_EXCLUDE_KERNEL_FROM_RECOVERY_IMAGE)),, $(recovery_kernel)))
在Makefile的开头可以看到:
INSTALLED_RECOVERYIMAGE_TARGET :=
# Build recovery image if
# BUILDING_RECOVERY_IMAGE && !BOARD_USES_RECOVERY_AS_BOOT && !BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT.
# If BOARD_USES_RECOVERY_AS_BOOT is true, leave empty because INSTALLED_BOOTIMAGE_TARGET is built
# with recovery resources.
# If BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT is true, leave empty to build recovery resources
# but not the final recovery image.
# 满足条件时会生成recovery.img
ifdef BUILDING_RECOVERY_IMAGE
ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
ifneq ($(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT),true)
INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
endif
endif
endif
从上面生成boot.img和recovery.img:
如果是AB升级的镜像:
- BOARD_USES_RECOVERY_AS_BOOT:这个参数通常被设置为true,表示设备使用恢复模式作为启动模式。在这种情况下,不需要单独的recovery.img文件,因为恢复模式的功能已经被集成到启动镜像中。
- BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT:这个参数通常被设置为true,表示设备的恢复资源被移动到供应商启动分区。在这种情况下,也不需要单独的recovery.img文件。
- BUILDING_RECOVERY_IMAGE:这个参数通常被设置为false,因为不需要生成单独的recovery.img文件。
如果不是AB升级的镜像:
- BOARD_USES_RECOVERY_AS_BOOT:这个参数通常被设置为false,表示设备不使用恢复模式作为启动模式。在这种情况下,需要生成单独的recovery.img文件。
- BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT:这个参数通常被设置为false,表示设备的恢复资源不移动到供应商启动分区。在这种情况下,需要生成单独的recovery.img文件。
- BUILDING_RECOVERY_IMAGE:这个参数通常被设置为true,因为需要生成单独的recovery.img文件。
总的来说,在AB升级机制中,通常不需要生成单独的recovery.img文件,因此相关参数会被设置为true。在非AB升级机制中,需要生成单独的recovery.img文件,因此相关参数会被设置为false
对比A/B系统下boot.img生成方式和非A/B系统下recovery.img的生成方式,基本上是一样的,所以A/B系统下的boot.img相当于非A/B系统下的recovery.img。
system.img
# $(1): output file
define build-systemimage-target
@echo "Target system fs image: $(1)"
# 创建必要的目录,并删除旧的系统镜像信息文件
@mkdir -p $(dir $(1)) $(systemimage_intermediates) && rm -rf $(systemimage_intermediates)/system_image_info.txt
# 调用call generate-userimage-prop-dictionary,重新生成系统属性文件system_image_info.txt
$(call generate-image-prop-dictionary, $(systemimage_intermediates)/system_image_info.txt,system, \
skip_fsck=true)
PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
$(BUILD_IMAGE) \ # 调用BUILD_IMAGE制作镜像
# 传递必要的参数给BUILD_IMAGE命令,包括目标输出目录、系统镜像信息文件、系统镜像文件的目标路径和目标输出目录。
$(TARGET_OUT) $(systemimage_intermediates)/system_image_info.txt $(1) $(TARGET_OUT) \
|| ( mkdir -p $${DIST_DIR}; \
cp $(INSTALLED_FILES_FILE) $${DIST_DIR}/installed-files-rescued.txt; \
exit 1 )
# 如果BUILD_IMAGE命令失败,则执行错误处理
# 复制已安装文件列表到分布目录,作为备份
endef
ifeq ($(BOARD_AVB_ENABLE),true)
$(BUILT_SYSTEMIMAGE): $(BOARD_AVB_SYSTEM_KEY_PATH)
endif
$(BUILT_SYSTEMIMAGE): $(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)
$(call build-systemimage-target,$@)
# 这行代码定义系统镜像文件的目标路径
INSTALLED_SYSTEMIMAGE_TARGET := $(PRODUCT_OUT)/system.img
# 这行代码定义系统镜像文件的源目录路径
SYSTEMIMAGE_SOURCE_DIR := $(TARGET_OUT)
这个BUILD_IMAGE在build/core/config.mk:571被定义:
BUILD_IMAGE := $(HOST_OUT_EXECUTABLES)/build_image$(HOST_EXECUTABLE_SUFFIX)
然后用find ./build/ -name "build_image*"只找到build_image.py命令
./build/make/tools/releasetools/build_image.py
所以应该是调用build_image.py,根据系统属性文件system_image_info.txt和system目录$(PRODUCT_OUT)/system创建system.img文件
build_image.py的程序入口
main(argv):
if len(argv) != 4:
print(__doc__)
sys.exit(1)
common.InitLogging()
in_dir = argv[0] # TARGET_OUT
glob_dict_file = argv[1] # $(systemimage_intermediates)/system_image_info.txt
out_file = argv[2] # $(1)
target_out = argv[3] # $(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 dictionary 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:
logger.error("Unknown image file name %s", image_filename)
sys.exit(1)
image_properties = ImagePropFromGlobalDict(glob_dict, mount_point)
try: # 调用BuildImage函数来创建文件
BuildImage(in_dir, image_properties, out_file, target_out)
except:
logger.error("Failed to build %s from %s", out_file, in_dir)
raise
if __name__ == '__main__':
try:
main(sys.argv[1:])
finally:
common.Cleanup()
对应的BuildImage函数用了BuildImageMkfs来创建文件系统,最后调用了e2fsck来创建文件系统
def BuildImageMkfs(in_dir, prop_dict, out_file, target_out, fs_config):
"""Builds a pure image for the files under in_dir and writes it to out_file.
Args:
in_dir: Path to input directory.
prop_dict: A property dict that contains info like partition size. Values
will be updated with computed values.
out_file: The output image file.
target_out: Path to the TARGET_OUT directory as in Makefile. It actually
points to the /system directory under PRODUCT_OUT. fs_config (the one
under system/core/libcutils) reads device specific FS config files from
there.
fs_config: The fs_config file that drives the prototype
Raises:
BuildImageError: On build image failures.
"""
build_command = []
fs_type = prop_dict.get("fs_type", "")
run_e2fsck = False
needs_projid = prop_dict.get("needs_projid", 0)
needs_casefold = prop_dict.get("needs_casefold", 0)
needs_compress = prop_dict.get("needs_compress", 0)
if fs_type.startswith("ext"): # 如果是ext格式,调用mkuserimg工具来创建文件系统镜像,并根据不同的参数来指定镜像的属性。
build_command = [prop_dict["ext_mkuserimg"]]
if "extfs_sparse_flag" in prop_dict:
build_command.append(prop_dict["extfs_sparse_flag"])
run_e2fsck = True
build_command.extend([in_dir, out_file, fs_type,
prop_dict["mount_point"]])
build_command.append(prop_dict["image_size"])
if "journal_size" in prop_dict:
build_command.extend(["-j", prop_dict["journal_size"]])
if "timestamp" in prop_dict:
build_command.extend(["-T", str(prop_dict["timestamp"])])
if fs_config:
build_command.extend(["-C", fs_config])
if target_out:
build_command.extend(["-D", target_out])
if "block_list" in prop_dict:
build_command.extend(["-B", prop_dict["block_list"]])
if "base_fs_file" in prop_dict:
base_fs_file = ConvertBlockMapToBaseFs(prop_dict["base_fs_file"])
build_command.extend(["-d", base_fs_file])
build_command.extend(["-L", prop_dict["mount_point"]])
if "extfs_inode_count" in prop_dict:
build_command.extend(["-i", prop_dict["extfs_inode_count"]])
if "extfs_rsv_pct" in prop_dict:
build_command.extend(["-M", prop_dict["extfs_rsv_pct"]])
if "flash_erase_block_size" in prop_dict:
build_command.extend(["-e", prop_dict["flash_erase_block_size"]])
if "flash_logical_block_size" in prop_dict:
build_command.extend(["-o", prop_dict["flash_logical_block_size"]])
# Specify UUID and hash_seed if using mke2fs.
if prop_dict["ext_mkuserimg"] == "mkuserimg_mke2fs":
if "uuid" in prop_dict:
build_command.extend(["-U", prop_dict["uuid"]])
if "hash_seed" in prop_dict:
build_command.extend(["-S", prop_dict["hash_seed"]])
if prop_dict.get("ext4_share_dup_blocks") == "true":
build_command.append("-c")
if (needs_projid):
build_command.extend(["--inode_size", "512"])
else:
build_command.extend(["--inode_size", "256"])
if "selinux_fc" in prop_dict:
build_command.append(prop_dict["selinux_fc"])
elif fs_type.startswith("erofs"): # 如果是erofs格式
build_command = ["mkerofsimage.sh"]
build_command.extend([in_dir, out_file])
# ...
elif fs_type.startswith("squash"): # 如果是squash格式
build_command = ["mksquashfsimage.sh"]
# ...
elif fs_type.startswith("f2fs"): # 如果是f2fs格式
# ...
else:
raise BuildImageError(
"Error: unknown filesystem type: {}".format(fs_type))
try: # 构建system
mkfs_output = common.RunAndCheckOutput(build_command)
except:
try:
du = GetDiskUsage(in_dir)
du_str = "{} bytes ({} MB)".format(du, du // BYTES_IN_MB)
# Suppress any errors from GetDiskUsage() to avoid hiding the real errors
# from common.RunAndCheckOutput().
except Exception: # pylint: disable=broad-except
logger.exception("Failed to compute disk usage with du")
du_str = "unknown"
print(
"Out of space? Out of inodes? The tree size of {} is {}, "
"with reserved space of {} bytes ({} MB).".format(
in_dir, du_str,
int(prop_dict.get("partition_reserved_size", 0)),
int(prop_dict.get("partition_reserved_size", 0)) // BYTES_IN_MB))
if ("image_size" in prop_dict and "partition_size" in prop_dict):
print(
"The max image size for filesystem files is {} bytes ({} MB), "
"out of a total partition size of {} bytes ({} MB).".format(
int(prop_dict["image_size"]),
int(prop_dict["image_size"]) // BYTES_IN_MB,
int(prop_dict["partition_size"]),
int(prop_dict["partition_size"]) // BYTES_IN_MB))
raise
if run_e2fsck and prop_dict.get("skip_fsck") != "true":
# 将稀疏镜像文件转换为未稀疏的镜像文件
unsparse_image = UnsparseImage(out_file, replace=False)
# Run e2fsck on the inflated image file 运行e2fsck工具对未稀疏的镜像文件进行检查
e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image]
try:
common.RunAndCheckOutput(e2fsck_command)
finally:
os.remove(unsparse_image)
return mkfs_output
userdata.img
# -----------------------------------------------------------------
# data partition image
INTERNAL_USERDATAIMAGE_FILES := \
$(filter $(TARGET_OUT_DATA)/%,$(ALL_DEFAULT_INSTALLED_MODULES))
# 如果定义了BUILDING_USERDATA_IMAGE,所以这里会定义userdata.img并生成这个文件
ifdef BUILDING_USERDATA_IMAGE
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-image-prop-dictionary, $(userdataimage_intermediates)/userdata_image_info.txt,userdata,skip_fsck=true)
PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
$(BUILD_IMAGE) \
$(TARGET_OUT_DATA) $(userdataimage_intermediates)/userdata_image_info.txt \
$(INSTALLED_USERDATAIMAGE_TARGET) $(TARGET_OUT)
$(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_DEPS := \
$(INTERNAL_USERIMAGES_DEPS) \
$(INTERNAL_USERDATAIMAGE_FILES)
$(INSTALLED_USERDATAIMAGE_TARGET): $(INSTALLED_USERDATAIMAGE_TARGET_DEPS)
$(build-userdataimage-target)
这里的步骤跟生成system.img基本一致,宏函数build-userdataimage-target内通过build_image.py来将$(PRODUCT_OUT)/data目录内容打包生成userdata.img,不同的是,这里不再需要放入ramdisk的内容。
显然,userdata.img的生成跟是否是A/B系统没有关系。
cache.img
# -----------------------------------------------------------------
# cache partition image
# `A/B`系统中 BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE 没有定义,这里条件不能满足,所以不会生成cache.img
ifdef BUILDING_CACHE_IMAGE
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
define build-cacheimage-target
$(call pretty,"Target cache fs image: $(INSTALLED_CACHEIMAGE_TARGET)")
@mkdir -p $(TARGET_OUT_CACHE)
@mkdir -p $(cacheimage_intermediates) && rm -rf $(cacheimage_intermediates)/cache_image_info.txt
$(call generate-image-prop-dictionary, $(cacheimage_intermediates)/cache_image_info.txt,cache,skip_fsck=true)
PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
$(BUILD_IMAGE) \
$(TARGET_OUT_CACHE) $(cacheimage_intermediates)/cache_image_info.txt \
$(INSTALLED_CACHEIMAGE_TARGET) $(TARGET_OUT)
$(call assert-max-image-size,$(INSTALLED_CACHEIMAGE_TARGET),$(BOARD_CACHEIMAGE_PARTITION_SIZE))
endef
# 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)
于A/B系统定了没有定义BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE,这里BUILT_CACHEIMAGE_TARGET也不会定义,所以不会生成cache.img
vendor.img
# -----------------------------------------------------------------
# vendor partition image
# 如果系统内有定义BUILDING_VENDOR_IMAGE,则这里会生成vendor.img
ifdef BUILDING_VENDOR_IMAGE
# 定义vendor系统内包含的所有文件
INTERNAL_VENDORIMAGE_FILES := \
$(filter $(TARGET_OUT_VENDOR)/%,\
$(ALL_DEFAULT_INSTALLED_MODULES))
# Create symlink /vendor/odm to /odm if necessary.
ifdef BOARD_USES_ODMIMAGE
INTERNAL_VENDORIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT_VENDOR)/odm,/odm,odm.img)
endif
ifdef BOARD_USES_VENDOR_DLKMIMAGE
INTERNAL_VENDORIMAGE_FILES += $(call create-partition-compat-symlink,$(TARGET_OUT_VENDOR)/lib/modules,/vendor_dlkm/lib/modules,vendor_dlkm.img)
endif
# vendor的文件列表:installed-files-vendor.txt
INSTALLED_FILES_FILE_VENDOR := $(PRODUCT_OUT)/installed-files-vendor.txt
INSTALLED_FILES_JSON_VENDOR := $(INSTALLED_FILES_FILE_VENDOR:.txt=.json)
$(INSTALLED_FILES_FILE_VENDOR): .KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON_VENDOR)
$(INSTALLED_FILES_FILE_VENDOR) : $(INTERNAL_VENDORIMAGE_FILES) $(FILESLIST) $(FILESLIST_UTIL)
@echo Installed file list: $@
mkdir -p $(dir $@)
rm -f $@
$(FILESLIST) $(TARGET_OUT_VENDOR) > $(@:.txt=.json)
$(FILESLIST_UTIL) -c $(@:.txt=.json) > $@
# 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-image-prop-dictionary, $(vendorimage_intermediates)/vendor_image_info.txt,vendor,skip_fsck=true)
PATH=$(INTERNAL_USERIMAGES_BINARY_PATHS):$$PATH \
$(BUILD_IMAGE) \
$(TARGET_OUT_VENDOR) $(vendorimage_intermediates)/vendor_image_info.txt \
$(INSTALLED_VENDORIMAGE_TARGET) $(TARGET_OUT)
$(call assert-max-image-size,$(INSTALLED_VENDORIMAGE_TARGET) $(RECOVERY_FROM_BOOT_PATCH),$(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) \
$(RECOVERY_FROM_BOOT_PATCH)
$(build-vendorimage-target)
.PHONY: vendorimage-nodeps vnod
vendorimage-nodeps vnod: | $(INTERNAL_USERIMAGES_DEPS)
$(build-vendorimage-target)
sync: $(INTERNAL_VENDORIMAGE_FILES)
# 如果定义了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
显然,vendor.img跟是否是A/B系统没有关系,主要看系统是否定义了BUILDING_VENDOR_IMAGE。
总结:
- recovery.img,不再单独生成,传统方式的recovery.img现在叫做boot.img
- boot.img,包含kernel和recovery模式的ramdisk
- system.img,传统方式下system.img由 ( P R O D U C T O U T ) / s y s t e m 文件夹打包而成, A / B 系统下,制作时将 (PRODUCT_OUT)/system文件夹打包而成,A/B系统下,制作时将 (PRODUCTOUT)/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系统无关,主要有厂家决定