가상 A/B 개요

Android에는 A/B(원활한) 업데이트와 비 A/B 업데이트, 이렇게 두 가지 업데이트 메커니즘이 있습니다. 코드 복잡성을 줄이고 업데이트 프로세스를 개선하기 위해 Android 11에서는 두 가지 메커니즘이 가상 A/B를 통해 통합되어 최소한의 저장용량 비용으로 모든 기기에 원활한 업데이트를 제공합니다. Android 12는 스냅샷 처리된 파티션을 압축하는 가상 A/B 압축 옵션을 제공합니다. 다음은 Android 11과 Android 12에 모두 적용됩니다.

  • 가상 A/B 업데이트는 A/B 업데이트처럼 원활합니다. 가상 A/B 업데이트는 기기가 오프라인 상태이고 사용할 수 없는 시간을 최소화합니다.
  • 가상 A/B 업데이트는 롤백될 수 있습니다. 새 OS가 부팅되지 않으면 기기가 자동으로 이전 버전으로 롤백합니다.
  • 가상 A/B 업데이트는 부트로더에서 사용하는 파티션만 복제하여 최소한의 추가 공간을 사용합니다. 업데이트 가능한 다른 파티션은 스냅샷 처리됩니다.

배경 및 용어

이 섹션에서는 용어를 정의하고 가상 A/B를 지원하는 기술을 설명합니다.

기기 매퍼

기기 매퍼는 Android에서 자주 사용되는 Linux 가상 블록 레이어입니다. 동적 파티션을 사용하면 /system과 같은 파티션이 계층화된 기기의 스택입니다.

  • 스택 맨 아래에는 실제 super 파티션(예: /dev/block/by-name/super)이 있습니다.
  • 중간에는 dm-linear 기기가 있어서 super 파티션에서 어떤 블록이 특정 파티션을 형성할지 지정합니다. 이는 A/B 기기에서는 /dev/block/mapper/system_[a|b]로, 비 A/B 기기에서는 /dev/block/mapper/system으로 표시됩니다.
  • 상단에는 확인된 파티션을 위해 만들어진 dm-verity 기기가 있습니다. 이 기기는 dm-linear 기기의 블록이 올바르게 서명되었는지 확인합니다. /dev/block/mapper/system-verity로 표시되며, /system 마운트 지점의 소스입니다.

그림 1은 /system 마운트 지점 아래의 스택 모양을 보여줍니다.

시스템 아래의 파티션 스택

그림 1. /system 마운트 지점 아래의 스택

dm-snapshot

가상 A/B는 저장소 기기의 상태를 스냅샷 처리하는 기기 매퍼 모듈인 dm-snapshot을 사용합니다. dm-snapshot을 사용할 때는 다음 네 가지 기기가 작동합니다.

  • 기본 기기: 스냅샷 처리되는 기기입니다. 이 페이지에서 기본 기기는 항상 동적 파티션(예: 시스템 또는 공급업체)입니다.
  • 기록 중 복사(COW) 기기: 기본 기기에 변경사항을 로깅합니다. 크기에 제한은 없지만 기본 기기의 모든 변경사항을 수용할 만큼 커야 합니다.
  • 스냅샷 기기: snapshot 타겟을 사용하여 만들어집니다. 스냅샷 기기에 쓰면 COW 기기에 기록됩니다. 스냅샷 기기에서 읽기는 액세스되는 데이터가 스냅샷으로 인해 변경되었는지에 따라 기본 기기나 COW 기기에서 판독됩니다.
  • 원본 기기: snapshot-origin 타겟을 사용하여 만들어집니다. 원본 기기에서 읽으면 기본 기기에서 직접 판독됩니다. 원본 기기에 쓰면 기본 기기에 직접 기록되지만 원본 데이터는 COW 기기에 써서 백업됩니다.

dm-snapshot의 기기 매핑

그림 2. dm-snapshot의 기기 매핑

압축된 스냅샷

