A/B 업데이트 구현

A/B 시스템 업데이트를 구현하려는 OEM 및 SoC는 부트로더가 boot_control HAL을 구현하고 올바른 매개변수를 커널에 전달하는지 확인해야 합니다.

부팅 제어 HAL 구현

A/B 지원 부트로더는 hardware/libhardware/include/hardware/boot_control.h에서 boot_control HAL을 구현해야 합니다. 구현은 system/extras/bootctl 유틸리티 및 system/extras/tests/bootloader/를 사용하여 테스트할 수 있습니다.

또한 아래 표시된 상태 시스템을 구현해야 합니다.

그림 1. 부트로더 상태 머신

커널 설정

A/B 시스템 업데이트를 구현하는 방법은 다음과 같습니다.

  1. 다음과 같은 패치 시리즈를 선택합니다(필요한 경우).
  2. 커널 명령줄 인수에 다음과 같은 추가 인수가 포함되어 있는지 확인합니다.
        skip_initramfs rootwait ro init=/init root="/dev/dm-0 dm=system none ro,0 1 android-verity <public-key-id> <path-to-system-partition>"
    여기서 <public-key-id> 값은 verity 표 서명을 확인하는 데 사용된 공개 키의 ID입니다(자세한 내용은 dm-verity 참조).
  3. 공개 키가 포함된 X509 인증서를 시스템 키링에 추가합니다.
    1. 형식이 .der로 지정된 X509 인증서를 kernel 디렉터리의 루트에 복사합니다. 만약 X509 인증서가 .pem 파일로 형식이 지정된 경우 다음 openssl 명령어를 사용하여 .pem.der 형식으로 변환합니다.
          openssl x509 -in <x509-pem-certificate> -outform der -out <x509-der-certificate>
    2. 인증서를 시스템 키링의 일부로 포함하도록 zImage를 빌드합니다. 검증하려면 procfs 항목을 확인합니다(KEYS_CONFIG_DEBUG_PROC_KEYS를 사용 설정해야 함).
          angler:/# cat /proc/keys
      
          1c8a217e I------     1 perm 1f010000     0     0 asymmetri
          Android: 7e4333f9bba00adfe0ede979e28ed1920492b40f: X509.RSA 0492b40f []
          2d454e3e I------     1 perm 1f030000     0     0 keyring
          .system_keyring: 1/4
      정상적으로 X509 인증서가 포함되었다면 이는 시스템 키링에 공개 키가 존재한다는 의미입니다(강조표시된 부분이 공개 키 ID를 나타냄).
    3. 공백을 #으로 교체하고 이를 커널 명령줄에 <public-key-id>로 전달합니다. 예를 들면 <public-key-id> 자리에 Android:#7e4333f9bba00adfe0ede979e28ed1920492b40f를 전달합니다.

빌드 변수 설정

A/B 지원 부트로더는 다음과 같은 빌드 변수 기준을 충족해야 합니다.

A/B 타겟에 대해 정의해야 함
  • AB_OTA_UPDATER := true
  • AB_OTA_PARTITIONS := \
      boot \
      system \
      vendor
    update_engine을 통해 업데이트된 기타 파티션(라디오, 부트로더 등)
  • TARGET_NO_RECOVERY := true
  • BOARD_USES_RECOVERY_AS_BOOT := true
  • PRODUCT_PACKAGES += \
      update_engine \
      update_verifier
예시는 /device/google/marlin/+/android-7.1.0_r1/device-common.mk를 참조하세요. 원하는 경우 컴파일에 설명된 설치 후(재부팅 전) dex2oat 단계를 수행할 수도 있습니다.
A/B 타겟에 대해 정의할 수 없음
  • BOARD_RECOVERYIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_PARTITION_SIZE
  • BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE
디버그 빌드 관련 선택사항 PRODUCT_PACKAGES_DEBUG += update_engine_client

파티션 설정(슬롯)

A/B 기기에는 복구 파티션이나 캐시 파티션이 필요없습니다. 이러한 파티션은 Android에서 더 이상 사용되지 않습니다. 이제 데이터 파티션이 다운로드된 OTA 패키지에 사용되며, 복구 이미지 코드는 부팅 파티션에 위치합니다. A/B에 적용된 모든 파티션은 boot_a, boot_b, system_a, system_b, vendor_a, vendor_b로 이름이 지정되어야 합니다(슬롯은 항상 a, b 등으로 이름이 지정됨).

캐시

