Androidには、A / B(シームレス)アップデートと非A/Bアップデートの2つのアップデートメカニズムがあります。コードの複雑さを軽減し、更新プロセスを強化するために、Android 11では、2つのメカニズムが仮想A / Bによって統合され、ストレージのコストを最小限に抑えてすべてのデバイスにシームレスな更新をもたらします。 Android 12には、スナップショットのパーティションを圧縮するための仮想A/B圧縮のオプションがあります。 Android11とAndroid12の両方で、以下が適用されます。
- 仮想A/B更新は、A/B更新のようにシームレスです。仮想A/Bの更新により、デバイスがオフラインで使用できなくなる時間を最小限に抑えることができます。
- 仮想A/Bの更新はロールバックできます。新しいOSの起動に失敗した場合、デバイスは自動的に以前のバージョンにロールバックします。
- 仮想A/B更新は、ブートローダーによって使用されるパーティションのみを複製することにより、最小限の余分なスペースを使用します。他の更新可能なパーティションはスナップショットが作成されます。
背景と用語
このセクションでは、用語を定義し、仮想A/Bをサポートするテクノロジーについて説明します。
デバイスマッパー
デバイスマッパーは、Androidでよく使用されるLinux仮想ブロックレイヤーです。動的パーティションでは、 /system
のようなパーティションは階層化されたデバイスのスタックです。
- スタックの一番下には、物理的なスーパーパーティションがあります(たとえば、
/dev/block/by-name/super
)。 - 真ん中には
dm-linear
デバイスがあり、スーパーパーティション内のどのブロックが特定のパーティションを形成するかを指定します。これは、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-スナップショット
仮想A/Bは、ストレージデバイスの状態をスナップショットするためのデバイスマッパーモジュールであるdm-snapshot
に依存しています。 dm-snapshot
を使用する場合、次の4つのデバイスが使用されます。
- ベースデバイスは、スナップショットが作成されたデバイスです。このページでは、ベースデバイスは常にシステムやベンダーなどの動的パーティションです。
- ベースデバイスへの変更をログに記録するためのコピーオンライト(COW)デバイス。サイズは任意ですが、ベースデバイスへのすべての変更に対応できる十分な大きさである必要があります。
- スナップショットデバイスは、
snapshot
ターゲットを使用して作成されます。スナップショットデバイスへの書き込みは、COWデバイスに書き込まれます。アクセスされているデータがスナップショットによって変更されたかどうかに応じて、ベースデバイスまたはCOWデバイスのいずれかから読み取られたスナップショットデバイスからの読み取り。 - オリジンデバイスは、
snapshot-origin
ターゲットを使用して作成されます。ベースデバイスから直接読み取ったオリジンデバイスに読み取ります。オリジンデバイスへの書き込みはベースデバイスへの直接書き込みですが、元のデータはCOWデバイスへの書き込みによってバックアップされます。
図2.dm-snapshotのデバイスマッピング
圧縮されたスナップショット
Android 12では、 /data
パーティションのスペース要件が高くなる可能性があるため、ビルドで圧縮スナップショットを有効にして、 /data
パーティションのより高いスペース要件に対処できます。
仮想A/B圧縮スナップショットは、Android12で利用可能な2つの新しいコンポーネントの上に構築されています。
-
dm-user
は、ユーザースペースがブロックデバイスを実装できるようにするFUSEに似たカーネルモジュールです。 -
snapuserd
、新しいスナップショット形式を実装するためのユーザースペースデーモン。
これらのコンポーネントは圧縮を可能にします。圧縮スナップショット機能を実装するために行われたその他の必要な変更については、次のセクションで説明します。圧縮スナップショットのCOW形式、 dm-user 、およびSnapuserd 。
圧縮スナップショットのCOW形式
Android 12では、圧縮されたスナップショットは新しいCOW形式を使用します。非圧縮スナップショットに使用されるカーネルの組み込み形式と同様に、圧縮スナップショットのCOW形式には、メタデータとデータのセクションが交互にあります。元の形式のメタデータは、「置換」操作でのみ許可されています。ベースイメージのブロックXを、スナップショットのブロックYの内容に置き換えます。圧縮スナップショットCOW形式はより表現力があり、次の3つの操作をサポートします。
- コピー-ベースデバイスのブロックXは、ベースデバイスのブロックYに置き換える必要があります。
- 置換-ベースデバイスのブロックXは、スナップショットのブロックYの内容に置き換える必要があります。これらの各ブロックはgz圧縮されています。
- ゼロ-ベースデバイスのブロックXをすべてゼロに置き換える必要があります。
完全なOTA更新は、置換操作とゼロ操作のみで構成されます。インクリメンタルOTAアップデートには、さらにコピー操作を含めることができます。
dm-Android12のユーザー
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圧縮を実装します。
非圧縮バージョンのVirtualA/ B(Android 11以前、または圧縮スナップショットオプションのないAndroid 12)では、COWデバイスはrawファイルです。圧縮が有効になっている場合、COWは代わりに、 snapuserd
デーモンのインスタンスに接続されているdm-user
デバイスとして機能します。
カーネルは新しいCOW形式を使用しません。したがって、 snapuserd
コンポーネントは、AndroidCOW形式とカーネルの組み込み形式の間で要求を変換します。
図3.AndroidとカーネルのCOW形式間のトランスレーターとしてのsnapuserdのフロー図
この変換と解凍はディスク上では発生しません。 snapuserd
コンポーネントは、カーネルで発生するCOWの読み取りと書き込みをインターセプトし、AndroidCOW形式を使用してそれらを実装します。
仮想A/B圧縮プロセス
これらのセクションでは、仮想A / B圧縮で使用されるプロセス(メタデータの読み取り、マージ、初期化遷移の実行)に関する詳細を提供します。
メタデータの読み取り
メタデータはsnapuserd
デーモンによって構築されます。メタデータは主に、マージされるセクターを表す2つのID(それぞれ8バイト)のマッピングです。 dm-snapshot
では、 disk_exception
と呼ばれます。
struct disk_exception {
uint64_t old_chunk;
uint64_t new_chunk;
};
ディスク例外は、データの古いチャンクが新しいチャンクに置き換えられるときに使用されます。
Snapuserd
デーモンは、COWライブラリを介して内部COWファイルを読み取り、COWファイルに存在する各COW操作のメタデータを構築します。
メタデータの読み取りは、dm- dm- snapshot
デバイスの作成時にカーネルのdm-snapshot
から開始されます。
次の図は、メタデータ構築のIOパスのシーケンス図を示しています。
図4.メタデータ構築におけるIOパスのシーケンスフロー
マージ
ブートプロセスが完了すると、更新エンジンはスロットをブート成功としてマークし、 dm-snapshot
ターゲットをdm-snapshot-merge
ターゲットに切り替えることによってマージを開始します。
dm-snapshot
はメタデータをウォークスルーし、ディスク例外ごとにマージIOを開始します。マージIOパスの概要を以下に示します。
図5.マージIOパスの概要
マージプロセス中にデバイスがリブートされた場合、マージは次のリブートで再開され、マージが完了します。
初期化遷移
圧縮されたスナップショットを使用して起動する場合、最初のステージのinitはsnapuserd
を起動してパーティションをマウントする必要があります。これは問題を引き起こします: sepolicy
がロードされて適用されると、 snapuserd
が間違ったコンテキストに置かれ、その読み取り要求が失敗し、selinuxが拒否されます。
これに対処するために、 snapuserd
は次のようにinit
を使用してロックステップで遷移します。
- 第1段階の
init
は、ramdiskからsnapuserd
を起動し、開いているファイル記述子を環境変数に保存します。 - 第1段階の
init
は、ルートファイルシステムをシステムパーティションに切り替えてから、init
のシステムコピーを実行します。 -
init
のシステムコピーは、結合されたsepolicyを文字列に読み込みます。 -
Init
は、ext4でバックアップされたすべてのページでmlock()
を呼び出します。次に、スナップショットデバイスのすべてのデバイスマッパーテーブルを非アクティブ化し、snapuserd
を停止します。この後、デッドロックが発生するため、パーティションからの読み取りは禁止されています。 -
snapuserd
のramdiskコピーへのオープン記述子を使用して、init
は正しいselinuxコンテキストでデーモンを再起動します。スナップショットデバイスのデバイスマッパーテーブルが再アクティブ化されます。 - 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予約済み) |
その他の静的パーティション | /キャッシュ | なし | なし | なし |
OTA中の追加ストレージ(OTAの適用後に返されるスペース) | /dataで1.4GB | 0 | /dataで3.8GB2 | /dataで2.1GB2 |
OTAを適用するために必要な総ストレージ | 5.9GB 3 (スーパーおよびデータ) | 9GB(スーパー) | 8.3GB 3 (スーパーおよびデータ) | 6.6GB 3 (スーパーおよびデータ) |
1ピクセルマッピングに基づいて想定されるレイアウトを示します。
2新しいシステムイメージが元のシステムイメージと同じサイズであると想定します。
3スペース要件は、再起動するまで一時的です。
仮想A/Bを実装する、または圧縮スナップショット機能を使用するには、仮想A/Bの実装を参照してください。