バインダ IPC を使用する

このページでは、Android 8 におけるバインダ ドライバの変更と、バインダ IPC の使用について説明し、必要な SELinux ポリシーの一覧を示します。

バインダ ドライバの変更

Android 8 以降、Android フレームワークと HAL はバインダを使用して相互に通信するようになりました。この通信によりバインダ トラフィックが大幅に増えるため、Android 8 では、バインダ IPC を高速に保つための改善がいくつか行われています。SoC ベンダーと OEM は、kernel/common プロジェクトの android-4.4 および android-4.9 以上の該当ブランチから直接マージする必要があります。

複数のバインダ ドメイン(コンテキスト)

common-4.4 以上(アップストリームを含む)

フレームワーク(デバイス非依存)とベンダー(デバイス固有)コード間のバインダ トラフィックを明確に区分するために、Android 8 ではバインダ コンテキストという概念が導入されました。各バインダ コンテキストには、固有のデバイスノードとコンテキスト(サービス)マネージャーがあります。コンテキスト マネージャーには、コンテキスト マネージャー自身が属するデバイスノードからのみアクセスできます。また、特定のコンテキストからバインダノードを渡す場合、別のプロセスのみが同じコンテキストからアクセスできるため、各ドメインが完全に分離されます。使用方法の詳細については、vndbindervndservicemanager をご覧ください。

スキャッタ ギャザー

common-4.4 以上(アップストリームを含む)

Android の以前のリリースでは、バインダ呼び出しのすべてのデータは、次のように 3 回コピーされていました。

  • 呼び出しプロセスで、Parcel にシリアル化するのに 1 回
  • カーネル ドライバで、Parcel をターゲット プロセスにコピーするのに 1 回
  • ターゲット プロセスで、Parcel のシリアル化を解除するのに 1 回

Android 8 では、スキャッタ ギャザー最適化を使用して、コピー回数を 3 回から 1 回に減らしています。最初にデータを Parcel にシリアル化する処理は行いません。データは元の構造とメモリ レイアウトのままであり、ドライバは直ちにデータをターゲット プロセスにコピーします。データがターゲット プロセスに挿入された後、構造とメモリ レイアウトが同じなので、もう 1 回コピーしなくてもデータを読み取ることができます。

細粒度ロック

common-4.4 以上(アップストリームを含む)

以前の Android リリースでは、バインダ ドライバはグローバル ロックを使用して、重要なデータ構造への同時アクセスを防いでいました。ロックの競合は最小限に抑えられていましたが、優先度の低いスレッドがロックを取得してプリエンプトされると、同じロックを取得する必要のある優先度の高いスレッドが大幅に遅延することが主な問題でした。これにより、プラットフォームでジャンクが発生していました。

この問題を解決するための最初の試みとして、グローバル ロックを保持したまま、プリエンプションを無効にしました。しかし、これは真の解決策というよりもハッキングであり、結局はアップストリームで拒否され、放棄されました。その後の試みでは、ロックの粒度を細かくすることに焦点が当てられ、そのバージョンの 1 つが 2017 年 1 月から Pixel デバイスで実行されています。変更の大部分は公開されましたが、その後のバージョンで大幅な改善が行われました。

細粒度ロックの実装における小さな問題を特定した後、異なるロック アーキテクチャを使用するより良い解決策を考案し、すべての共通カーネル ブランチにその変更をサブミットしました。この実装はさまざまなデバイスで引き続きテストされていますが、未解決の問題は見つかっていないため、Android 8 を搭載したデバイスでは推奨される実装となっています。

リアルタイム優先度の継承

common-4.4 および common-4.9(アップストリームは近日提供予定)

バインダ ドライバは、常に nice の優先度継承をサポートしてきました。リアルタイム優先度で実行される Android のプロセスが増えているため、場合によっては、リアルタイムのスレッドがバインダ呼び出しを行う際は、その呼び出しを処理するプロセス内のスレッドもリアルタイム優先度で実行されることが理にかなっています。こうしたユースケースをサポートするため、Android 8 では、バインダ ドライバにリアルタイム優先度の継承が実装されました。

トランザクション レベルの優先度継承に加えて、ノード優先度継承では、ノード(バインダ サービス オブジェクト)で、このノードへの呼び出しを実行する際の最小優先度を指定できます。nice 値を使用したノード優先度継承は Android の以前のバージョンですでにサポートされていましたが、Android 8 では、リアルタイム スケジューリング ポリシーのノード継承が新たにサポートされています。

ユーザー空間の変更

Android 8 には、共通カーネルで現在のバインダ ドライバを操作するために必要なユーザー空間の変更がすべて含まれていますが、1 つだけ例外があります。/dev/binder のリアルタイム優先度の継承を無効にする元の実装では、ioctl を使用していました。その後の開発では、優先度継承の制御を、(コンテキストごとではなく)バインダモードごとの、より粒度の細かい方法に切り替えました。したがって、ioctl は Android の共通ブランチにはなく、共通カーネルにサブミットされています

この変更の影響で、リアルタイム優先度の継承は、デフォルトではすべてのノードで無効になっています。Android パフォーマンス チームは、hwbinder ドメインのすべてのノードについて、リアルタイム優先度の継承を有効にすることが有益であると考えています。それと同じ効果を得るには、ユーザー空間でこちらの変更を選択してください。

共通カーネルの SHA

バインダ ドライバに必要な変更を取得するには、適切な SHA に同期します。

  • Common-3.18
    cc8b90c121de ANDROID: バインダ: 復元時に prio 権限を確認しません。
  • Common-4.4
    76b376eac7a2 ANDROID: バインダ: 復元時に prio 権限を確認しません。
  • Common-4.9
    ecd972d4f9b5 ANDROID: バインダ: 復元時に prio 権限を確認しません。

