A/B(シームレス)システム アップデート

以前の A/B システム アップデートはシームレス アップデートとも呼ばれています。このアップデートにより、実行可能な起動システムが無線(OTA)アップデート中もディスク上に残ります。この方法では、アップデート後のデバイスが動作しなくなる可能性が低くなるため、修理保証センターでのデバイスの交換や修理の回数が少なくなります。ChromeOS などのその他の業務用オペレーティング システムでも A/B アップデートは問題なく使用されています。

A/B システム アップデートとその動作の詳細については、パーティションの選択(スロット)をご覧ください。

A/B システム アップデートには次のようなメリットがあります。

  • OTA アップデートは、システムの稼働中でも実行できるため、ユーザーの作業が中断されることはありません。ユーザーは OTA アップデート中も継続してデバイスを使用できます。アップデート中のダウンタイムは、更新されたディスク パーティションでデバイスが再起動するときだけです。
  • アップデート後の再起動にかかる時間は通常の再起動と変わりません。
  • 書き込みがうまくできなかったなど、OTA を適用できない場合も、ユーザーに影響はありません。ユーザーは引き続きアップデート前の OS を実行し、クライアントはいつでもアップデートを再試行できます。
  • OTA アップデートが適用されているのに起動できない場合、デバイスは古いパーティションで再起動し、引き続き使用可能となります。クライアントは、いつでもアップデートを再試行できます。
  • I/O エラーなど、すべてのエラーは使用されていないパーティション セットにしか影響せず、また再試行が可能です。I/O 負荷を意図的に低くしているため、このようなエラーは起こりにくくなっており、ユーザー エクスペリエンスの低下を回避できます。
  • アップデートは A/B デバイスにストリーミングすることもでき、その場合はインストール前にパッケージをダウンロードする必要がありません。ストリーミングにより、ユーザーはアップデート パッケージを /data/cache に保存するために必要な空き領域を確保する必要がなくなります。
  • キャッシュ パーティションを OTA アップデート パッケージの保存に使用することはなくなるため、将来のアップデートに備えて大規模なキャッシュ パーティションを用意する必要はありません。
  • dm-verity により、デバイスは破損していないイメージで起動できます。OTA や dm-verity の問題により起動しない場合、古いイメージで再起動できます(Android の確認付きブートでは A/B アップデートが必要ありません)。

A/B システム アップデートについて

A/B アップデートでは、クライアントとシステムの両方を変更する必要があります。ただし、OTA パッケージ サーバーでは変更の必要はなく、アップデート パッケージは HTTPS 経由で提供されます。Google の OTA インフラストラクチャを使用しているデバイスでは、システムの変更はすべて AOSP で行われ、クライアント コードは Google Play 開発者サービスで提供されます。Google の OTA インフラストラクチャを使用していない OEM の場合、AOSP システムコードを再利用できますが、独自のクライアントを用意する必要があります。

OEM が独自のクライアントを提供している場合、クライアントでは次のことを行う必要があります。

  • アップデートを行うタイミングを決めます。A/B アップデートはバックグラウンドで行われるため、ユーザーが開始するものではありません。ユーザーの邪魔にならないよう、デバイスがアイドル状態のメンテナンス モードで(夜間など)Wi-Fi 接続している時点をアップデートの日時として指定することをおすすめします。ただし、クライアントは任意のヒューリスティックを使用できます。
  • OTA パッケージ サーバーにチェックインし、アップデートが利用可能かどうかを判断します。これは、既存のクライアント コードとほとんど同じです。ただし、デバイスが A/B に対応していることを示している点が異なります(Google のクライアントには、ユーザーが最新のアップデートを確認するための [今すぐ確認] ボタンもあります)。
  • アップデート パッケージが利用可能な場合は、その HTTPS URL を使用して update_engine を呼び出します。update_engine はアップデート パッケージをストリーミングする際に、その時点で使用されていないパーティションの raw ブロックを更新します。
  • update_engine 結果コードに基づいて、サーバーにインストールの成功または失敗をレポートします。アップデートが正常に適用された場合、update_engine は次の再起動時にブートローダーに新しい OS で起動するよう指示します。新しい OS が起動に失敗した場合、ブートローダーは古い OS にフォールバックするので、クライアントからの作業は不要です。アップデートが失敗した場合、クライアントは詳細なエラーコードに基づいて、再試行するかどうか、いつ再試行するかを判断する必要があります。たとえば、正常なクライアントは部分的な(「差分」)OTA パッケージが失敗したことを認識し、代わりにフル OTA パッケージを試すことができます。

