A/B系统是Google推的一种方便OTA升级版本的特性,好像是N就开始了。之前有好几个问题都是异常进recovery的,不过A/B系统里已经去掉recovery分区了,不再编译recovery.img,那系统是怎么进的recovery?带着这个问题来看下。

先看看Google对recovery的介绍:

recovery: The recovery partition stores the recovery image, booted during the OTA process. If the device supports A/B updates, recovery can be a RAM disk contained in the boot image rather than a separate image.

说的很清楚,recovery已经不作为独立的image存在,而是包含在boot image里。

看下makefile:

ifeq ($(ENABLE_AB), true)
# A/B related defines
AB_OTA_UPDATER := true
# Full A/B partiton update set
# AB_OTA_PARTITIONS := xbl rpm tz hyp pmic modem abl boot keymaster cmnlib cmnlib64 system bluetooth
# Subset A/B partitions for Android-only image update
AB_OTA_PARTITIONS ?= boot system
BOARD_BUILD_SYSTEM_ROOT_IMAGE := true
TARGET_NO_RECOVERY := true
BOARD_USES_RECOVERY_AS_BOOT := true
else
BOARD_RECOVERYIMAGE_PARTITION_SIZE := 0x04000000
BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4
BOARD_CACHEIMAGE_PARTITION_SIZE := 268435456
TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_msm
# Enable System As Root even for non-A/B from P onwards
BOARD_BUILD_SYSTEM_ROOT_IMAGE := true
endif

A/B系统定义了TARGET_NO_RECOVERY去recovery,BOARD_USES_RECOVERY_AS_BOOT意思就是build recovery作为boot image.

再看看这个宏的管控下都做了什么:

ifeq (,$(filter true, $(TARGET_NO_KERNEL) $(TARGET_NO_RECOVERY)))
INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
else
INSTALLED_RECOVERYIMAGE_TARGET :=
endif
...
# -----------------------------------------------------------------
# Recovery image

# Recovery image exists if we are building recovery, or building recovery as boot.
ifneq (,$(INSTALLED_RECOVERYIMAGE_TARGET)$(filter true,$(BOARD_USES_RECOVERY_AS_BOOT)))

有独立的recovery.img or BOARD_USES_RECOVERY_AS_BOOT=true都进来。

继续看:

INTERNAL_RECOVERYIMAGE_FILES := $(filter $(TARGET_RECOVERY_OUT)/%, \
    $(ALL_DEFAULT_INSTALLED_MODULES))
TARGET_COPY_OUT_RECOVERY := recovery
...
TARGET_RECOVERY_OUT := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_RECOVERY)
TARGET_RECOVERY_ROOT_OUT := $(TARGET_RECOVERY_OUT)/root

obj下能看到recovery目录,里面只有root。

继续看:

recovery_kernel := $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system
recovery_ramdisk := $(PRODUCT_OUT)/ramdisk-recovery.img

recovery_kernel用的就是那kernel:

ifneq ($(strip $(TARGET_NO_KERNEL)),true)
  INSTALLED_KERNEL_TARGET := $(PRODUCT_OUT)/kernel
else
  INSTALLED_KERNEL_TARGET :=
endif

继续看:

INTERNAL_RECOVERYIMAGE_ARGS := \
        $(addprefix --second ,$(INSTALLED_2NDBOOTLOADER_TARGET)) \
        --kernel $(recovery_kernel) \
        --ramdisk $(recovery_ramdisk)

# Assumes this has already been stripped
ifdef INTERNAL_KERNEL_CMDLINE
  INTERNAL_RECOVERYIMAGE_ARGS += --cmdline "$(INTERNAL_KERNEL_CMDLINE)"
endif
ifdef BOARD_KERNEL_BASE
  INTERNAL_RECOVERYIMAGE_ARGS += --base $(BOARD_KERNEL_BASE)
endif
ifdef BOARD_KERNEL_PAGESIZE
  INTERNAL_RECOVERYIMAGE_ARGS += --pagesize $(BOARD_KERNEL_PAGESIZE)