バインダ IPC と連携する

従来、ベンダー プロセスはバインダ プロセス間通信(IPC)を使用して通信していました。Android 8 では、/dev/binder のデバイスノードがフレームワーク プロセス専用となり、ベンダー プロセスがそれにアクセスできなくなりました。ベンダー プロセスは /dev/hwbinder にアクセスできますが、HIDL を使用するように AIDL インターフェースを変換する必要があります。引き続きベンダー プロセス間で AIDL インターフェースを使用したいベンダー向けに、Android では以下のバインダ IPC をサポートしています。Android 10 では、安定版の AIDL によりすべてのプロセスが /dev/binder を使用できると同時に、安定性の問題の解決により HIDL と /dev/hwbinder の解決を保証します。安定版 AIDL の使用方法については、HAL 用の AIDL をご覧ください。

vndbinder

Android 8 は、ベンダー サービスで使用する新しいバインダ ドメインをサポートしています。これには /dev/binder の代わりに /dev/vndbinder を使用してアクセスします。/dev/vndbinder が追加されたため、現在 Android には次の 3 つの IPC ドメインがあります。

IPC ドメイン 説明
/dev/binder AIDL インターフェースを使用した、フレームワーク プロセスとアプリプロセス間の IPC
/dev/hwbinder HIDL インターフェースを使用した、フレームワーク プロセスとベンダープロセス間の IPC
HIDL インターフェースを使用した、ベンダープロセス間の IPC
/dev/vndbinder AIDL インターフェースを使用した、ベンダープロセス間の IPC

/dev/vndbinder を選択できるようにするには、カーネル構成アイテム CONFIG_ANDROID_BINDER_DEVICES"binder,hwbinder,vndbinder" に設定されていることを確認します(これは Android の共通カーネルツリーのデフォルトです)。

通常、ベンダー プロセスはバインダ ドライバを直接開かず、代わりに libbinder ユーザー空間ライブラリをリンクして、このライブラリによりバインダ ドライバを開きます。::android::ProcessState() のメソッドを追加すると、libbinder のバインダ ドライバが選択されます。ベンダー プロセスは、ProcessState, IPCThreadState を呼び出すに、または一般的なバインダ呼び出しを行う前に、このメソッドを呼び出す必要があります。これを使用するには、ベンダー プロセス(クライアントとサーバー)の main() の後に、次の呼び出しを配置します。

ProcessState::initWithDriver("/dev/vndbinder");

vndservicemanager

以前は、バインダー サービスは servicemanager で登録されていましたが、他のプロセスによって取得される可能性がありました。Android 8 では、servicemanager はフレームワークとアプリプロセスでのみ使用されるようになり、ベンダー プロセスはアクセスできなくなりました。

ただし、ベンダー サービスでは vndservicemanager が使用できるようになりました。これは、/dev/binder の代わりに /dev/vndbinder を使用する servicemanager の新しいインスタンスであり、servicemanager フレームワークと同じソースからビルドされています。ベンダー プロセスは、vndservicemanager と通信するように変更する必要はありません。ベンダー プロセスが /dev/vndbinder を開くと、サービス ルックアップは自動的に vndservicemanager に移動します。

vndservicemanager バイナリは Android のデフォルトのデバイス makefile に含まれています。

SELinux ポリシー

バインダ機能を使用して相互に通信するベンダー プロセスには、以下が必要です。

  1. /dev/vndbinder へのアクセス。
  2. バインダ {transfer, call}vndservicemanager へのフック。
  3. ベンダーのバインダ インターフェース経由でベンダー ドメイン B を呼び出すベンダー ドメイン A の binder_call(A, B)
  4. vndservicemanager{add, find} サービスに対する権限。

要件 1 と 2 を満たすには、次のように vndbinder_use() マクロを使用します。

vndbinder_use(some_vendor_process_domain);

要件 3 を満たすには、バインダを介して通信を行う必要があるベンダー プロセス A および B の binder_call(A, B) をそのままにします。名前の変更は必要ありません。

要件 4 を満たすには、サービス名、サービスラベル、ルールの処理方法を変更する必要があります。

SELinux の詳細については、Android における Security-Enhanced Linux をご覧ください。Android 8.0 における SELinux の詳細については、SELinux for Android 8.0 をご覧ください。

サービス名

以前は、ベンダー プロセスがサービス名を service_contexts ファイルに登録し、そのファイルにアクセスするための対応するルールを追加していました。device/google/marlin/sepolicy からの service_contexts ファイルの例を次に示します。

AtCmdFwd                              u:object_r:atfwd_service:s0
cneservice                            u:object_r:cne_service:s0
qti.ims.connectionmanagerservice      u:object_r:imscm_service:s0
rcs                                   u:object_r:radio_service:s0
uce                                   u:object_r:uce_service:s0
vendor.qcom.PeripheralManager         u:object_r:per_mgr_service:s0

Android 8 では、代わりに vndservicemanagervndservice_contexts ファイルを読み込みます。vndservicemanager に移行する(古い service_contexts ファイルにすでに存在する)ベンダー サービスは、新しい vndservice_contexts ファイルに追加する必要があります。

サービスラベル

以前は、u:object_r:atfwd_service:s0 などのサービスラベルは service.te ファイルで定義されていました。例:

type atfwd_service,      service_manager_type;

Android 8 では、タイプを vndservice_manager_type に変更し、ルールを vndservice.te ファイルに移動する必要があります。例:

type atfwd_service,      vndservice_manager_type;

servicemanager のルール

以前は、ルールにより、servicemanager のサービスを追加または検索するためのアクセス権をドメインに付与していました。例:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;

Android 8 では、このようなルールはそのまま変更せず、同じクラスを使用できます。例:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;