仮想 A/B は、Android の主要なアップデート メカニズムです。仮想 A/B は、以前の A/B アップデート(A/B システム アップデートを参照)と、アップデートに要する領域を削るために 15 で非推奨となった非 A/B を基盤としています。
仮想 A/B に動的パーティションの追加スロットはありません(動的パーティションを参照)。その代わりに、差分がスナップショットに書き込まれ、起動の成功が確認された後でベース パーティションに統合されます。仮想 A/B では Android 専用のスナップショット形式を使用します。スナップショットを圧縮し、ディスク使用量を最小に抑える圧縮スナップショットの COW 形式を参照してください。フル OTA では、圧縮によりスナップショットのサイズが約 45% 削減され、増分 OTA では約 55% 削減されます。
Android 12 には、仮想 A/B 圧縮を使用して、スナップショットされたパーティションを圧縮するオプションがあります。仮想 A/B には次のようなメリットがあります。
- 仮想 A/B アップデートはシームレスです(A/B アップデートのようにアップデートはすべてバックグラウンドで行われ、その間もデバイスは使用可能です)。仮想 A/B アップデートにより、デバイスがオフライン状態で使用できない時間を最小限に抑えられます。
- 仮想 A/B アップデートはロールバックできます。新しい OS の起動に失敗した場合、デバイスは自動的に以前のバージョンにロールバックされます。
- 仮想 A/B アップデートは、ブートローダーで使用されるパーティションのみを複製することにより、最小限の追加スペースを使用します。その他の更新可能なパーティションはスナップショットされます。
背景と用語
このセクションでは、仮想 A/B に関連する用語を定義し、仮想 A/B を支えるテクノロジーについて説明します。OTA インスールの間、新しいオペレーティング システムのデータは物理パーティションの新たなスロットか、Android 専用の COW デバイスのいずれかに書き込まれます。デバイスの再起動後、dm-user と snapuserd デーモンの使用を通じて、動的パーティションのデータがベースデバイスに統合されます。この処理はすべてユーザー空間で行われます。
デバイス マッパー
デバイス マッパーは、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 マウント ポイントの下のスタック
圧縮スナップショット
Android 12 以降では、/data
パーティションに必要なスペースが大きくなる可能性があるため、ビルドで圧縮スナップショットを有効にして、/data
パーティションのスペース要件の高さに対処できます。
仮想 A/B の圧縮スナップショットは、Android 12 以降で利用できる以下のコンポーネントの上に構築されます。
これらのコンポーネントにより、圧縮が可能になります。圧縮スナップショット機能を実装するために必要なその他の変更については、この後のセクション(圧縮スナップショットの COW 形式、dm-user、snapuserd)をご覧ください。
圧縮スナップショットの COW 形式
Android 12 以降、圧縮スナップショットは Android 専用の COW 形式を使用します。COW 形式は、OTA に関するメタデータを含み、COW 処理を含んだ個別のバッファと新しいオペレーティング システムのデータを備えています。置換オペレーション(ベースイメージのブロック X をスナップショットのブロック Y に置き換える)が可能なカーネル スナップショット形式に比べると、Android 圧縮スナップショット COW 形式は表現力が高く、以下のオペレーションをサポートしています。
- コピー: ベースデバイス内のブロック X が、ベースデバイス内のブロック Y に置き換えられます。
- 置換: ベースデバイス内のブロック X が、スナップショット内のブロック Y の内容に置き換えられます。それぞれのブロックは gz 形式で圧縮されます。
- ゼロ: ベースデバイス内のブロック X がすべてゼロに置き換えられます。
- XOR: COW デバイスは、ブロック X とブロック Y の間で XOR 圧縮されたバイトを格納します。(Android 13 以降で利用できます)。
フル OTA アップデートは、「置換」オペレーションと「ゼロ」オペレーションのみで構成されます。増分 OTA アップデートには、追加で「コピー」オペレーションを含めることができます。
フル スナップショットのディスク上のレイアウトは次のようになっています。
図 2. ディスク上の Android COW 形式
dm-user
dm-user カーネル モジュールを使用すると、userspace
にデバイス マッパー ブロック デバイスを実装できます。dm-user テーブル エントリは、/dev/dm-user/<control-name>
の下にその他デバイスを作成します。userspace
プロセスは、カーネルからの読み取りリクエストと書き込みリクエストを受信するためにデバイスをポーリングできます。各リクエストには、ユーザースペースがデータ移入(読み取りの場合)または伝播(書き込みの場合)を行うためのバッファが関連付けられています。
dm-user
カーネル モジュールは、アップストリームの kernel.org コードベースの一部ではない、ユーザーから見える新しいユーザー インターフェースをカーネルに提供します。現時点では、Google は Android の dm-user
インターフェースを変更する権限を有しています。
snapuserd
dm-user
に対する snapuserd
ユーザースペース コンポーネントは、仮想 A/B 圧縮を実装します。snapuserd は、Android COW デバイスの読み書きを担当するユーザー空間デーモンです。スナップショットに対する I/O は、すべてこのサービスを経由する必要があります。OTA インスールの間、snapuserd が新しいオペレーティング システムのデータを(圧縮して)スナップショットに書き込みます。メタデータの解析と新しいブロックデータの展開もここで処理されます。
XOR 圧縮
Android 13 以降を搭載したデバイスの場合、デフォルトで有効になっている XOR 圧縮機能により、ユーザー空間のスナップショットを使用して、古いブロックと新しいブロックの間で XOR 圧縮されたバイトを格納できます。仮想 A/B アップデートでブロック内の数バイトのみが変更される場合、スナップショットが 4K バイトすべてを保存するわけではないため、XOR 圧縮ストレージ スキームが使用する容量はデフォルトのストレージ スキームよりも少なくなります。XOR データはゼロを多く含み、未加工のブロックデータよりも圧縮しやすいため、スナップショット サイズを削減できます。Google Pixel デバイスでは、XOR 圧縮によりスナップショット サイズが 25~40% 削減されます。
Android 13 以降にアップグレードするデバイスでは、XOR 圧縮を有効にする必要があります。詳細については、XOR 圧縮をご覧ください。
スナップショットの統合
Android 13 以降を搭載したデバイスの場合、仮想 A/B 圧縮におけるスナップショットとスナップショットの統合プロセスは、snapuserd
ユーザー空間コンポーネントによって実施されます。Android 13 以降にアップグレードするデバイスでは、この機能を有効にする必要があります。詳しくは、ユーザー空間の統合をご覧ください。
仮想 A/B 圧縮プロセスは次のとおりです。
- フレームワークは、
dm-user
デバイスの上にスタックされたdm-verity
デバイスから/system
パーティションをマウントします。つまり、ルート ファイル システムからの I/O はすべてdm-user
にルーティングされます。 dm-user
は、I/O リクエストを処理するユーザー空間snapuserd
デーモンに I/O をルーティングします。- 統合オペレーションが完了すると、フレームワークは
dm-linear
(system_base
)の上のdm-verity
を閉じ、dm-user
を削除します。
図 3. 仮想 A/B 圧縮プロセス
スナップショットの統合プロセスは中断できます。統合プロセス中にデバイスが再起動された場合、統合プロセスは再起動後に再開されます。
init 遷移
圧縮スナップショットで起動する場合、第 1 ステージである init は、snapuserd
を開始してパーティションをマウントする必要があります。これにより、sepolicy
が読み込まれて適用される際に、snapuserd
が誤ったコンテキストに配置され、その読み取りリクエストが SELinux の拒否が原因で失敗するという問題が発生します。
これに対処するため、snapuserd
は、lock-step で init
の遷移を次のように行います。
- 第 1 ステージである
init
は、RAM ディスクからsnapuserd
を起動し、それに対するオープン状態の file-descriptor を環境変数に保存します。 - 第 1 ステージである
init
は、ルート ファイルシステムをシステム パーティションに切り替えた後、init
のシステムコピーを実行します。 init
のシステムコピーは、結合された sepolicy を文字列に読み込みます。Init
は、ext4 を利用するすべてのページでmlock()
を呼び出します。次に、スナップショット デバイスのすべてのデバイス マッパー テーブルを非アクティブ化し、snapuserd
を停止します。これ以降、パーティションからの読み取りは、デッドロックが発生するため禁止されます。init
は、snapuserd
の RAM ディスクコピーに対するオープン状態の記述子を使用して、正しい SELinux コンテキストでデーモンを再起動します。スナップショット デバイス用のデバイス マッパー テーブルが再アクティブ化されます。- init は
munlockall()
を呼び出します。これで、再び IO を安全に実行できるようになります。
スペース使用量
次の表は、Pixel の OS と OTA サイズを使用するさまざまな OTA メカニズムのスペース使用量を比較したものです。
サイズへの影響 | 非 A/B | A/B | 仮想 A/B | 仮想 A/B(圧縮) |
---|---|---|---|---|
元のファクトリー イメージ | 4.5 GB super(3.8 G イメージ + 700 M 予約済み)1 | 9 GB super(3.8 G + 700 M 予約済み、2 スロット) | 4.5 GB super(3.8 G イメージ + 700 M 予約済み) | 4.5 GB super(3.8 G イメージ + 700 M 予約済み) |
その他の静的パーティション | /cache | なし | なし | なし |
OTA 時の追加ストレージ(OTA の適用後に返されるスペース) | /data で 1.4 GB | 0 | /data で 3.8 GB2 | /data で 2.1 GB2 |
OTA の適用に必要な合計ストレージ | 5.9 GB3(super と data) | 9 GB(super) | 8.3 GB3(super と data) | 6.6 GB3(super と data) |
1 Pixel マッピングに基づく想定レイアウトを示します。
2 新しいシステム イメージが元のイメージと同じサイズであると想定しています。
3 スペース要件は再起動までの一時的な要件です。
Android 11 の仮想 A/B
仮想 A/B の Android 11 では、カーネル COW 形式で動的パーティションに書き込んでいました。カーネル COW 形式では圧縮がサポートされていないため、これはサポート終了となりました。
Android 12 の仮想 A/B
Android 12 では、Android 専用 COW 形式の形で圧縮がサポートされています。このバージョンの仮想 A/B では、Android 専用 COW 形式をカーネル COW 形式に変換する必要がありました。結局これは Android 13 で置き換えられ、カーネル COW 形式と dm-snapshot
への依存が解消しました。
仮想 A/B を実装する方法または圧縮スナップショット機能を使用する方法については、仮想 A/B の実装をご覧ください。