必要に応じて、クライアントは次のことを実行できます。

  • ユーザーに再起動を要求する通知を表示します。ユーザーに定期的にアップデートを要求するポリシーを実装する場合は、この通知をクライアントに追加できます。クライアントがユーザーに再起動を要求しない場合は、次回の再起動でアップデートされます(Google クライアントではアップデートごとに遅延を構成可能です)。
  • 新しい OS バージョンで起動したかどうか、または想定に反して以前の OS バージョンにフォールバックしたかどうかを示す通知を表示します(通常、Google クライアントはどちらも表示しません)。

システム側では、A/B システム アップデートによって以下の影響があります。

  • パーティションの選択(スロット)、update_engine デーモン、ブートローダーとの交信(詳細は下記をご覧ください)
  • ビルドプロセスと OTA アップデート パッケージの生成(詳細は A/B アップデートの実装をご覧ください)

パーティションの選択(スロット)

A/B システム アップデートではスロット(通常はスロット A とスロット B と呼ばれます)と呼ばれる 2 つのパーティション セットが使用されます。通常の運用中には、システムは現在のスロットから実行され、使用されていないスロットのパーティションは実行中のシステムからはアクセスされません。この方法では、使用されていないスロットをフォールバックとして確保しておくことでアップデートをフォールト トレラントにできます。アップデート中またはアップデート直後にエラーが発生した場合、システムは古いスロットにロールバックし、動作可能な状態を継続します。この目的を達成するため、現在のスロットで使用されるパーティションは OTA アップデートの一環としては更新されません。パーティションが 1 つしかない場合でも同様です。

各スロットには起動可能属性があり、デバイスが起動できる適切なシステムがスロットにあるかどうかを示します。現在のスロットが起動可能となるのはシステムの実行中ですが、もう一方のスロットにシステムの(現時点では問題のない)古いバージョン、新しいバージョン、または無効なデータが含まれている可能性があります。現在のスロットの状態にかかわらず、アクティブなスロット(ブートローダーが次回起動時に起動するスロット)、すなわち優先スロットは 1 つです。

各スロットには、ユーザー空間で設定された正常属性もあり、これはスロットが起動可能である場合にのみ関係します。正常とされたスロットでは起動、実行、アップデートができます。正常とされていない起動可能スロットは(何度か起動を試行した後)、ブートローダーによって起動不能としてマークされ、アクティブなスロットは別の起動可能スロットに変更されます。この場合、通常は新規のアクティブ スロットで起動する直前に実行されていたスロットに変更されます。インターフェースの具体的な詳細は、 boot_control.h をご覧ください。

アップデート エンジン デーモン

A/B システム アップデートでは、update_engine と呼ばれるバックグラウンド デーモンを使用して、システムをアップデート後の新しいバージョンで起動する準備をします。このデーモンは次のアクションを実行できます。

  • OTA パッケージの指示どおりに、現在のスロット A/B パーティションから読み取り、使用されていないスロット A/B パーティションにデータを書き込みます。
  • 定義済みのワークフローで boot_control インターフェースを呼び出します。
  • OTA パッケージの指示どおりに、使用されていないスロットのパーティションすべてに書き込んだ後、新しいパーティションからインストール後プログラムを実行します(詳細については、インストール後の手順をご覧ください)。

update_engine デーモンは起動プロセス自体には関係しないため、アップデート中の機能は現在のスロット内の SELinux ポリシーおよび機能に限定されています。システムが新しいバージョンで起動するまで、このポリシーや機能は更新できません。強固なシステムを維持するために、アップデート プロセスとしてパーティション テーブル、現在のスロットのパーティションの内容、出荷時設定へのリセットで消去されない非 A/B パーティションの内容は修正しないでください

アップデート エンジンのソース

update_engine のソースは system/update_engine にあります。A/B OTA dexopt ファイルは、installd とパッケージ管理システムに次のように分割されています。

実際の例については、/device/google/marlin/device-common.mk をご覧ください。

アップデート エンジンのログ

Android 8.x 以前のリリースでは、update_engine ログは logcat およびバグレポートにあります。ファイル システム内で update_engine ログを利用可能にするには、ビルドに次の変更パッチを適用します。

これらの変更により、最新の update_engine ログのコピーが /data/misc/update_engine_log/update_engine.YEAR-TIME に保存されます。現在のログと、直近の 5 件のログが /data/misc/update_engine_log/ に保存されます。ログのグループ ID を持つユーザーは、ファイル システムのログにアクセスできます。

ブートローダーの操作

