가상 A/B 구현

새 기기에 가상 A/B를 구현하거나 출시된 기기를 재구성하려면 기기별 코드를 변경해야 합니다.

빌드 플래그

가상 A/B를 사용하는 기기는 A/B 기기로 구성되어야 하고 동적 파티션으로 출시해야 합니다.

가상 A/B로 출시되는 기기는 가상 A/B 기기 기본 구성을 상속하도록 설정하세요.

$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota.mk)

가상 A/B로 출시되는 기기에는 BOARD_SUPER_PARTITION_SIZE의 보드 크기 절반만 필요합니다. B 슬롯이 더 이상 super에 있지 않기 때문입니다. 즉, BOARD_SUPER_PARTITION_SIZE합계(업데이트 그룹 크기) + 오버헤드보다 크거나 이와 같아야 하고 결과적으로 합계(파티션 크기) + 오버헤드보다 크거나 이와 같아야 합니다.

Android 13 이상에서 가상 A/B로 압축된 스냅샷을 사용 설정하려면 다음 기본 구성을 상속해야 합니다.

$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota/android_t_baseline.mk)

이렇게 하면 노옵스(no-ops) 압축 메서드를 사용하는 동안 가상 A/B로 사용자 공간 스냅샷을 사용할 수 있습니다. 그런 다음 지원되는 메서드인 gzbrotli 중 하나로 압축 메서드를 구성할 수 있습니다.

PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD := gz

Android 12의 경우 가상 A/B로 압축된 스냅샷을 사용 설정하려면 다음 기본 구성을 상속해야 합니다.

$(call inherit-product, $(SRC_TARGET_DIR)/product/generic_ramdisk.mk)
$(call inherit-product, \
    $(SRC_TARGET_DIR)/product/virtual_ab_ota/compression.mk)

XOR 압축

Android 13 이상으로 업그레이드하는 기기에서는 기본적으로 XOR 압축 기능이 사용 설정되지 않습니다. XOR 압축을 사용 설정하려면 기기의 .mk 파일에 다음을 추가합니다.

PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.compression.xor.enabled=true

android_t_baseline.mk에서 상속한 기기에서는 XOR 압축이 기본적으로 사용 설정됩니다.

사용자 공간 병합

Android 13 이상으로 업그레이드하는 기기에서는 기본적으로 기기 매퍼 레이어에 설명된 사용자 공간 병합 프로세스가 사용 설정되지 않습니다. 사용자 공간 병합을 사용 설정하려면 기기의 .mk 파일에 다음 줄을 추가합니다.

PRODUCT_VENDOR_PROPERTIES += ro.virtual_ab.userspace.snapshots.enabled=true

13 이상으로 출시되는 기기에서는 기본적으로 사용자 공간 병합이 사용 설정됩니다.

부팅 제어 HAL

부팅 제어 HAL은 OTA 클라이언트가 부팅 슬롯을 제어할 수 있는 인터페이스를 제공합니다. 가상 A/B에는 부팅 제어 HAL의 마이너 버전 업그레이드가 필요합니다. 플래시 및 초기화 중에 부트로더를 보호하기 위해서는 추가 API가 필요하기 때문입니다. 최신 버전의 HAL 정의는 IBootControl.haltypes.hal을 참고하세요.

// hardware/interfaces/boot/1.1/types.hal
enum MergeStatus : uint8_t {
    NONE, UNKNOWN, SNAPSHOTTED, MERGING, CANCELLED };

// hardware/interfaces/boot/1.1/IBootControl.hal
package android.hardware.boot@1.1;
interface IBootControl extends @1.0::IBootControl {
    setSnapshotMergeStatus(MergeStatus status)
        generates (bool success);
    getSnapshotMergeStatus()
        generates (MergeStatus status);
}
// Recommended implementation

Return<bool> BootControl::setSnapshotMergeStatus(MergeStatus v) {
    // Write value to persistent storage
    // e.g. misc partition (using libbootloader_message)
    // bootloader rejects wipe when status is SNAPSHOTTED
    // or MERGING
}

fstab 변경사항

메타데이터 파티션의 무결성은 특히 OTA 업데이트가 적용된 직후 부팅 프로세스에 매우 중요합니다. 따라서 메타데이터 파티션을 first_stage_init가 마운트하기 전에 확인해야 합니다. 이렇게 하려면 /metadata의 항목에 check fs_mgr 플래그를 추가합니다. 다음 예를 참고하세요.

/dev/block/by-name/metadata /metadata ext4 noatime,nosuid,nodev,discard,sync wait,formattable,first_stage_mount,check

커널 요구사항

스냅샷 기능을 사용 설정하려면 CONFIG_DM_SNAPSHOTtrue로 설정합니다.