Android 12 이상에서는 /data 파티션의 공간 요구사항이 높을 수 있으므로 빌드에서 압축된 스냅샷을 사용 설정하여 /data 파티션의 높은 공간 요구사항에 대응할 수 있습니다.

가상 A/B의 압축된 스냅샷은 Android 12 이상에서 사용할 수 있는 다음의 구성요소를 기반으로 빌드됩니다.

  • dm-user: FUSE와 유사한 커널 모듈로, 사용자 공간이 블록 기기를 구현할 수 있게 해 줍니다.
  • snapuserd: 새 스냅샷 형식을 구현하는 사용자 공간 데몬입니다.

이러한 구성요소는 압축을 사용 설정합니다. 압축된 스냅샷 기능을 구현하기 위해 필요한 그 밖의 변경사항은 이어지는 압축된 스냅샷의 COW 형식 섹션, dm-user 섹션, Snapuserd 섹션에 나와 있습니다.

압축된 스냅샷의 COW 형식

Android 12 이상에서는 압축된 스냅샷이 COW 형식을 사용합니다. 압축된 스냅샷의 COW 형식에서는 압축되지 않은 스냅샷에 사용되는 커널의 내장 형식과 비슷하게 메타데이터 섹션과 데이터 섹션이 교대로 나타납니다. 교체 작업에만 허용되는 원본 형식의 메타데이터는 기본 이미지의 블록 X를 스냅샷의 블록 Y 콘텐츠로 교체합니다. 압축된 스냅샷의 COW 형식은 더 다양한 표현이 가능하며, 다음의 연산을 지원합니다.

  • 복사: 기본 기기의 블록 X를 기본 기기의 블록 Y로 바꿉니다.
  • 바꾸기: 기본 기기의 블록 X를 스냅샷의 블록 Y의 콘텐츠로 바꿉니다. 각 블록은 gz 압축됩니다.
  • 0으로 바꾸기: 기본 기기의 블록 X를 모두 0으로 바꿉니다.
  • XOR: COW 기기는 블록 X와 블록 Y 사이에 XOR 연산으로 압축된 바이트를 저장합니다. (Android 13 및 이후 버전에서 사용 가능)

전체 OTA 업데이트는 바꾸기 연산과 0으로 바꾸기 연산으로만 이루어집니다. 증분 OTA 업데이트에는 추가로 복사 연산이 포함될 수 있습니다.

Android 12의 dm-user

dm-user 커널 모듈은 userspace가 기기 매퍼 블록 기기를 구현할 수 있도록 해 줍니다. dm-user 테이블 항목은 /dev/dm-user/<control-name> 아래에 기타 기기를 만듭니다. userspace 프로세스는 기기가 커널의 읽기 및 쓰기 요청을 받도록 폴링할 수 있습니다. 각 요청에는 연결된 버퍼가 있어서 사용자 공간이 (읽기의 경우) 채우거나 (쓰기의 경우) 전파할 수 있습니다.

dm-user 커널 모듈은 커널에 업스트림 kernel.org 코드베이스의 일부가 아닌, 사용자에게 표시되는 새로운 인터페이스를 제공합니다. Google은 dm-user 인터페이스가 코드베이스의 일부가 되기 전까지는 Android에서 이 인터페이스를 수정할 권리를 보유합니다.

snapuserd

dm-usersnapuserd 사용자 공간 구성요소는 가상 A/B 압축을 구현합니다.

Android 11 이하 또는 압축된 스냅샷 옵션이 없는 Android 12의 경우, 압축되지 않은 버전의 가상 A/B에서 COW 기기는 원시 파일입니다. 압축이 사용 설정된 경우에는 COW가 대신 dm-user 기기로 기능하며 snapuserd 데몬의 인스턴스에 연결됩니다.

커널은 새로운 COW 형식을 사용하지 않습니다. 따라서 snapuserd 구성요소는 Android COW 형식과 커널의 내장 형식 사이에서 요청을 변환합니다.