비 A/B 업데이트의 경우에는 업데이트를 적용하는 동안 다운로드된 OTA 패키지를 저장하고 블록을 보관하기 위해 캐시 파티션이 사용되었습니다. 캐시 파티션의 크기를 지정하기 위한 좋은 방법은 없었습니다. 이 파티션의 크기는 어떤 업데이트를 적용하고 싶은지에 따라 달랐습니다. 최악은 파티션이 시스템 이미지 만큼이나 큰 경우입니다. A/B 업데이트의 경우 블록을 보관할 필요가 없으며(항상 현재 사용되지 않는 파티션에 쓰기 때문) A/B 스트리밍의 경우 적용 전에 전체 OTA 패키지를 다운로드할 필요가 없습니다.

복구

복구 램디스크는 이제 boot.img 파일에 포함됩니다. 복구에 돌입하면 부트로더는 커널 명령줄에 skip_initramfs 옵션을 배치할 수 없습니다.

비 A/B 업데이트의 경우 복구 파티션에 업데이트 적용에 사용되는 코드가 포함됩니다. A/B 업데이트는 일반 방식으로 부팅된 시스템 이미지에서 실행되는 update_engine에 의해 적용됩니다. 공장 데이터 초기화 구현 및 업데이트 패키지 사이드로드에 사용되는 복구 모두가 여전히 남아 있습니다(여기서 '복구'라는 이름이 유래됨). 복구 모두의 코드와 데이터는 램디스크의 일반 부팅 파티션에 저장됩니다. 시스템 이미지로 부팅하기 위해 부트로더는 커널에 램디스크를 건너뛰도록 지시합니다(그렇지 않으면 기기가 복구 모드로 부팅됨). 복구 모드는 용량이 작고 대부분이 이미 부팅 파티션에 있었으므로 부팅 파티션으로 인해 크기가 늘어나지 않습니다.

Fstab

slotselect 인수는 A/B가 적용된 파티션의 행에 위치해야 합니다. 예:

    <path-to-block-device>/vendor  /vendor  ext4  ro
    wait,verify=<path-to-block-device>/metadata,slotselect
    

어떠한 파티션도 이름을 vendor로 지정하면 안 됩니다. 대신 파티션 vendor_a 또는 vendor_b가 선택되어 /vendor 마운트 지점에 마운트됩니다.

커널 슬롯 인수

현재의 슬롯 접미사는 특정 기기 트리(DT) 노드(/firmware/android/slot_suffix) 또는 androidboot.slot_suffix 명령줄 인수를 통해 전달되어야 합니다.

기본적으로 fastboot는 A/B 기기의 현재 슬롯을 플래시합니다. 업데이트 패키지에도 현재 슬롯이 아닌 다른 슬롯이 포함되면 fastboot가 이러한 이미지까지 플래시합니다. 사용 가능한 옵션은 다음과 같습니다.

  • --slot SLOT. 기본 동작을 재정의하고 fastboot가 인수로 전달된 슬롯을 플래시하도록 요청합니다.
  • --set-active [SLOT]. 슬롯을 활성으로 설정합니다. 선택적 인수가 지정되지 않은 경우 현재 슬롯이 활성으로 설정됩니다.
  • fastboot --help. 명령어에 대한 세부정보를 가져옵니다.

fastboot를 구현한 부트로더는 현재의 활성 슬롯을 지정된 슬롯으로 설정하는 명령어 set_active <slot>을 지원해야 합니다(이 경우 해당 슬롯에 대해 부팅할 수 없는 플래그가 제거되고 재시도 횟수가 기본값으로 재설정되어야 함). 부트로더는 다음과 같은 변수도 지원해야 합니다.

  • has-slot:<partition-base-name-without-suffix>. 파티션이 슬롯을 지원하면 'yes'를, 지원하지 않으면 'no'를 반환합니다.
  • current-slot. 다음에서 부팅되는 슬롯 접미사를 반환합니다.
  • slot-count. 가용한 슬롯 개수를 나타내는 정수를 반환합니다. 현재는 두 개의 슬롯이 지원되므로 이 값은 2입니다.
  • slot-successful:<slot-suffix>. 슬롯이 정상 부팅으로 표시되면 'yes'를, 그렇지 않은 경우에는 'no'를 반환합니다.
  • slot-unbootable:<slot-suffix>. 슬롯이 부팅 불가로 표시되면 'yes'를, 그렇지 않은 경우에는 'no'를 반환합니다.
  • slot-retry-count. 남은 슬롯 부팅 재시도 횟수입니다.

