blog

バインダー機構をマスターする?まずはこれらの重要なクラスを知ろう!

導入として、これらのクラスの役割の一般的なアイデアを得るためにバインダーの全体的なアーキテクチャを簡単に確認してみましょう。 クライアントがAMSを通して取得するリモートバインダのエンティティは、一般...

Apr 18, 2020 · 9 min. read
シェア
  1. ProcessState
  2. IPCThreadState
  3. BpBinder
  4. BinderProxy

binder

はじめに、バインダーの全体的なアーキテクチャーを簡単におさらいして、これらのクラスの役割について大まかに理解しておきましょう。

2つのアプリケーション間のより典型的なIPC通信フロー:

ProcessStateは プロセス・シングルトンで、バインダー・ドライバとmmapのオープンを担当します。IPCThreadStateは スレッド・シングルトンで、特定のコマンドのためにバインダー・ドライバとの通信を担当します。

以下は、これらのクラスを徹底的に理解するために、ソースコードと合わせて詳細に分析したものです!

ProcessState

ProcessStateは、各アプリケーション・プロセスのBinder操作を管理することに特化しています。 ProcessStateのインスタンスは同じプロセス内に1つだけ存在し、Binderデバイスとメモリ・マップはProcessStateオブジェクトが生成されたときにのみオープンされます。関連するコードは次のとおりです:

///frameworks/native/libs/binder/ProcessState.cpp sp<ProcessState> ProcessState::self(){ Mutex::Autolock _l(gProcessMutex); if (gProcess != NULL) { //ProcessStateを作成した場合は、その値を返すだけでよい。 return gProcess; } gProcess = new ProcessState; return gProcess; }
#define BINDER_VM_SIZE ((1*) - ()) #define DEFAULT_MAX_BINDER_THREADS 15 ProcessState::ProcessState() : mDriverFD(open_driver()) //バインダーデバイスを開く , mVMStart(MAP_FAILED) //MAPに初期化する_FAILED,マッピング成功後の変更点 , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER) , mThreadCountDecrement(PTHREAD_COND_INITIALIZER) , mExecutingThreadsCount(0) , mMaxThreads(DEFAULT_MAX_BINDER_THREADS) //binder スレッドの最大数 , mStarvationStartTimeMs(0) , mManagesContexts(false) , mBinderContextCheckFunc(NULL) , mBinderContextUserData(NULL) , mThreadPoolStarted(false) , mThreadPoolSeq(1){ if (mDriverFD >= 0) { //バインドライバが正常に開かれている // バインドライバにアプリケーションプロセスの仮想メモリのブロックをマッピングし、メモリのこのブロック上のデータを通信する mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); if (mVMStart == MAP_FAILED) { //マッピングの失敗処理 ALOGE("Using /dev/binder failed: unable to mmap transaction memory. "); close(mDriverFD); mDriverFD = -1; } } }

ProcessStateのコンストラクタは、open_driver()を呼び出してバインダー・デバイスをオープンしたり、バインダー・スレッドの最大数を初期化したり、BINDER_VM_SIZEメモリをバインダー・ドライバにマッピングしたりするなど、多くの重要な変数を初期化します。