F2FS를 사용하는 기기의 경우 f2fs: export FS_NOCOW_FL flag to user 커널 패치를 포함하여 파일 고정 문제를 수정합니다. f2fs: support aligned pinned file 커널 패치도 포함하세요.

가상 A/B는 커널 버전 4.3에 추가된 기능(snapshotsnapshot-merge 타겟의 overflow 상태 비트)을 사용합니다. Android 9 이상으로 출시되는 모든 기기에는 이미 커널 버전 4.4 이상이 있습니다.

압축된 스냅샷을 사용 설정하는 데 필요한 최소 커널 버전은 4.19입니다. CONFIG_DM_USER=m 또는 CONFIG_DM_USER=y를 설정합니다. 전자(모듈)를 사용하는 경우, 모듈이 1단계 램디스크에 로드되어야 합니다. 이렇게 하려면 기기 Makefile에 다음 행을 추가하면 됩니다.

BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko

Android 11로 업그레이드하는 기기의 재구성

Android 11로 업그레이드할 때 동적 파티션으로 출시된 기기는 선택적으로 가상 A/B를 재구성할 수 있습니다. 업데이트 프로세스는 가상 A/B로 출시되는 기기와 거의 동일하지만 사소한 차이점이 있습니다.

  • COW 파일 위치: 출시 기기의 경우 OTA 클라이언트는 /data의 공간을 사용하기 전에 super 파티션에서 사용 가능한 모든 빈 공간을 사용합니다. 재구성 기기의 경우 super 파티션에 항상 공간이 충분하므로 COW 파일이 /data에서 생성되지 않습니다.

  • 빌드 시간 기능 플래그: 가상 A/B를 재구성하는 기기의 경우 PRODUCT_VIRTUAL_AB_OTAPRODUCT_VIRTUAL_AB_OTA_RETROFIT이 모두 다음과 같이 true로 설정됩니다.

    (call inherit-product, \
        (SRC_TARGET_DIR)/product/virtual_ab_ota_retrofit.mk)
    
  • super 파티션 크기: 가상 A/B로 출시되는 기기는 BOARD_SUPER_PARTITION_SIZE를 절반으로 줄일 수 있습니다. B 슬롯이 super 파티션에 있지 않기 때문입니다. 가상 A/B를 재구성하는 기기는 이전 super 파티션 크기를 유지하므로 BOARD_SUPER_PARTITION_SIZE2 * 합계(업데이트 그룹 크기) + 오버헤드보다 크거나 이와 같고 결과적으로 2 * 합계(파티션 크기) + 오버헤드보다 크거나 이와 같습니다.

부트로더 변경사항

업데이트의 병합 단계에서 /data는 Android OS의 전체 인스턴스만 보유합니다. 이전이 시작되면 네이티브 system, vendorproduct 파티션은 복사가 완료될 때까지 불완전합니다. 이 프로세스 중에 기기가 복구나 시스템 설정 대화상자를 통해 초기화되면 기기를 부팅할 수 없습니다.

/data를 삭제하기 전에 기기 상태에 따라 복구나 롤백에서 병합을 완료하세요.

  • 새 빌드가 이전에 성공적으로 부팅되었다면 이전을 완료합니다.
  • 그렇지 않으면 이전 슬롯으로 롤백합니다.
    • 동적 파티션의 경우 이전 상태로 롤백합니다.
    • 정적 파티션의 경우 활성 슬롯을 이전 슬롯으로 설정합니다.

부트로더와 fastbootd는 모두 기기가 잠금 해제되면 /data 파티션을 삭제할 수 있습니다. fastbootd를 사용하면 이전을 강제로 완료할 수 있지만 부트로더를 사용하면 강제로 완료할 수 없습니다. 부트로더는 병합이 진행 중인지 또는 /data의 어떤 블록이 OS 파티션을 구성하는지 알 수 없습니다. 기기는 다음을 실행하여 사용자가 인지하지 못하는 상태에서 기기를 벽돌화(사용하지 못하는 상태)하지 않도록 해야 합니다.

  1. 부트로더가 setSnapshotMergeStatus() 메서드로 설정한 값을 읽을 수 있도록 부팅 제어 HAL을 구현합니다.
  2. 병합 상태가 MERGING이거나, 병합 상태가 SNAPSHOTTED이고 슬롯이 새로 업데이트된 슬롯으로 변경된 경우, userdata, metadata 또는 병합 상태를 저장하는 파티션을 완전히 삭제해 달라는 요청이 부트로더에서 거부됩니다.
  3. 사용자가 이 보호 메커니즘을 우회하고 싶다고 부트로더에 알릴 수 있도록 fastboot snapshot-update cancel 명령어를 구현합니다.
  4. 맞춤 플래시 도구나 스크립트를 수정하여 전체 기기를 플래시할 때 fastboot snapshot-update cancel을 실행합니다. 전체 기기를 플래시하면 OTA가 삭제되므로 실행해도 안전합니다. 도구는 fastboot getvar snapshot-update-status를 구현하여 런타임 시 이 명령어를 감지할 수 있습니다. 이 명령어는 오류 조건을 구별하는 데 도움이 됩니다.