endif
ifdef BOARD_INCLUDE_RECOVERY_DTBO
  INTERNAL_RECOVERYIMAGE_ARGS += --recovery_dtbo $(BOARD_PREBUILT_DTBOIMAGE)
endif

定义了许多参数, 接下来进入重要时刻, 咳咳:

# $(1): output file
define build-recoveryimage-target
  # Making recovery image
...

这里有许多hide你可以放开看看。是谁在call build-recoveryimage-target?在下面:

NSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
...
ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
...
$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) $(ADBD) \
                $(INSTALLED_RAMDISK_TARGET) \
                $(INTERNAL_RECOVERYIMAGE_FILES) \
                $(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \
                $(INSTALLED_2NDBOOTLOADER_TARGET) \
                $(recovery_build_props) $(recovery_resource_deps) \
                $(recovery_fstab) \
                $(RECOVERY_INSTALL_OTA_KEYS) \
                $(INSTALLED_VENDOR_DEFAULT_PROP_TARGET) \
                $(BOARD_RECOVERY_KERNEL_MODULES) \
                $(DEPMOD)
                $(call pretty,"Target boot image from recovery: $@")
                $(call build-recoveryimage-target, $@)
endif

接下来的基本上是给独立image用的了:

$(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) $(ADBD) \
                $(INSTALLED_RAMDISK_TARGET) \
                $(INSTALLED_BOOTIMAGE_TARGET) \
                $(INTERNAL_RECOVERYIMAGE_FILES) \
                $(recovery_initrc) $(recovery_sepolicy) $(recovery_kernel) \
                $(INSTALLED_2NDBOOTLOADER_TARGET) \
                $(recovery_build_props) $(recovery_resource_deps) \
                $(recovery_fstab) \
                $(RECOVERY_INSTALL_OTA_KEYS) \
                $(INSTALLED_VENDOR_DEFAULT_PROP_TARGET) \
                $(BOARD_RECOVERY_KERNEL_MODULES) \
                $(DEPMOD)
                $(call build-recoveryimage-target, $@)

ifdef RECOVERY_RESOURCE_ZIP
$(RECOVERY_RESOURCE_ZIP): $(INSTALLED_RECOVERYIMAGE_TARGET) | $(ZIPTIME)
        $(hide) mkdir -p $(dir $@)
        $(hide) find $(TARGET_RECOVERY_ROOT_OUT)/res -type f | sort | zip -0qrjX $@ -@
        $(remove-timestamps-from-package)
endif

.PHONY: recoveryimage-nodeps
recoveryimage-nodeps:
        @echo "make $@: ignoring dependencies"
        $(call build-recoveryimage-target, $(INSTALLED_RECOVERYIMAGE_TARGET))

else # INSTALLED_RECOVERYIMAGE_TARGET not defined
RECOVERY_RESOURCE_ZIP :=
endif

.PHONY: recoveryimage
recoveryimage: $(INSTALLED_RECOVERYIMAGE_TARGET) $(RECOVERY_RESOURCE_ZIP)

那bootable/recovery下的代码是编译成什么的了?

来看Android.mk:

# recovery (static executable)
# ===============================
include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
    adb_install.cpp \
    device.cpp \
    fuse_sdcard_provider.cpp \
    recovery.cpp \
    roots.cpp \
    rotate_logs.cpp \
    screen_ui.cpp \
    ui.cpp \
    vr_ui.cpp \
    wear_ui.cpp \

LOCAL_MODULE := recovery

恢复模式的"Can't load Android System"就在recovery.cpp里,很明显是编译成recovery可执行文件,具体在obj目录能找到:

./recovery/root/sbin/recovery

应该是放到recovery_ramdisk里了,这个ramdisk是怎么生成了的,其实就是上面提到的build-recoveryimage-target:

TARGET_RECOVERY_ROOT_OUT := $(TARGET_RECOVERY_OUT)/root
...
$(hide) $(MKBOOTFS) -d $(TARGET_OUT) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) > $(recovery_ramdisk)

可见TARGET_RECOVERY_ROOT_OUT就是ramdisk的来源。

very clear, ehe...