wp<IBinder> ProcessState::getWeakProxyForHandle(int32_t handle){ wp<IBinder> result; AutoMutex _l(mLock); //IBinderがすでに作成されているかどうかを調べる handle_entry* e = lookupHandleLocked(handle); if (e != NULL) { IBinder* b = e->binder; if (b == NULL || !e->refs->attemptIncWeak(this)) { b = new BpBinder(handle); //もしまだなら、新しいBpBinderを作ろう。 result = b; e->binder = b; if (b) e->refs = b->getWeakRefs(); } else { result = b; e->refs->decWeak(this); } } return result; }

lookupHandleLocked() メソッドは、取得したい IBinder がすでにプロセス内で作成されているかどうかを調べ、作成されていない場合は作成します。lookupHandleLocked() は内部的に Vector を使用して、作成された IBinder を保持します:

Vector<handle_entry> mHandleToObject; struct handle_entry{ IBinder* binder; RefBase::weakref_type* refs; }

上のコードにあるように、各IBinderオブジェクトはhandle_entry構造体を介して格納されます。これは、ProcessStateにすべてのIBinderオブジェクトのグローバルリストがあることを意味します。

IPCThreadState

ProcessStateはプロセスに対応し、プロセス内シングルトンであり、IPCThreadStateはスレッドに対応し、スレッドシングルトンです。

ProcessState はバインダードライバをオープンし、mmap マッピングを行い、ioctl()関数を呼び出しますが、主に初期設定のためのものです。具体的なBR_TRANSACTIONなどのコマンドはIPCThreadStateが実行し、上位層がコマンドを渡すとそのtransact関数を呼び出しますが、その流れは以下のようになります:

status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){ //データが有効かどうかをチェックする status_t err = data.errorCheck(); if (err == NO_ERROR) { //mOutにデータを詰め込む err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); } if ((flags & TF_ONE_WAY) == 0) { //一方通行の呼び出しではなく、応答を待つ必要がある if (reply) { err = waitForResponse(reply); } else { Parcel fakeReply; err = waitForResponse(&fakeReply); } } else { //one way 電話して、返事を待ってはいけない! err = waitForResponse(NULL, NULL); } return err; }

IPCThreadStateは2つのParcelデータmInとmOutを持ち、mInはどこかから読み込まれたデータを格納するために使用され、mOutはどこかに書き込まれるデータを格納するために使用されます。 writeTransactionData()メソッドでは、データはmOutに格納され、バインダードライバに書き込まれる準備が整います。

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){ uint32_t cmd; int32_t err; while (1) { //バインドライバにデータを書き込むためにtalkWithDriverへのさらなる呼び出しが行われる if ((err=talkWithDriver()) < NO_ERROR) break; err = mIn.errorCheck(); //データの有効性をチェックする if (err < NO_ERROR) break; if (mIn.dataAvail() == 0) continue; //データの有効性をチェックする cmd = (uint32_t)mIn.readInt32(); //バインダードライバーからコマンドを取得する switch (cmd) { //コマンドを処理する case BR_TRANSACTION_COMPLETE:{...} case BR_DEAD_REPLY:{...} case BR_FAILED_REPLY:{...} case BR_ACQUIRE_RESULT:{...} case BR_REPLY:{...} default: //その他のコマンドは、executeCommandメソッドで処理される。 err = executeCommand(cmd); if (err != NO_ERROR) goto finish; break; } } return err; }
case BR_TRANSACTION_COMPLETE: if (!reply && !acquireResult) goto finish; break;

transact()メソッドでは、片方向呼び出しの場合、replyとacquireResultの両方にNULLが渡されるため、上記の条件が真となり、バインダードライバからの応答を待たずにループを抜けることができます。

ここまでで、transact()からwaitForResponse()までは、送信するデータが準備され、その後のバインダードライバからの返信が処理されていますが、実際にバインダードライバにデータを書き込むコードはまだ見ていません。このメソッドでは3つのことが行われます:

  1. バインダーの準備_write_read
  2. バインドライバの書き込み
  3. ドライバの応答を処理

talkWithDriver()コードを3つの対応する部分に単純化すると、1つ目はbinder_write_readデータを準備することです:

status_t IPCThreadState::talkWithDriver(bool doReceive){ binder_write_read bwr; //binder ドライバが受け入れるデータ形式 const bool needRead = mIn.dataPosition() >= mIn.dataSize(); const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0; bwr.write_size = outAvail; //書き込むデータ量 bwr.write_buffer = (uintptr_t)mOut.data(); //書き込むデータ // This is what we'll read. if (doReceive && needRead) { bwr.read_size = mIn.dataCapacity(); //読み込むデータ量 bwr.read_buffer = (uintptr_t)mIn.data(); //読み取りデータが格納されているメモリ空間 } else { bwr.read_size = 0; bwr.read_buffer = 0; } // 読み書きする必要がない場合は、単に if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

binder_write_readは、バインダードライバーとユーザーランドで共有され、読み込みと書き込みの操作を保存します。 バインダードライバーは、binder_write_readに依存して、データを読み込むか書き込むかを決定します:その内部変数read_size>0は読み込みデータを意味し、write_size>0は書き込みデータを意味します。両方が0より大きければ、まず書き込みを行い、次に読み込みを行います。

binder_write_readの準備ができたら、バインダードライバに書き込む方法を見てみましょう:

ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)

対応する呼び出しはバインダードライバのbinder_ioctl()関数で、ここでは拡張していません:

if (bwr.write_consumed > 0) { //正常にデータを書き込んだ if (bwr.write_consumed < mOut.dataSize()) mOut.remove(0, bwr.write_consumed); else mOut.setDataSize(0); } if (bwr.read_consumed > 0) { //正常にデータを読み取る mIn.setDataSize(bwr.read_consumed); mIn.setDataPosition(0); } return NO_ERROR; }

bwr.write_consumed>0は、バインダードライバがmOutのデータを消費したことを意味するので、この部分の処理データを削除します。bwr.read_consumed>0は、バインダードライバがbwr.read_bufferで指定されたメモリアドレス、つまりmInにデータを正常に返したことを意味します。がmInになっているので、mInを修正する必要があります。

ここでは、talkWithDriverが実行され、読み込まれたデータがmInに入れられます。これは、上記のwaitForResponse()メソッドのロジックに対応しており、mInからデータを受け取ります。

BpBinder

new BpBinder(handle)
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)

1つはハンドルがBpBinderに渡されて保持されていること、もう1つはIPCThreadStateトランザクトメソッドが呼ばれたときにハンドルが渡される必要があることです。 これは何を意味するのでしょうか?ということですが、これはあくまで推測です。 これが本当かどうか、コンストラクタから始まるBpBinderのソースコードを見てみましょう:

BpBinder::BpBinder(int32_t handle) : mHandle(handle) , mAlive(1) , mObitsSent(0) , mObituaries(NULL) ALOGV("Creating BpBinder %p handle %d ", this, mHandle); extendObjectLifetime(OBJECT_LIFETIME_WEAK); IPCThreadState::self()->incWeakHandle(handle); }

スマート・ポインタのOBJECT_LIFETIME_WEAKコンフィギュレーションは、BpBinderオブジェクトを表す強カウンタと弱カウンタが両方とも値ゼロの場合にのみ破棄されるように設定されています。ハンドルの値は内部変数mHandleによって保持され、BpBinderのtransactメソッドで使用されることもわかります:

status_t BpBinder::transact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){ if (mAlive) { status_t status = IPCThreadState::self()->transact( mHandle, code, data, reply, flags); if (status == DEAD_OBJECT) mAlive = 0; return status; } return DEAD_OBJECT; }

