仮想A / Bの概要

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マウントポイントの下のスタックがどのように見えるかを示しています。

Partition stacking underneath system

1./systemマウントポイントの下のスタック

dm-スナップショット

仮想A/Bは、ストレージデバイスの状態をスナップショットするためのデバイスマッパーモジュールであるdm-snapshotに依存しています。 dm-snapshotを使用する場合、次の4つのデバイスが使用されます。

  • ベースデバイスは、スナップショットが作成されたデバイスです。このページでは、ベースデバイスは常にシステムやベンダーなどの動的パーティションです。
  • ベースデバイスへの変更をログに記録するためのコピーオンライト(COW)デバイス。サイズは任意ですが、ベースデバイスへのすべての変更に対応できる十分な大きさである必要があります。
  • スナップショットデバイスは、 snapshotターゲットを使用して作成されます。スナップショットデバイスへの書き込みは、COWデバイスに書き込まれます。アクセスされているデータがスナップショットによって変更されたかどうかに応じて、ベースデバイスまたはCOWデバイスのいずれかから読み取られたスナップショットデバイスからの読み取り。
  • オリジンデバイスは、 snapshot-originターゲットを使用して作成されます。ベースデバイスから直接読み取ったオリジンデバイスに読み取ります。オリジンデバイスへの書き込みはベースデバイスへの直接書き込みですが、元のデータはCOWデバイスへの書き込みによってバックアップされます。

Device mapping for dm-snapshot

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形式とカーネルの組み込み形式の間で要求を変換します。

Snapuserd component translating requests between Android COW format and kernel built-in format

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パスのシーケンス図を示しています。

Sequence diagram, IO path for metadata construction

図4.メタデータ構築におけるIOパスのシーケンスフロー

マージ

ブートプロセスが完了すると、更新エンジンはスロットをブート成功としてマークし、 dm-snapshotターゲットをdm-snapshot-mergeターゲットに切り替えることによってマージを開始します。

dm-snapshotはメタデータをウォークスルーし、ディスク例外ごとにマージIOを開始します。マージIOパスの概要を以下に示します。

Merge IO path

図5.マージIOパスの概要

マージプロセス中にデバイスがリブートされた場合、マージは次のリブートで再開され、マージが完了します。

初期化遷移

圧縮されたスナップショットを使用して起動する場合、最初のステージのinitはsnapuserdを起動してパーティションをマウントする必要があります。これは問題を引き起こします: sepolicyがロードされて適用されると、 snapuserdが間違ったコンテキストに置かれ、その読み取り要求が失敗し、selinuxが拒否されます。

これに対処するために、 snapuserdは次のようにinitを使用してロックステップで遷移します。

  1. 第1段階のinitは、ramdiskからsnapuserdを起動し、開いているファイル記述子を環境変数に保存します。
  2. 第1段階のinitは、ルートファイルシステムをシステムパーティションに切り替えてから、 initのシステムコピーを実行します。
  3. initのシステムコピーは、結合されたsepolicyを文字列に読み込みます。
  4. Initは、ext4でバックアップされたすべてのページでmlock()を呼び出します。次に、スナップショットデバイスのすべてのデバイスマッパーテーブルを非アクティブ化し、 snapuserdを停止します。この後、デッドロックが発生するため、パーティションからの読み取りは禁止されています。
  5. snapuserdのramdiskコピーへのオープン記述子を使用して、 initは正しい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予約済み)
その他の静的パーティション/キャッシュなしなしなし
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の実装を参照してください。