新しいデバイスに仮想 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-op 圧縮方式を使用しながら、仮想 A/B でユーザー空間のスナップショットが有効になります。すると、圧縮方式を、サポートされている方式のいずれか(gz
、brotli
)に構成できます。
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.hal と types.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_SNAPSHOT
を true
に設定します。
F2FS を使用するデバイスの場合、f2fs: export FS_NOCOW_FL flag to user のカーネルパッチを追加して、ファイルの固定を修正します。また、f2fs: support aligned pinned file のカーネルパッチも含めてください。
仮想 A/B は、カーネル バージョン 4.3 で追加された機能である snapshot
と snapshot-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
Android 11 にアップグレードするデバイスでのレトロフィット
Android 11 にアップグレードする際、動的パーティションを使用してリリースされたデバイスでは、必要に応じて仮想 A/B をレトロフィットできます。更新プロセスは、仮想 A/B を使用してリリースするデバイスとほぼ同じですが、次の若干の違いがあります。
COW ファイルの場所 - リリース済みデバイスの場合、OTA クライアントは
/data
のスペースを使用する前に、super パーティションの利用可能な空のスペースをすべて使用します。レトロフィット デバイスの場合、super パーティションに十分なスペースがあり、COW ファイルが/data
に作成されないようにします。ビルド時の機能フラグ - 仮想 A/B をレトロフィットするデバイスの場合、次に示すように
PRODUCT_VIRTUAL_AB_OTA
とPRODUCT_VIRTUAL_AB_OTA_RETROFIT
の両方がtrue
に設定されます。(call inherit-product, \
(SRC_TARGET_DIR)/product/virtual_ab_ota_retrofit.mk)
super パーティション サイズ - 仮想 A/B でリリースするデバイスでは、super パーティションに B スロットが含まれないため、
BOARD_SUPER_PARTITION_SIZE
を半分に短縮できます。仮想 A/B をレトロフィットしたデバイスでは、古い super パーティションのサイズが維持されます。そのため、BOARD_SUPER_PARTITION_SIZE
は 2 × アップデート グループのサイズ合計 + オーバーヘッド以上となり、同様に、2 × パーティションのサイズ合計 + オーバーヘッド以上の値になります。
ブートローダーの変更
アップデートでのマージ中、/data
には Android OS のインスタンス全体のみが含まれます。移行が開始されると、コピーが完了するまで、ネイティブの system
、vendor
、product
のパーティションは不完全な状態になります。このプロセス中に、リカバリまたはシステム設定ダイアログによってデバイスが出荷時設定にリセットされると、デバイスは起動できなくなります。
/data
を消去する前に、デバイスの状態に応じて、リカバリまたはロールバックでマージを完了します。
- 新しいビルドが正常に起動した場合は、移行を完了します。
- それ以外の場合は、古いスロットにロールバックします。
- 動的パーティションの場合は、前の状態にロールバックします。
- 静的パーティションの場合は、アクティブ スロットを古いスロットに設定します。
デバイスがロック解除されている場合、ブートローダーと fastbootd
はどちらも /data
パーティションを消去できます。fastbootd
は移行の完了を強制することもできますが、ブートローダーは強制できません。ブートローダーは、マージが進行中かどうか、/data
内のどのブロックが OS パーティションを構成するのかを把握できません。デバイスは、ユーザーが知らずにデバイスを動作不能(文鎮化)にすることを防ぐために、以下を行う必要があります。
- ブートローダーが
setSnapshotMergeStatus()
メソッドで設定された値を読み取れるように、ブート コントロール HAL を実装する。 - マージ ステータスが
MERGING
の場合、またはマージ ステータスがSNAPSHOTTED
で、スロットが新しく更新されたスロットに変更された場合は、ブートローダーでuserdata
、metadata
、またはマージ ステータスを格納するパーティションのワイプ リクエストを拒否する。 fastboot snapshot-update cancel
コマンドを実装して、ユーザーがこの保護メカニズムの回避をブートローダーに通知できるようにする。- デバイス全体のフラッシュ時に
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()));
}
fastboot ツールの変更
Android 11 では、fastboot プロトコルに次の変更が加えられます。
getvar snapshot-update-status
- ブート コントロール HAL がブートローダーに通知した値を返します。- ステータスが
MERGING
の場合、ブートローダーはmerging
を返します。 - ステータスが
SNAPSHOTTED
の場合、ブートローダーはsnapshotted
を返します。 - それ以外の場合は、ブートローダーは
none
を返します。
- ステータスが
snapshot-update merge
- マージ処理を完了します。必要に応じてリカバリまたは fastboot を起動します。このコマンドは、snapshot-update-status
がmerging
の場合にのみ有効で、fastbootd でのみサポートされています。snapshot-update cancel
- ブート コントロール HAL のマージ ステータスをCANCELLED
に設定します。このコマンドは、デバイスがロックされている場合は無効です。erase
またはwipe
-metadata
、userdata
、またはブート コントロール HAL のマージ ステータスを保持するパーティションのerase
またはwipe
ではスナップショットのマージ ステータスを確認します。ステータスがMERGING
またはSNAPSHOTTED
の場合、デバイスはオペレーションを中止します。set_active
- アクティブ スロットを変更するset_active
コマンドは、スナップショットのマージ ステータスを確認する必要があります。ステータスがMERGING
の場合、デバイスはオペレーションを中止します。スロットはSNAPSHOTTED
状態で安全に変更できます。
これらの変更の目的は、デバイスが誤って起動不能になることを避けるためですが、自動化ツールの妨げとなる場合があります。すべてのパーティションをフラッシュするコンポーネント(fastboot flashall
の実行など)としてコマンドを使用する場合は、次のフローを使用することをおすすめします。
getvar snapshot-update-status
をクエリします。merging
またはsnapshotted
の場合、snapshot-update cancel
を発行します。- フラッシュの手順に進みます。
ストレージ要件の削減
フル A/B ストレージが super パーティションに割り当てられたものではなく、必要に応じて /data
を使用する必要があるデバイスでは、ブロック マッピング ツールを使用することを強くおすすめします。ブロック マッピング ツールは、ビルド間でブロック割り当ての一貫性を保ち、スナップショットへの不必要な書き込みを削減します。詳細については、OTA サイズの削減をご覧ください。
OTA の圧縮方法
OTA パッケージは、さまざまなパフォーマンス指標に合わせて調整できます。Android では現在、いくつかの圧縮方法(gz
、lz4
、none
)がサポートされており、それぞれにインストール時間、cow のスペース使用量、起動時間、スナップショットのマージ時間のトレードオフがあります。圧縮ありの仮想 ab でデフォルトで有効になっているオプションは gz compression method
です(注: 各圧縮方法のパフォーマンスは CPU の速度やストレージのスループットによって異なるため、デバイスによってパフォーマンスに差が生じる可能性があります。以下の手順で生成される OTA パッケージはすべて PostInstall を無効にしているため、起動がわずかに遅くなります。圧縮なしのフル OTA の動的パーティションの合計サイズは 4.81 GB です)。
1. Google Pixel 6 Pro の増分 OTA
Postinstall フェーズなしのインストール時間 | cow のスペース使用量 | OTA 後の起動時間 | スナップショットのマージ時間 | |
---|---|---|---|---|
gz | 24 分 | 1.18 GB | 40.2 秒 | 45.5 秒 |
lz4 | 13 分 | 1.49 GB | 37.4 秒 | 37.1 秒 |
なし | 13 分 | 2.90 GB | 37.6 秒 | 40.7 秒 |
2. Google Pixel 6 Pro のフル OTA
Postinstall フェーズなしのインストール時間 | cow のスペース使用量 | OTA 後の起動時間 | スナップショットのマージ時間 | |
---|---|---|---|---|
gz | 23 分 | 2.79 GB | 24.9 秒 | 41.7 秒 |
lz4 | 12 分 | 3.46 GB | 20.0 秒 | 25.3 秒 |
なし | 10 分 | 4.85 GB | 20.6 秒 | 29.8 秒 |