update_engine(場合によっては他のデーモンも)は boot_control HAL を使用して、ブートローダーに何を起動するかを指示します。一般的なシナリオ例と、それに関連する状態を以下に示します。

  • 通常のケース: 現在のスロット(スロット A または B)からシステムを実行しています。現在までに適用されたアップデートはありません。システムの現在のスロットは起動可能、正常、アクティブなスロットです。
  • アップデート中: システムはスロット B で実行されているため、スロット B は起動可能、正常、アクティブなスロットです。スロット A の内容はアップデート中で、まだ完了していないため、スロット A は起動不能です。この状態で再起動すると、スロット B からの起動が続行されます。
  • アップデートは適用済み、再起動を保留中: システムはスロット B から起動しています。スロット B は起動可能および正常ですが、スロット A はアクティブです(したがって起動可能です)。スロット A はまだ正常と見なされておらず、ブートローダーによってスロット A からの起動が何度か試行されます。
  • システムが新しいアップデート版で再起動: システムは初めてスロット A から起動しています。スロット B は引き続き起動可能かつ正常な状態です。スロット A は起動可能で引き続きアクティブですが、正常ではありません。ユーザー空間デーモンの update_verifier がいくつかのチェックを行った後に、スロット A を正常としてマークします。

ストリーミング アップデートのサポート

ユーザー デバイスの /data に、アップデート パッケージをダウンロードする十分な空き容量がない場合があります。OEM もユーザーも /cache パーティションのスペースを無駄にしたくないため、一部のユーザーはデバイスにアップデート パッケージを保存する場所がないことを理由にアップデートを実施していませんでした。この問題に対処するため、Android 8.0 ではストリーミング A/B アップデートをサポートするようになりました。このアップデートでは、ブロックを /data に保存せず、ダウンロード時に B パーティションに直接書き込みます。ストリーミング A/B アップデートには一時ストレージはほとんど必要なく、100 KiB 程度のメタデータに必要な保存容量だけで済みます。

Android 7.1 でストリーミング アップデートを有効にするには、次のパッチから選択して適用します。

Android 7.1 以降でストリーミング A/B アップデートをサポートするには、上記のパッチが必要です。Google モバイル サービス(GMS)でも、その他のアップデート クライアントでも同様です。

A/B アップデートのライフサイクル

アップデート プロセスは、OTA パッケージ(コード内では「ペイロード」)がダウンロード可能な状態になると開始します。バッテリー残量、ユーザーの操作、充電状況などに応じて、デバイスのポリシーによりペイロードのダウンロードや適用が遅延することがあります。また、アップデートはバックグラウンドで実行されるため、ユーザーにはアップデートが進行中であることはわかりません。つまり、ポリシー、予期しない再起動、ユーザーの操作などにより、アップデート プロセスはいつでも中断される可能性があります。

必要に応じて、OTA パッケージ自体のメタデータが、ストリーミングによるアップデートが可能であることを示します。同じパッケージを非ストリーミング インストールに使用することもできます。サーバーはメタデータを使用して、ストリーミング中であることをクライアントに伝えることができるため、クライアントは OTA を update_engine に正しく引き渡せます。独自のサーバーとクライアントを持つデバイス メーカーは、アップデートがストリーミングされていることをサーバーに識別させる(または、すべてのアップデートがストリーミングされるものとサーバーが判断する)ことで、ストリーミング アップデートを有効にできます。これにより、クライアントはストリーミングのため適切に update_engine を呼び出せます。メーカーは、パッケージがストリーミング バリアントであることを利用して、フレームワーク側への引き渡しをストリーミングにより行うフラグをクライアントに送信できます。

ペイロードが使用可能になると、アップデート プロセスは次のようになります。

ステップ アクティビティ
1 現在のスロット(ソーススロット)がまだ正常とマークされていない場合、markBootSuccessful() により正常とマークされます。
2 使用されていないスロット(ターゲット スロット)は、setSlotAsUnbootable() 関数を呼び出すことにより起動不可としてマークされます。現在のスロットはアップデートの開始時に必ず正常とマークされます。これにより、使用されていないスロットにブートローダーがフォールバックしないようにします。使用されていないスロットには無効なデータが入力されます。システムがアップデートの適用を開始できる状態になると、他の主要コンポーネント(クラッシュ ループの UI など)が破損している場合でも、新しいソフトウェアでこれらの問題を修復することが可能なため、現在のスロットは正常とマークされます。

アップデートのペイロードは、新しいバージョンにアップデートする手順を示した不透明 blob です。アップデート ペイロードは、以下で構成されます。
  • メタデータ: アップデート ペイロードの比較的小さい部分を占めます。メタデータにはターゲット スロットで新しいバージョンを生成および検証するためのオペレーションのリストが含まれます。たとえば、特定の blob を解凍してターゲット パーティション内の特定のブロックに書き込んだり、ソース パーティションから読み取ったデータにバイナリパッチを適用して、ターゲット パーティション内の特定のブロックに書き込むなどのオペレーションです。
  • 追加データ: アップデート ペイロードの大部分を占めます。追加データはオペレーションに関連付けられていて、この例にある圧縮された blob やバイナリパッチから構成されます。
