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系统无关,主要有厂家决定

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/664045.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GSM信令流程(附着、去附着、PDP激活、修改流程)

1、联合附着流程 附着包括身份认证、鉴权等 2、去附着流程 用户发起去附着 SGSN发起去附着 HLR发起去附着 GSSN使用S4发起去附着 3、Activation Procedures(PDP激活流程) 4、PDP更新或修改流程 5、Deactivate PDP Context 6、RAU(Routeing Area Update)流程 7、鉴权加…

Centos 7 安装刻录至硬件服务器

前言 在日常测试中,会遇到很多安装的场景,今天给大家讲一下centos 7 的安装,希望对大家有所帮助。 一.下载镜像 地址如下: centos官方镜像下载地址https://www.centos.org/download/ 按照需求依次点击下载 二.镜像刻录 镜像刻…

STM32---蓝牙模块ECB02(从机模式_与手机通信)

目录 1、ECB02 重点 参数 2、引脚说明 3、AT指令使用重点 4、使用USB转TTL模块测试 5、STM32开发板的接线 6、STM32通信代码 7、手机小程序 连接ECB02 1、ECB02 重点 参数 蓝牙协议: 5.2主从模式:主、从一体,可设置睡眠模式&#xff…

Jmeter压测中遇到的问题汇总

Jmeter使用过程问题总结 一、某个请求的请求体中有中文字段,执行后该请求无法成功 解决方法:在取样器的内容编码处加上UTF-8 二、遇到接口请求后报401,请求未授权(或者信息头管理器只写了cookie请求不成功) 解决方…

开展FMEA,这些准备工作你做足了吗?

在产品研发和制造过程中,失效模式与影响分析(FMEA)是一项至关重要的质量工具。它能够帮助团队提前识别和预防潜在的产品失效问题,从而提升产品的可靠性和质量。然而,要想充分发挥FMEA的效用,充分的准备工作…

全志H616(BIGTREETECH CB1)和 博通BCM2711(树莓派4B)CPU对比测试

一,实物对比图: BIGTREETECH CB1的底板接口的分布和树莓派4B是一样的,但是没有树莓派的音频接口,底板也不能放到树莓派4B的官方外壳里,因为底板的背面有一个DSI接口,高度超出了。 二,开发板硬…

【QEMU中文文档】1.关于QEMU

本文由 AI 翻译(ChatGPT-4)完成,并由作者进行人工校对。如有任何问题或建议,欢迎联系我。联系方式:jelin-shoutlook.com。 QEMU 是一款通用的开源机器仿真器和虚拟化器。 QEMU 可以通过几种不同的方式使用。最常见的用…

每日一题《leetcode--LCR 021.删除链表的倒数第N个结点》

https://leetcode.cn/problems/SLwz0R/ 这道题我们可以设一个哨兵位,然后把要遍历链表的结点指向该哨兵位。最后用for循环将指针指向要删除结点的前一个。 struct ListNode* removeNthFromEnd(struct ListNode* head, int n){struct ListNode* dummy malloc(sizeof…

[数据集][目标检测]RSNA肺炎检测数据集VOC+YOLO格式6012张1类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):6012 标注数量(xml文件个数):6012 标注数量(txt文件个数):6012 标注…

一文读懂OrangePi AIpro体验测评与应用开发

文章目录 OrangePi AIpro 基本介绍AI边缘计算产品对比性能测试体验1. 点亮系统2. Jupyter远程启动测试官方例程3. 官方例程在OrangePi AIpro上的运行效果1. YOLOv5 目标检测2. OCR 识别3. ResNet50 物体检测4. 图像增强模型测试5 CartoonGAN 图像风格迁移 总结参考文档 OrangeP…

.NET SK 如何给AI大模型添加搜索引擎功能?

