バインダー 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 をサポートしています。

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 が使用できるようになりました。これは servicemanager の新しいインスタンスであり、/dev/binder の代わりに /dev/vndbinder を使用し、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 の詳細については、Android 8.0 用 SELinuxをご覧ください。

サービス名

以前は、ベンダー プロセスがサービス名を 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;