3 ペイロードのメタデータがダウンロードされます。
4 メタデータで定義された各オペレーションについて、順番に、関連データがある場合はメモリにダウンロードされ、オペレーションが適用された後、関連付けられたメモリが破棄されます。
5 パーティション全体が再読み込みされ、予想されるハッシュに対して検証されます。
6 インストール後のステップがある場合は、その手順が実行されます。いずれかのステップの実行中にエラーが発生した場合、アップデートは失敗し、別のペイロードがあった際に再試行されます。上記のすべてのステップが成功した場合、アップデートは成功し、最後のステップが実行されます。
7 使用されていないスロットは、setActiveBootSlot() を呼び出すことによってアクティブとしてマークされます。使用されていないスロットをアクティブとしてマークしても、起動が完了するわけではありません。ブートローダー(またはシステム自体)が正常な状態を読み取れない場合、アクティブなスロットを元に戻すことができます。
8 インストール後の手順(後述)では、古いバージョンを稼働させながら「新しいアップデート」バージョンからプログラムを実行します。この手順は、OTA パッケージで定義されている場合は必須であり、プログラムは終了コード 0 を返す必要があります。それ以外の場合、アップデートは失敗です。
9 システムが新しいスロットで正常に起動し、再起動後のチェックが完了すると、新しい現在のスロット(それまでの「ターゲット スロット」)は、markBootSuccessful() の呼び出しにより正常とマークされます。

インストール後の手順

インストール後の手順が定義されているすべてのパーティションで、update_engine は新しいパーティションを特定の場所にマウントし、マウントされたパーティションに関して OTA で指定されたプログラムを実行します。たとえば、インストール後のプログラムがシステム パーティションで usr/bin/postinstall と定義される場合、使用されていないスロットにあるこのパーティションは、/postinstall_mount などの固定の場所にマウントされ、/postinstall_mount/usr/bin/postinstall コマンドが実行されます。

インストール後の手順を正常に実行するには、古いカーネルで次のことができる必要があります。

  • 新しいファイル システム フォーマットをマウントする。ファイル システムのタイプは変更できません。ただし、圧縮されたファイル システム(SquashFS など)を使用する場合の圧縮アルゴリズムなど、古いカーネルでサポートされている場合を除きます。
  • 新しいパーティションのインストール後のプログラム形式を理解する。ELF(Executable and Linkable Format)バイナリを使用している場合、古いカーネルと互換性があります。たとえば、32 ビットから 64 ビットのアーキテクチャに切り替えた場合に、古い 32 ビットのカーネル上で 64 ビットの新しいプログラムを実行できます。ローダー(ld)が他のパスを使用するか、静的バイナリを作成するように指示された場合、ライブラリは新しいシステム イメージではなく古いシステム イメージから読み込まれます。

たとえば、シェル スクリプトを、先頭に #! マーカーの付いた古いシステムのシェルバイナリで解釈されたインストール後のプログラムとして使用し、新しい環境からライブラリパスをセットアップして、より複雑なバイナリでインストール後プログラムを実行できるようにします。また、インストール後の手順を専用の小さなパーティションから実行すると、メインシステム パーティションのファイル システム形式をアップデートする際に、下位互換性の問題や飛び石アップデートを避けられます。こうすることでファクトリー イメージから最新バージョンに直接アップデートできます。

新しいインストール後のプログラムは、古いシステムで定義された SELinux ポリシーによって制限されます。そのため、インストール後の手順は、特定のデバイスや他のベストエフォート型タスクで仕様上必要なタスクを実行するのに適しています。必要な権限が予想できない再起動前の 1 回限りのバグ修正には、インストール後の手順は適しません

選択したインストール後のプログラムは、postinstall SELinux コンテキストで実行されます。新しくマウントされたパーティション内のファイルはいずれも、新しいシステムで再起動した後の属性にかかわらず、postinstall_file でタグ付けされます。新しいシステムで SELinux 属性を変更しても、インストール後の手順には影響しません。インストール後のプログラムに追加の権限が必要な場合は、インストール後のコンテキストに追加する必要があります。

再起動後の手順

再起動後、update_verifier は dm-verity を使用して完全性チェックをトリガーします。このチェックは、安全なロールバックを妨げるような不可逆的な変更を Java サービスが行わないよう、zygote の前に開始されます。このプロセス中、確認付きブートや dm-verity が破損を検出した場合、ブートローダーやカーネルにより再起動が行われることもあります。チェックが完了したら update_verifier は起動が正常に成功したことをマークします。

update_verifier は、/data/ota_package/care_map.txt にリストされているブロックのみを読み取ります。これは AOSP コードの使用時に A/B OTA パッケージに含まれます。GmsCore などの Java システム アップデート クライアントは、care_map.txt を抽出し、デバイスを再起動する前にアクセス権を設定すると、システムが新しいバージョンで正常に起動した後でその抽出したファイルを削除します。