IPCThreadStateのtransactメソッドを内部的に呼び出しており、記述が正しいことが確認できます。

BinderProxy

この結論を検証するためのソースコードを以下に示します。 ServiceManager.javaでサービスマネージャを取得するコードは以下の通りです:

 private static IServiceManager getIServiceManager() {
 if (sServiceManager != null) {
 return sServiceManager;
 }
 // Find the service manager
 sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
 return sServiceManager;
 }
 /**
 * Return the global "context object" of the system. This is usually
 * an implementation of IServiceManager, which you can use to find
 * other services.
 */
 public static final native IBinder getContextObject();

このメソッドのネイティブ実装はandroid_util_Binder.cppにあります:

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz){ sp<IBinder> b = ProcessState::self()->getContextObject(NULL); return javaObjectForIBinder(env, b); }

ProcessStateのgetContextObject()メソッドは以下の通りです:

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/){ return getStrongProxyForHandle(0); }

BinderProxy→BpBinderで呼び出されるjavaObjectForIBinder()と対になるのは、android_util_Binder.cppのibinderForJavaObject()メソッドです。

最後に

忍耐とブレークスルーのその単一のポイントに絶え間ない限り、困難を恐れてはいけない、洗練を繰り返し、最も複雑なメカニズムであっても、ただの紙の虎です!

Read next

データベース・トランザクションの4つの主な特徴とトランザクション照合の分離レベル

トランザクションの4つの主な特徴\n原子性\n多くの場合、トランザクションの実行が失敗した場合、ロールバックする必要があると言われますが、実際には、これはトランザクションの原子性であり、完全なトランザクションは、1つ以上の障害が発生した場合、ロールバックする必要があります。\n\n分離

Apr 18, 2020 · 6 min read