普通的AI大模型的数据都是在一开始训练的时候决定的&#xff0c;所以大模型的数据来源都可能存在时效性。 下面我们会利用SK插件来给AI大模型添加联网功能。 准备工作 创建一个名称为5_SK_Plugin_Web的控制台项目 复制以下代码到5_SK_Plugin_Web项目文件中 <Project Sdk…

taskENTER_CRITICAL()分析

1. 临界段代码 //任务级的临界段代码保护 taskENTER_CRITICAL() taskEXIT_CRITICAL()//中断级的临界段代码保护 taskENTER_CRITICAL_FROM_ISR() taskEXIT_CRITICAL_FROM_ISR()2. 以STM32为例 &#xff08;1&#xff09;STM32有0~15&#xff0c;共16级中断&#xff0c;可嵌套…

数据结构算法 数组的实现与练习(C语言实现,Java实现)

文章目录 数据结构数组(顺序表)特点使用Java实现更高级的数组C语言实现总结优点缺点 例题[26. 删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/)[1. 两数之和](https://leetcode.cn/problems/two-sum/)[27. 移除元素](https://lee…

教育数字展馆助力全球教育传播,科技引领数字化教育潮流

一、教育数字展馆助力教育传播 1、提高教育资源的可及性 教育数字展馆通过VR和WEB3D技术&#xff0c;将丰富的教育资源呈现在用户面前。不论是名校的经典课程&#xff0c;还是专家的精彩讲座&#xff0c;均可通过教育数字展馆实现线上展示。用户只需登录平台&#xff0c;即可…

【解决】Tree prefab at index 8 is missing.

开发平台&#xff1a;Unity 2020 版本以上   问题描述 翻译&#xff1a;树预制体集合中第8位预制体丢失。   解决方法&#xff1a;修复丢失树资产 关联 Unity Terrier 组件使用&#xff0c;前往 树绘制工作区&#xff0c;检查 “树资产” 引用是否丢失&#xff1f;删除或重…

Mysql基础教程(10):LIMIT

MySQL LIMIT 用法与实例 在 MySQL 中&#xff0c;我们使用 LIMIT 子句来限定 SELECT 语句返回的行的数量。 MySQL LIMIT语法 该 LIMIT 子句可用于限制 SELECT 语句返回的行数。 LIMIT 接受一个或两个非负数正数作为参数。 LIMIT 子句的语法如下&#xff1a; LIMIT [offset,…

CDH6.3.2集成Flink1.12.2

一、Linux下载httpd服务并开启 yum install y httpd systemctl start httpd systemctl enable httpd 二、获取已制作好的安装包 flink-1.12.2-bin-scala_2.11.tar ​ FLINK_ON_YARN-1.12.2.jar ​ flink-shaded-hadoop-2-uber-3.0.0-cdh6.3.2-10.0.jar 三、集成CM 1.上传编…

K210视觉识别模块学习笔记2:固件的下载升级_官方数字识别例程导入方法

今日开始学习K210视觉识别模块:固件的下载升级_官方数字识别例程导入方法 主要学习如何升级固件库&#xff0c;在哪下载固件库&#xff0c;以及如何在TF卡正确导入官方例程&#xff1a; 亚博智能的K210视觉识别模块...... 本次最终目的是正确导入官方的数字识别例程&#xff0…

[GESP202306 四级] 幸运数

按照题目做就OK了&#xff08;本蒟蒻写得太烂了&#xff09; #include<bits/stdc.h> using namespace std; long long w(long long n) {if(n1||n0){return n*7;}n*7;long long tsgn,s0;while(true){s0;while(tsg!0){stsg%10;tsg/10;}if(s<9){return s;}tsgs;} } bool…

可编程晶体振荡器应用于车载倒车雷达

倒车雷达&#xff0c;即“倒车防撞雷达”&#xff0c;又称“"停车辅助装置”&#xff0c;是汽车停车或倒车时的安全辅助装置。它主要由超声波传感器、控制器和显示器等组成&#xff0c;可以通过声音或更直观的显示告知驾驶员周围的障碍物&#xff0c;解除驾驶员在停车、倒…