Android COW 형식과 커널의 내장 형식 사이에서 요청을 변환하는 Snapuserd 구성요소

그림 3. Android COW 형식과 커널 COW 형식 사이에서 변환기로 기능하는 snapuserd 흐름 다이어그램

디스크에서는 이 변환 및 압축 해제가 발생하지 않습니다. snapuserd 구성요소가 커널에서 발생하는 COW 읽기 및 쓰기를 가로채서 Android COW 형식을 사용하여 구현합니다.

XOR 압축

Android 13 및 이후 버전으로 출시되는 기기의 경우 기본적으로 사용 설정된 XOR 압축 기능을 사용하면 사용자 공간 스냅샷에서 기존 블록과 새 블록 간의 XOR 압축 바이트를 저장할 수 있습니다. 블록의 소수 바이트만 가상 A/B 업데이트에서 변경될 경우 스냅샷은 전체 4킬로바이트를 저장하지 않으므로 XOR 압축 저장소 스킴은 기본 저장소 스킴보다 공간을 적게 사용합니다. XOR 데이터에는 0이 많이 포함되어 있고 원시 블록 데이터보다 압축하기 쉬우므로 스냅샷 크기를 줄일 수 있습니다. Pixel 기기에서 XOR 압축은 스냅샷 크기를 25~40%로 줄입니다.

Android 13 및 이후 버전으로 업그레이드하는 기기에서는 XOR 압축을 사용 설정해야 합니다. 자세한 내용은 XOR 압축을 참고하세요.

가상 A/B 압축 프로세스

이 섹션은 Android 13 및 Android 12에서 사용되는 가상 A/B 압축 프로세스에 관한 세부정보를 제공합니다.

메타데이터 읽기(Android 12)

메타데이터는 snapuserd 데몬에 의해 구성됩니다. 메타데이터는 주로 병합할 섹터를 나타내는 두 개 ID(각각 8비트)의 매핑입니다. dm-snapshot에서는 disk_exception이라고 합니다.

struct disk_exception {
    uint64_t old_chunk;
    uint64_t new_chunk;
};

디스크 예외는 오래된 데이터 청크가 새로운 청크로 바뀐 경우에 사용됩니다.

snapuserd 데몬은 COW 라이브러리를 통해 내부 COW 파일을 읽은 다음 COW 파일에 있는 각 COW 연산을 대상으로 메타데이터를 구성합니다.

메타데이터 읽기는 dm- snapshot 기기가 생성될 때 커널의 dm-snapshot에서 시작됩니다.

아래 그림은 메타데이터 구성의 IO 경로 시퀀스 다이어그램을 보여줍니다.

메타데이터 구성의 IO 경로 시퀀스 다이어그램

그림 4. 메타데이터 구성의 IO 경로 시퀀스 흐름

병합(Android 12)

부팅 프로세스가 완료되면 업데이트 엔진이 슬롯을 부팅 성공으로 표시하고 dm-snapshot 타겟을 dm-snapshot-merge 타겟으로 전환하여 병합을 시작합니다.

dm-snapshot은 메타데이터를 조회하여 각 디스크 예외를 대상으로 병합 IO를 시작합니다. 아래에는 병합 IO 경로의 개략적인 개요가 나와 있습니다.

병합 IO 경로

그림 5. 병합 IO 경로 개요

병합 프로세스 중에 기기가 재부팅되면 다음번 재부팅에서 병합이 다시 시작되고 완료됩니다.

기기 매퍼 레이어

Android 13 및 이후 버전으로 출시되는 기기의 경우 가상 A/B 압축의 스냅샷 및 스냅샷 병합 프로세스는 snapuserd 사용자 공간 구성요소에서 실행합니다. Android 13 및 이후 버전으로 업그레이드하는 기기에서는 이 기능을 사용 설정해야 합니다. 자세한 내용은 사용자 공간 병합을 참고하세요.