모든 변수를 보려면 fastboot getvar all을 실행합니다.

OTA 패키지 생성

OTA 패키지 도구는 비 A/B 기기와 동일한 명령어를 따릅니다. target_files.zip 파일은 A/B 타겟의 빌드 변수를 정의하여 생성해야 합니다. OTA 패키지 도구는 A/B 업데이터 관련 형식의 패키지를 자동으로 식별하고 생성합니다.

예:

  • 전체 OTA를 생성하려면 다음을 실행합니다.
        ./build/tools/releasetools/ota_from_target_files \
          dist_output/tardis-target_files.zip ota_update.zip
        
  • 증분 OTA를 생성하려면 다음을 실행합니다.
        ./build/tools/releasetools/ota_from_target_files \
          -i PREVIOUS-tardis-target_files.zip \
          dist_output/tardis-target_files.zip incremental_ota_update.zip
        

파티션 구성

update_engine는 같은 디스크에 정의된 모든 A/B 파티션 쌍을 업데이트할 수 있습니다. 파티션 쌍에는 공통 접두사(예: system 또는 boot)와 슬롯별 접미사(예: _a)가 있습니다. 페이로드 생성기가 업데이트를 정의하는 파티션 목록은 AB_OTA_PARTITIONS 생성 변수에 의해 구성됩니다.

예를 들어 파티션 bootloader_abooloader_b의 쌍이 포함되면(_a_b는 슬롯 접미사) 다음을 제품이나 보드 구성에 지정하여 이러한 파티션을 업데이트할 수 있습니다.

    AB_OTA_PARTITIONS := \
      boot \
      system \
      bootloader
    

update_engine에 의해 업데이트된 모든 파티션은 시스템의 나머지 부분에 의해 수정되어야 합니다. 증분 또는 델타 업데이트 도중이는 현재 슬롯의 바이너리 데이터가 새 슬롯에 데이터를 생성하는 데 사용됩니다. 모든 수정사항은 업데이트 프로세스 도중에 새 슬롯 데이터가 인증에 실패하는 원인이 될 수 있으므로 업데이트가 실패합니다.

설치 후 구성

키-값 쌍 집합을 사용하여 업데이트된 각 파티션에 대해 설치 후 단계를 다르게 구성할 수 있습니다. 새 이미지의 /system/usr/bin/postinst에 위치한 프로그램을 실행하려면 시스템 파티션의 파일 시스템 루트에 대한 경로를 지정합니다.

예를 들어 usr/bin/postinstsystem/usr/bin/postinst입니다(램디스크를 사용하지 않는 경우). 또한 파일 시스템 유형을 지정하여 mount(2) 시스템 호출로 전달합니다. 다음을 제품 또는 기기 .mk 파일에 추가합니다(적용 가능한 경우).

    AB_OTA_POSTINSTALL_CONFIG += \
      RUN_POSTINSTALL_system=true \
      POSTINSTALL_PATH_system=usr/bin/postinst \
      FILESYSTEM_TYPE_system=ext4
    

컴파일

보안 상의 이유로 system_serverJIT(just-in-time) 컴파일을 사용할 수 없습니다. 즉, system_server의 사전 odex 파일을 컴파일하고 종속성을 최소로 지정해야 합니다. 다른 모든 부분은 선택사항입니다.

배경에서 앱을 컴파일하려면 다음을 제품의 기기 구성(제품의 device.mk)에 추가해야 합니다.

  1. 빌드에 기본 구성요소를 포함하여 컴파일 스크립트 및 바이너리가 컴파일되고 시스템 이미지에 포함되도록 합니다.
          # A/B OTA dexopt package
          PRODUCT_PACKAGES += otapreopt_script
        
  2. 실치 후 단계로 실행되도록 컴파일 스크립트를 update_engine에 연결합니다.
          # A/B OTA dexopt update_engine hookup
          AB_OTA_POSTINSTALL_CONFIG += \
            RUN_POSTINSTALL_system=true \
            POSTINSTALL_PATH_system=system/bin/otapreopt_script \
            FILESYSTEM_TYPE_system=ext4 \
            POSTINSTALL_OPTIONAL_system=true
        

사전 선택된 파일을 사용하지 않는 두 번째 시스템 파티션에 설치하는 방법은 DEX_PREOPT 파일의 최초 부팅 설치를 참조하세요.