struct VirtualAbState {
    uint8_t StructVersion;
    uint8_t MergeStatus;
    uint8_t SourceSlot;
};

bool ShouldPreventUserdataWipe() {
    VirtualAbState state;
    if (!ReadVirtualAbState(&state)) ...
    return state.MergeStatus == MergeStatus::MERGING ||
           (state.MergeStatus == MergeStatus::SNAPSHOTTED &&
            state.SourceSlot != CurrentSlot()));
}

빠른 부팅 도구 변경사항

Android 11은 빠른 부팅 프로토콜을 다음과 같이 변경합니다.

  • getvar snapshot-update-status - 부팅 제어 HAL이 부트로더에 전달한 값을 반환합니다.
    • 상태가 MERGING이면 부트로더는 merging을 반환해야 합니다.
    • 상태가 SNAPSHOTTED이면 부트로더는 snapshotted를 반환해야 합니다.
    • 그 외의 경우에는 부트로더가 none을 반환해야 합니다.
  • snapshot-update merge - 병합 작업을 완료하여 필요에 따라 복구/fastbootd로 부팅합니다. 이 명령어는 snapshot-update-statusmerging인 경우에만 유효하며 fastbootd에서만 지원됩니다.
  • snapshot-update cancel - 부팅 제어 HAL의 병합 상태를 CANCELLED로 설정합니다. 이 명령어는 기기가 잠겨 있으면 유효하지 않습니다.
  • erase 또는 wipe - metadata, userdata 또는 부팅 제어 HAL의 병합 상태를 보유하는 파티션의 erase 또는 wipe는 스냅샷 병합 상태를 확인해야 합니다. 상태가 MERGING 또는 SNAPSHOTTED이면 기기는 작업을 취소해야 합니다.
  • set_active - 활성 슬롯을 변경하는 set_active 명령어는 스냅샷 병합 상태를 확인해야 합니다. 상태가 MERGING이면 기기는 작업을 취소해야 합니다. 슬롯은 SNAPSHOTTED 상태에서 안전하게 변경할 수 있습니다.

이러한 변경은 실수로 기기를 부팅할 수 없게 만드는 것을 방지하기 위한 것이지만 자동화된 도구에 지장을 줄 수 있습니다. 명령어가 모든 파티션을 플래시하는 구성요소로 사용될 때(예: fastboot flashall 실행) 다음 흐름을 사용하는 것이 좋습니다.

  1. getvar snapshot-update-status를 쿼리합니다.
  2. merging 또는 snapshotted이면 snapshot-update cancel을 실행합니다.
  3. 플래시 단계를 진행합니다.

저장소 요구사항 줄이기

전체 A/B 저장소가 super에 할당되지 않고 필요에 따라 /data를 사용할 것으로 예상되는 기기는 블록 매핑 도구를 사용하는 것이 좋습니다. 블록 매핑 도구는 블록 할당을 빌드 간에 일관되게 유지하여 스냅샷에 불필요하게 쓰는 작업을 줄입니다. OTA 크기 줄이기에 설명되어 있습니다.

OTA 압축 메서드

OTA 패키지는 다양한 성능 측정항목에 맞게 조정할 수 있습니다. Android는 현재 몇 가지 지원되는 압축 메서드(gz, lz4, none)를 제공하며 이는 설치 시간, cow 공간 사용, 부팅 시간, 스냅샷 병합 시간 간에 절충합니다. 압축을 사용하는 가상 ab에 사용 설정된 기본 옵션은 gz compression method입니다. (참고: 압축 방식 간의 상대적 성능은 기기에 따라 달라질 수 있는 CPU 속도와 저장 처리량의 영향을 받습니다. 아래에서 생성된 모든 OTA 패키지는 PostInstall이 사용 중지되어 있으므로 부팅 시간이 약간 늘어납니다. 압축하지 않은 전체 OTA의 총 동적 파티션 크기4.81GB입니다.)

1. Pixel 6 Pro의 증분 OTA

PostInstall 단계가 없는 설치 시간 cow 공간 사용 OTA 후 부팅 시간 스냅샷 병합 시간
gz 24분 1.18GB 40.2초 45.5초
lz4 13분 1.49GB 37.4초 37.1초
압축하지 않음 13분 2.90GB 37.6초 40.7초

2. Pixel 6 Pro의 전체 OTA

PostInstall 단계가 없는 설치 시간 cow 공간 사용 OTA 후 부팅 시간 스냅샷 병합 시간
gz 23분 2.79GB 24.9초 41.7초
lz4 12분 3.46GB 20.0초 25.3초
압축하지 않음 10분 4.85GB 20.6초 29.8초