仮想 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/vabc_features.mk)

これにより、no-op 圧縮方式を使用しながら、仮想 A/B でユーザー空間のスナップショットが有効になります。すると、圧縮方式を、サポートされている方式のいずれか(zstdlz4、)に構成できます。Android 15 では、デバイスのニーズに合わせて圧縮をさらにカスタマイズできます。詳細については、圧縮のファインチューニングを参照してください。

PRODUCT_VIRTUAL_AB_COMPRESSION_METHOD := lz4
PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR := 65536

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 圧縮がデフォルトで有効になっています。

ユーザー空間の統合

最新バージョンの仮想 A/B(Android T 以降)では、スナップショットの統合プロセスは完全にユーザー空間で実行されます。この変更は snapuserd と dm-user によって可能になりました。Android 13 以降を搭載したデバイスでは、ユーザー空間の統合がデフォルトで有効になっています。古いデバイスをアップグレードする場合は、このプロパティを次のように設定できます。

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

ブート コントロール 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 がマウントする前にメタデータ パーティションをチェックする必要があります。確実にこれを行うには、check fs_mgr フラグを /metadata のエントリに追加します。次に例を示します。

/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 ターゲットにおけるオーバーフロー ステータス ビットに依存します。Android 9 以降を搭載したデバイスには、カーネル バージョン 4.4 以降が搭載されているはずです。

圧縮スナップショットを有効にする場合、サポートされる最小カーネル バージョンは 4.19 です。CONFIG_DM_USER=m または CONFIG_DM_USER=y を設定します。前者(モジュール)を使用する場合は、モジュールを第 1 ステージの RAM ディスクに読み込む必要があります。そのためには、デバイスの Makefile に次の行を追加します。

BOARD_GENERIC_RAMDISK_KERNEL_MODULES_LOAD := dm-user.ko

fastboot ツールの変更

Android 11 では、fastboot プロトコルに次の変更が加えられます。

  • getvar snapshot-update-status - ブート コントロール HAL がブートローダーに通知した値を返します。
    • ステータスが MERGING の場合、ブートローダーは merging を返します。
    • ステータスが SNAPSHOTTED の場合、ブートローダーは snapshotted を返します。
    • それ以外の場合は、ブートローダーは none を返します。
  • snapshot-update merge - マージ処理を完了します。必要に応じてリカバリまたは fastboot を起動します。このコマンドは、snapshot-update-statusmerging の場合にのみ有効で、fastbootd でのみサポートされています。
  • snapshot-update cancel - ブート コントロール HAL のマージ ステータスを CANCELLED に設定します。このコマンドは、デバイスがロックされている場合は無効です。
  • erase または wipe - metadatauserdata、またはブート コントロール 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 ではいくつかの圧縮方法(lz4zstdnone)がサポートされており、それぞれにインストール時間、COW のスペース使用量、起動時間、スナップショットのマージ時間のトレードオフがあります。圧縮ありの仮想 ab でデフォルトで有効になっているオプションは lz4 compression method です。

圧縮の微調整

圧縮アルゴリズムは、(圧縮レベル)(速度を犠牲にして達成される圧縮の量)と(圧縮係数)(圧縮可能な最大ウィンドウ サイズ)の 2 つの方法でさらにカスタマイズできます。圧縮レベルは、zstd などの特定のアルゴリズムで使用可能であり、レベルを変更すると、速度と圧縮率の間でトレードオフが発生します。圧縮係数は、OTA インストール中に使用される最大圧縮ウィンドウ サイズを表します。デフォルトは 64k に設定されていますが、ビルド パラメータ PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR をカスタマイズすることで上書きできます。サポートされている圧縮係数は 4k、8k、16k、32k、64k、128k、256k です。

PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR := 65536

Google Pixel 8 Pro の増分 OTA

postinstall フェーズなしのインストール時間 COW のスペース使用量 OTA 後の起動時間 スナップショットのマージ時間
lz4 18 分 15 秒 2.5 GB 32.7 秒 98.6 秒
zstd 24 分 49 秒 2.05 GB 36.3 秒 133.2 秒
なし 16 分 42 秒 4.76 GB 28.7 秒 76.6 秒

Google Pixel 8 Pro のフル OTA

postinstall フェーズなしのインストール時間 COW のスペース使用量 OTA 後の起動時間 スナップショットのマージ時間
lz4 15 分 11 秒 4.16 GB 17.6 秒 82.2 秒
zstd 16 分 19 秒 3.46 GB 21.0 秒 106.3 秒
なし 13 分 33 秒 6.39 GB 18.5 秒 92.5 秒