RK3588 AB镜像升级学习(一)

参考资料:Android A/B 系统_洛奇看世界的博客-CSDN博客

一、AB镜像分区


区分了OTA升级镜像的两种方式:

  • 传统的升级方式:设备有Android系统和Recovery系统,如果Android需要升级时,把内容存到cache分区。重启后进入recovery系统,从cache分区更新Android系统。
  • A/B的升级方式:由Android后台的update_engine和两套slot A和slot B组成,update_engine监测升级信息,并下载升级数据,会把数据更新到非运行的分区,写完后从更新的分区启动。

系统的分区

说明引导linuxAndroid主系统的linux kernel文件和挂载system和其他分区的ramdiskAndroid系统分区(应用程序、库文件)厂商定制应用和库文件用户数据分区临时数据分区,存放OTA存放Recovery系统的linux kernel和ramdisk存放Android主系统和Recovery系统跟bootloader通信数据
传统升级分区bootloaderbootsystemvendoruserdatacacherecoverymisc
A/B升级分区bootloaderboot_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分区升级机制中,这些属性通常存储在分区的元数据中,或者在引导加载程序的配置中。它们用于指导系统的升级和回滚过程,确保设备始终能够启动到一个稳定、可靠的系统版本。

  1. 普通场景(Normal cases):

A分区:灰色方框,表示当前没有用,设置为bootable和successful。
B分区:绿色方框,表示当前正在使用,设置为active、bootable和successful。

  1. 升级中(Update in progress):

A分区:灰色方框,表示正在进行升级,设置为unbootable和清除successful标识。
B分区:绿色方框,表示当前正在使用,保持为active、bootable和successful。

  1. 更新完成,等待重启(Update applied, reboot pending):

A分区:灰色方框,表示升级完成,设置为bootable和active,但没有设置successful。
B分区:灰色方框,表示等待重启,设置为bootable和successful,但没有active。

  1. 从新系统成功启动(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系统无关,主要有厂家决定
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
RK3588S是瑞芯微推出的一款高性能AIoT芯片,具有强大的计算和图像处理能力。关于镜像克隆,我理解你是指将一个系统镜像复制到多个设备上的过程。 镜像克隆是一种常见的部署方式,可以快速批量部署相同的操作系统和应用程序。对于RK3588S芯片,镜像克隆的步骤如下: 1. 准备源设备:首先需要准备一个已经配置好的RK3588S设备作为源设备,该设备上已经安装了所需的操作系统和应用程序,并进行了相应的配置。 2. 创建系统镜像:使用专业的镜像制作工具,如Win32 Disk Imager、dd命令等,将源设备的整个磁盘或分区制作成一个镜像文件。这个镜像文件包含了源设备上的所有数据和配置。 3. 复制镜像到目标设备:将制作好的系统镜像文件复制到目标设备上。可以使用USB存储设备、网络传输等方式将镜像文件传输到目标设备。 4. 写入镜像到目标设备:使用相同的镜像制作工具,将镜像文件写入目标设备的磁盘或分区。这个过程会将镜像文件中的数据和配置写入目标设备,使其与源设备完全相同。 5. 配置目标设备:完成镜像写入后,需要对目标设备进行一些必要的配置,如修改主机名、网络设置等,以确保目标设备可以正常工作。 需要注意的是,在进行镜像克隆时,要确保目标设备与源设备的硬件配置相同或兼容,以避免出现兼容性问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

习惯就好zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值