다음은 가상 A/B 압축 프로세스를 설명합니다.

  1. 프레임워크는 dm-user 기기 위에 스택된 dm-verity 기기의 /system 파티션 마운트를 해제합니다. 즉, 루트 파일 시스템의 모든 I/O가 dm-user로 라우팅됩니다.
  2. dm-user는 I/O 요청을 처리하는 사용자 공간 snapuserd 데몬으로 I/O를 라우팅합니다.
  3. 병합 작업이 완료되면 프레임워크는 dm-linear(system_base) 위의 dm-verity를 축소하고 dm-user를 삭제합니다.

가상 A/B 압축 프로세스

그림 6. 가상 A/B 압축 프로세스

스냅샷 병합 프로세스는 중단될 수 있습니다. 병합 프로세스 중에 기기가 재부팅되면 재부팅 후에 병합 프로세스가 다시 시작됩니다.

init 전환

압축된 스냅샷을 사용하여 부팅할 때 파티션을 마운트하려면 1단계 init이 snapuserd를 시작해야 합니다. 이로 인해 문제가 발생합니다. sepolicy가 로드되어 적용되면 snapuserd가 잘못된 컨텍스트에 배치되어 selinux 거부와 함께 읽기 요청이 실패하기 때문입니다.

이 문제를 해결하기 위해 snapuserdinit과 함께 다음과 같이 정해진 단계에 따라 전환됩니다.

  1. 1단계 init이 램디스크에서 snapuserd을 실행하고 환경 변수에 램디스크의 열린 파일 설명자를 저장합니다.
  2. 1단계 init이 루트 파일 시스템을 시스템 파티션으로 전환한 다음 init의 시스템 사본을 실행합니다.
  3. init의 시스템 사본이 결합된 sepolicy를 문자열로 읽어 들입니다.
  4. Init이 모든 ext4 기반 페이지에서 mlock()을 호출합니다. 그런 다음 스냅샷 기기의 모든 기기 매퍼 테이블을 비활성화하고 snapuserd를 중지합니다. 이렇게 되면 파티션에서 읽기가 금지됩니다. 파티션에서 읽기를 실행하면 교착 상태가 발생하기 때문입니다.
  5. initsnapuserd의 램디스크 사본의 열린 설명자를 사용하여 올바른 selinux 컨텍스트로 데몬을 다시 실행합니다. 스냅샷 기기의 기기 매퍼 테이블이 다시 활성화됩니다.
  6. init이 munlockall()을 호출합니다. 이제 다시 안전하게 IO를 실행할 수 있습니다.

공간 사용량

다음 표에는 Pixel의 OS 및 OTA 크기를 사용하여 여러 OTA 메커니즘의 공간 사용량이 비교되어 있습니다.

크기 영향 비 A/B A/B 가상 A/B 가상 A/B(압축됨)
원본 공장 출고 시 이미지 슈퍼 4.5GB(이미지 3.8G + 예약된 공간 700M)1 슈퍼 9GB(3.8G + 예약된 공간 700M, 슬롯 2개) 슈퍼 4.5GB(이미지 3.8G + 예약된 공간 700M) 슈퍼 4.5GB(이미지 3.8G + 예약된 공간 700M)
기타 정적 파티션 /cache 없음 없음 없음
OTA 중 추가 저장용량(OTA를 적용한 후 반환된 공간) /data에 1.4GB 0 /data에 3.8GB2 /data에 2.1GB2
OTA를 적용하는 데 필요한 총 저장용량 5.9GB3(슈퍼 및 데이터) 9GB(슈퍼) 8.3GB3(슈퍼 및 데이터) 6.6GB3(슈퍼 및 데이터)

1Pixel 매핑에 따라 가정된 레이아웃을 나타냅니다.

2새 시스템 이미지의 크기가 원본과 동일하다고 가정합니다.

3재부팅까지는 공간 요구사항이 일시적입니다.

가상 A/B를 구현하거나 압축된 스냅샷 기능을 사용하려면 가상 A/B 구현을 참고하세요.