ソースコードの観点からAndroidにおけるBinderメカニズムの前因と結果を分析する


前にも「linuxの下のsocketプログラミングをゼロから学ぶ」という文章を述べましたが、主にプロセス通信の観点からlinuxの中のsocketの開発に拡張されています.この文章は依然としてプロセス通信の観点からAndroidにおけるプロセス通信メカニズムを分析している.
なぜAndroidでbinder通信メカニズムを使うのですか?
linuxにおけるプロセス通信には、パイプ、メッセージキュー、socketメカニズムなど、多くの方法が知られています.socketはよく知られていますが、一般的なインタフェースとして、通信オーバーヘッドが大きく、データ伝送効率が低く、主にネットワーク間のプロセス間通信やローカルの低速通信に使用されています.メッセージキューとパイプはいずれもストレージ転送モードを採用しており、このモードのデータ転送は2回のメモリコピーを経なければならないため、送信者のキャッシュ領域からカーネルが開いたキャッシュ領域にコピーし、カーネルから受信者のキャッシュ領域にコピーする.従来のipcにはセキュリティ対策がなく、2つのプロセスの間で相手のアイデンティティを識別することはできませんが、Androidではアプリケーションごとにインストール後にuidが割り当てられているので、このアイデンティティも保障され、より安全です.安全と効率を保障するために、Androidは新しいipc通信メカニズム、すなわちbinderを提供しています.
binder通信モデル
プロセス間通信はClient-serverモードとして簡単に理解でき、binderメカニズムのAndroidシステムにおけるモデルは以下の通りである.
  • Clientは、server側のproxyオブジェクトを取得する.
  • Clientはproxyオブジェクトを呼び出すことによってserverに要求を送信する.
  • proxyオブジェクトはbinderデバイスノードを介してClient要求情報をlinuxカーネル空間に送信し、binder駆動によって取得してサービスプロセスに送信する.
  • サービスプロセスはClient要求を処理し、linuxカーネルのbinderドライバによって結果をproxyオブジェクトに返す.
  • クライアントはproxyの戻り結果を受信する.

  • Clientのスレッドがbinder駆動に入ると、カーネルはclientのスレッドを保留し、結果が戻るまでサービスプロセスに入ります.Clientスレッドは起動されます.したがって、このプロセスは「スレッド移行」に似ています.つまり、スレッドが別のプロセスに入って結果を持って戻ります.
    binder組成構造
  • Binderドライバbinderは、カーネル内の1つの文字ドライバデバイスが/dev/binderにある.このデバイスはAndroidシステムIPCの核心部分であり、クライアントのサービスエージェントはサーバに要求を送信するために使用され、サーバも処理結果をクライアントのサービスエージェントオブジェクトに返す.この部分では、Androidでは、Binder駆動の操作をIPCThreadStateオブジェクトでカプセル化しています.
  • Service Managerというものは主に管理サービスに使われています.Androidで提供されるシステムサービスは、サービスマネージャを介して自分自身を登録し、サービス管理チェーンテーブルに自分自身を追加し、クライアントにサービスを提供します.クライアントが特定のシステム・サービス・エンドと通信する場合は、サービス・マネージャに必要なサービスを問い合せて取得する必要があります.サービスマネージャは、システムサービスオブジェクトの管理センターであることがわかります.
  • サービス(サーバ)は、SDKサーバではなくSystemサーバを指し、クライアントにサービスを提供することを強調する必要がある.
  • クライアントとは、一般にAndroidシステム上のアプリケーションを指す.サーバ内のサービスを要求できます.
  • エージェントオブジェクトとは、生成されたサーバエージェントクラスオブジェクトをクライアントアプリケーションで取得することを意味する.アプリケーションの観点からエージェントオブジェクトとローカルオブジェクトに差はなく、メソッドを呼び出すことができます.メソッドは同期され、対応する結果が返されます.

  • ソースからMediaPlayerServiceを分析する
    他のbinderメカニズムを紹介するブログ記事と同様に、次はMediaPlayerServiceの観点からbidner(Androidという本を深く理解することをお勧めします)を紹介します.ソースコードはframeworkbaseMediaMediaServerMain_mediaserver.cpp中.MPSを選んだのは、このサービスがオーディオ、撮像など多くの重要なサービスにかかわっているからです.
    int main(int argc, char** argv)
    {
        //       ,             ,             
        sp<ProcessState> proc(ProcessState::self());
    
        sp<IServiceManager> sm = defaultServiceManager();
    
        LOGI("ServiceManager: %p", sm.get());
    
        waitBeforeAdding( String16("media.audio_flinger") );
    
        AudioFlinger::instantiate();
    
        waitBeforeAdding( String16("media.player") );
    
        //   MediaPlayerService,
        MediaPlayerService::instantiate();
    
        waitBeforeAdding( String16("media.camera") );
    
        CameraService::instantiate();
    
        waitBeforeAdding( String16("media.audio_policy") );
    
        AudioPolicyService::instantiate();
    
        ProcessState::self()->startThreadPool();
    
        IPCThreadState::self()->joinThreadPool();
    
    }
    

    まずProcessStateのコンストラクション関数を見てみましょう
    ProcessState::ProcessState()
    : mDriverFD(open_driver())//   binder
    , mVMStart(MAP_FAILED) //          
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
    {
    
        ......
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        .....
    }
    

    上記ProcessStateコンストラクション関数コードは、まずopen_を呼び出すdriver()メソッド、入ってみてください.
    static int open_driver()
    {
        if (gSingleProcess) {
            return -1;
        }
    
        //   binder  ,                  
        int fd = open("/dev/binder", O_RDWR);   
        if (fd >= 0) {
            //             。  FD_CLOEXEC  0,  execve    ,      。     。
            fcntl(fd, F_SETFD, FD_CLOEXEC);
            int vers;
            #if defined(HAVE_ANDROID_OS)
            status_t result = ioctl(fd, BINDER_VERSION, &vers);
            #else
            status_t result = -1;
            errno = EPERM;
            #endif
            if (result == -1) {
                LOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
                close(fd);
                fd = -1;
            }
            if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
                LOGE("Binder driver protocol does not match user space protocol!");
                close(fd);
                fd = -1;
            }
        #if defined(HAVE_ANDROID_OS)
        size_t maxThreads = 15;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
        if (result == -1) {
            LOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
        }
        #endif
    
        } else {
            LOGW("Opening '/dev/binder' failed: %s
    ", strerror(errno)); } return fd; }

    open_driverはbinderデバイス/dev/binderファイルを開き、正常に開くとファイル記述子を返し、fcntl関数を呼び出してさっき開いたデバイスファイルの性質を変更します.次にioctl関数を呼び出し、ioctlはデバイスドライバでデバイスのI/Oチャネルを管理する関数です.I/Oチャネルの管理とは、シリアルポートの伝送ボーレート、モータの回転数など、デバイスの特性を制御することである.BINDER_SET_MAX_THREADSはデバイスに対するユーザプログラムの制御命令であり、maxThreadsは補足パラメータであり、この文の意味はbinderドライバに、このファイル記述子がサポートする最大スレッド数を教えることである.コンストラクション関数でmmap関数を使用してファイル記述子をメモリにマッピングし、このメモリを使用してデータを受信します.コンストラクション関数では、2つの重要なことをしました.
  • は/dev/binderデバイスファイルを開き、カーネルのbinder駆動とインタラクティブなチャネルを得ることに相当するデバイスファイルに関連するファイル記述子を取得した.
  • は、mmap関数を使用してファイル記述子をメモリにマッピングし、このメモリを使用してデータを受信する.

  • 次にProcessState::self()に戻ってみましょう.
    sp<ProcessState> ProcessState::self()
    {
        if (gProcess != NULL) return gProcess;
        AutoMutex _l(gProcessMutex);
        if (gProcess == NULL) gProcess = new ProcessState;
        return gProcess;
    }
    

    このselfはProcessStateオブジェクトを返しただけで、ProcessStateは現在のプロセスとbinderデバイスの通信時の状態を維持するために主に単一のモードで設計されています.
    binder駆動とのインタラクティブチャネルができたら、次に何をしましたか?次のコードを見てみましょう.
    sp<IServiceManager> sm = defaultServiceManager();
    

    defaultServiceManagerのビジネス実装はIServiceManagement.cppでは、プロトタイプは以下の通りです.
    sp<IServiceManager> defaultServiceManager(){
        if (gDefaultServiceManager != NULL)return gDefaultServiceManager;
        AutoMutex _l(gDefaultServiceManagerLock);
        if (gDefaultServiceManager == NULL) {
            gDefaultServiceManager = interface_cast<IServiceManager>(ProcessState::self()->getContextObject(NULL));
        }
        return gDefaultServiceManager;
    }
    

    最も重要なのはこのコードです.
    gDefaultServiceManager = interface_cast<IServiceManager>(ProcessState::self()->getContextObject(NULL))
    

    ProcessStateに戻ってgetContextObjectの実装を見てみましょう
    sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)
    {
        if (supportsProcesses()) {
            return getStrongProxyForHandle(0);
        } else {
            return getContextObject(String16("default"), caller);
        }
    }
    

    supportsProcesses()は、現在のデバイスがプロセスをサポートしているかどうかということであり、これは間違いなく、実際のデバイスがサポートされているに違いないので、次はgetStrongProxyForHandle(0)メソッドに進むに違いない.
    sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
    {
        sp<IBinder> result;
    
        AutoMutex _l(mLock);
    
        handle_entry* e = lookupHandleLocked(handle);
    
        if (e != NULL) {
            IBinder* b = e->binder;
            if (b == NULL || !e->refs->attemptIncWeak(this)) {
                b = new BpBinder(handle); //     BpBinder
                e->binder = b;//         binder
                if (b) e->refs = b->getWeakRefs();
                result = b;
            } else {
                // This little bit of nastyness is to allow us to add a primary
                // reference to the remote proxy when this team doesn't have one
                // but another team is sending the handle to us.
                result.force_set(b);
                e->refs->decWeak(this);
            }
        }
        //    new BpBinder(0); /
        return result;
    }
    

    lookupHandleLocked関数は対応するリソースを検索するために使用され、最初はなかったに違いないので、b=nullのブランチを歩き、このブランチでhandler=0をパラメータとしてBpBinderを新規作成し、このbinderをhandle_に割り当てました.entryのbinderプロパティは,最後にこのBpBinderオブジェクトを返す,すなわちnew BpBinder(0)を返す.この中にはhandleをentryリソースアイテムは現在のプロセスに関連付けられています.そうしないと、ここではなぜBpBinderとこのリソースアイテムをバインドするのでしょうか.
    ここには2つの重要なものがあります.BpBinderとBBinderです.彼ら2つはペアになって現れています.BpBinderはクライアントとサービス側のエージェントクラスで、BBinderはBpBinderの対立の一端で、クライアントが対話する目的を表しています.言い換えれば、BpBinderがクライアントであれば、BBinderはサービス側である.
    次にgDefaultServiceManager=interface_cast(ProcessState::self()->getContextObject(NULL))は、次のコードに変換されます.
    gDefaultServiceManager = interface_cast<IServiceManager>(BpBinder)
    

    このインターフェイスcastメソッドの説明は以下の通りです.
    template<typename INTERFACE>
    inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
    {
        return INTERFACE::asInterface(obj);
    }
    

    interface_キャストはテンプレートメソッドで、INTERFACEはIserviceManagerを表し、IserviceManagementを呼び出します.AsInterfaceメソッドでは、このメソッドでは、BpBinderオブジェクトを使用してBpManagerServiceオブジェクトを構築しています.BpBinderとBBinderの両方が相互通信の関係であることを知っています.では、なぜここにBpManagerServiceが来て、BpManagerServiceはIServiceManagerのビジネス関数を実現しています.また、BpManagerServiceには、BpBinderオブジェクトを指すmRemoteオブジェクトがあります.このように,BpManagerServiceはビジネス関数を実現し,サービス側と通信を行う代表者もいる.次にサービスの登録を分析します.
    MediaPlayerServicesでcpp:
    void MediaPlayerService::instantiate() {
        // defaultServiceManager       BpManagerService, IServiceManager   
        defaultServiceManager()->addService(String16("media.player"), new   MediaPlayerService());
    }
    

    defaultServiceManager()は、IServiceManagerのビジネス・メソッドを実装したBpManagerServiceを返します.そのため、BpManagerServiceでaddServiceの実装を見てみましょう.
    virtual status_t addService(const String16& name, const sp<IBinder>& service)
    {
        Parcel data, reply; //    
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        //       ,         BpServiceManager
        //   remote      mRemote,   BpBinder  
        //      ,   BpBinder   transact  ,              BpBinder     
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
    
        //   addService              ,     ,        ,         
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }
    

    Parcelはプロセス間で転送されるパケットと理解でき、dataは私たちが送信するパケットであり、replyはサービスが返してきたパケットであり、データがパッケージ化された後、remote()関数のtransactメソッドによって実行され、remote()メソッドはBpManagerice ServiceのmRemoteオブジェクトを返し、このオブジェクトは前のBpBinderオブジェクトを指し、サービスとの通信はBpBinderオブジェクトに任せて行われます.次に、BpBinderでこの方法の実装を見てみましょう.
    status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply,   uint32_t flags)
    {
        // Once a binder has died, it will never come back to life.
        if (mAlive) {
            status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
            if (status == DEAD_OBJECT) mAlive = 0;
            return status;
        }
    
        return DEAD_OBJECT;
    }
    

    BpBinderには実際の仕事、つまり表象は何もありません.この実際の仕事はIPCThreadStateのtransactに任せられています.IPCThreadStateこそプロセスの中で本当に働いている仲間で、IPCThreadStateに行きます.cppで見てみましょう.
    status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
    {
    ......
    
    if (err == NO_ERROR) {
        LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
            (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
        // handle  0,        
        // BC_TRANSACTION  :      binder          。
        //  binder             BR_  。
    
        //     ,        
        //                  mOut ,      
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }
    
    ........
    if ((flags & TF_ONE_WAY) == 0) {
       ............
        #endif
        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        #if 0
        .........
    } else {
        err = waitForResponse(NULL, NULL);
    }
    
    return err;
    }
    

    writeTransactionDataは、データを1つの場所に書き込むだけのように見えますが、原型は以下の通りです.
    status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
    {
        binder_transaction_data tr;
    
        //handle 0,      
        tr.target.handle = handle;
        //    
        tr.code = code;
        tr.flags = binderFlags;
    
        const status_t err = data.errorCheck();
        if (err == NO_ERROR) {
            tr.data_size = data.ipcDataSize();
            tr.data.ptr.buffer = data.ipcData();
            tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
            tr.data.ptr.offsets = data.ipcObjects();
        } else if (statusBuffer) {
            tr.flags |= TF_STATUS_CODE;
            *statusBuffer = err;
            tr.data_size = sizeof(status_t);
            tr.data.ptr.buffer = statusBuffer;
            tr.offsets_size = 0;
            tr.data.ptr.offsets = NULL;
        } else {
            return (mLastError = err);
        }
    
        mOut.writeInt32(cmd);
        mOut.write(&tr, sizeof(tr));
    
        return NO_ERROR;
    }
    

    この方法は命令をoutに直接書くだけで、このメッセージを送信していないので、waitForResponse方法を引き続き見て、データの送信と受信の仕事を担っているのは間違いありません.
    status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
    {
    int32_t cmd;
    int32_t err;
    
    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;
    
        cmd = mIn.readInt32();
    
        IF_LOG_COMMANDS() {
            alog << "Processing waitForResponse Command: "
                << getReturnString(cmd) << endl;
        }
    
        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
    
        case BR_DEAD_REPLY:
            err = DEAD_OBJECT;
            goto finish;
    
        case BR_FAILED_REPLY:
            err = FAILED_TRANSACTION;
            goto finish;
    
        case BR_ACQUIRE_RESULT:
            {
                LOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT");
                const int32_t result = mIn.readInt32();
                if (!acquireResult) continue;
                *acquireResult = result ? NO_ERROR : INVALID_OPERATION;
            }
            goto finish;
    
        case BR_REPLY:
            {
                binder_transaction_data tr;
                err = mIn.read(&tr, sizeof(tr));
                LOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
                if (err != NO_ERROR) goto finish;
    
                if (reply) {
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                        reply->ipcSetDataReference(
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(size_t),
                            freeBuffer, this);
                    } else {
                        err = *static_cast<const status_t*>(tr.data.ptr.buffer);
                        freeBuffer(NULL,
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(size_t), this);
                    }
                } else {
                    freeBuffer(NULL,
                        reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                        tr.data_size,
                        reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                        tr.offsets_size/sizeof(size_t), this);
                    continue;
                }
            }
            goto finish;
    
        default:
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }
    }
    
    finish:
    if (err != NO_ERROR) {
        if (acquireResult) *acquireResult = err;
        if (reply) reply->setError(err);
        mLastError = err;
    }
    
    return err;
    }
    

    waitForResponseはデータの送信と受信を担っていますが、binderドライバに送信するには、このプロセスはどのように通信しますか?talkWithDriver関数を見てみましょう.
    status_t IPCThreadState::talkWithDriver(bool doReceive)
    {
    LOG_ASSERT(mProcess->mDriverFD >= 0, "Binder driver is not opened");
    
    // binder_write_read  binder         
    binder_write_read bwr;
    
    // Is the read buffer empty?
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    
    // We don't want to write anything if we are still reading
    // from data left in the input buffer and the caller
    // has requested to read the next data.
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
    //        
    bwr.write_size = outAvail;
    bwr.write_buffer = (long unsigned int)mOut.data();
    
    // This is what we'll read.
    if (doReceive && needRead) {
        //              ,         ,     mIn 
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (long unsigned int)mIn.data();
    } else {
        bwr.read_size = 0;
    }
    
    IF_LOG_COMMANDS() {
        TextOutput::Bundle _b(alog);
        if (outAvail != 0) {
            alog << "Sending commands to driver: " << indent;
            const void* cmds = (const void*)bwr.write_buffer;
            const void* end = ((const uint8_t*)cmds)+bwr.write_size;
            alog << HexDump(cmds, bwr.write_size) << endl;
            while (cmds < end) cmds = printCommand(alog, cmds);
            alog << dedent;
        }
        alog << "Size of receive buffer: " << bwr.read_size
            << ", needRead: " << needRead << ", doReceive: " << doReceive << endl;
    }
    
    // Return immediately if there is nothing to do.
    //            ,     
    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
    
    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        IF_LOG_COMMANDS() {
            alog << "About to read/write, write size = " << mOut.dataSize() << endl;
        }
    #if defined(HAVE_ANDROID_OS)
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
    #else
        err = INVALID_OPERATION;
    #endif
        IF_LOG_COMMANDS() {
            alog << "Finished read/write, write size = " << mOut.dataSize() << endl;
        }
    } while (err == -EINTR);
    
    IF_LOG_COMMANDS() {
        alog << "Our err: " << (void*)err << ", write consumed: "
            << bwr.write_consumed << " (of " << mOut.dataSize()
            << "), read consumed: " << bwr.read_consumed << endl;
    }
    
    if (err >= NO_ERROR) {
        if (bwr.write_consumed > 0) {
            if (bwr.write_consumed < (ssize_t)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);
        }
        IF_LOG_COMMANDS() {
            TextOutput::Bundle _b(alog);
            alog << "Remaining data size: " << mOut.dataSize() << endl;
            alog << "Received commands from driver: " << indent;
            const void* cmds = mIn.data();
            const void* end = mIn.data() + mIn.dataSize();
            alog << HexDump(cmds, mIn.dataSize()) << endl;
            while (cmds < end) cmds = printReturnCommand(alog, cmds);
            alog << dedent;
        }
        return NO_ERROR;
    }
    
    return err;
    }
    

    ここでbinderドライバと通信するのはread/write方式ではなくioctl方式のようです.これでbinder通信が完了しました.
    ここまで分析しましょう.もう少し深く研究する暇がある