• android Bind机制(二)


    1.binder通信概述

        binder通信是一种client-server的通信结构,
        1.从表面上来看,是client通过获得一个server的代理接口,对server进行直接调用;
        2.实际上,代理接口中定义的方法与server中定义的方法是一一对应的;
        3.client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成为Parcel对象;
        4.代理接口将该Parcel发送给内核中的binder driver.
        5.server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回;
        6.整个的调用过程是一个同步过程,在server处理的时候,client会block住。

     

     

    2.service manager

    Service Manager是一个linux级的进程,顾名思义,就是service的管理器。这里的service是什么概念呢?这里的service的概念和init过程中init.rc中的service是不同,init.rc中的service是都是linux进程,但是这里的service它并不一定是一个进程,也就是说可能一个或多个service属于同一个linux进程。在这篇文章中不加特殊说明均指android native端的service。

    任何service在被使用之前,均要向SM(Service Manager)注册,同时客户端需要访问某个service时,应该首先向SM查询是否存在该服务。如果SM存在这个service,那么会将该service的handle返回给client,handle是每个service的唯一标识符。
        
        SM的入口函数在service_manager.c中,下面是SM的代码部分
    int main(int argc, char **argv)
    {
        struct binder_state *bs;
        void *svcmgr = BINDER_SERVICE_MANAGER;

        bs = binder_open(128*1024);

        if (binder_become_context_manager(bs)) {
            LOGE("cannot become context manager (%s)/n", strerror(errno));
            return -1;
        }

        svcmgr_handle = svcmgr;
        binder_loop(bs, svcmgr_handler);
        return 0;
    }

    这个进程的主要工作如下:
        1.初始化binder,打开/dev/binder设备;在内存中为binder映射128K字节空间;
        2.指定SM对应的代理binder的handle为0,当client尝试与SM通信时,需要创建一个handle为0的代理binder,这里的代理binder其实就是第一节中描述的那个代理接口;

    3.通知binder driver(BD)使SM成为BD的context manager;
    4.维护一个死循环,在这个死循环中,不停地去读内核中binder driver,查看是否有可读的内容;即是否有对service的操作要求, 如果有,则调用svcmgr_handler回调来处理请求的操作。

    5.SM维护了一个svclist列表来存储service的信息。

     



    这里需要声明一下,当service在向SM注册时,该service就是一个client,而SM则作为了server。而某个进程需要与service通信时,此时这个进程为client,service才作为server。因此service不一定为server,有时它也是作为client存在的。

     

    由于下面几节会介绍一些与binder通信相关的几个概念,所以将SM的功能介绍放在了后面的部分来讲。

    应用和service之间的通信会涉及到2次binder通信。

    1.应用向SM查询service是否存在,如果存在获得该service的代理binder,此为一次binder通信;
    2.应用通过代理binder调用service的方法,此为第二次binder通信。

    3.ProcessState

    ProcessState是以单例模式设计的。每个进程在使用binder机制通信时,均需要维护一个ProcessState实例来描述当前进程在binder通信时的binder状态。
        ProcessState有如下2个主要功能:
        1.创建一个thread,该线程负责与内核中的binder模块进行通信,称该线程为Pool thread;
        2.为指定的handle创建一个BpBinder对象,并管理该进程中所有的BpBinder对象。

     

    3.1 Pool thread

                在Binder IPC中,所有进程均会启动一个thread来负责与BD来直接通信,也就是不停的读写BD,这个线程的实现主体是一个IPCThreadState对象,下面会介绍这个类型。

                下面是 Pool thread的启动方式:

    ProcessState::self()->startThreadPool();

    3.2 BpBinder获取

    BpBinder主要功能是负责client向BD发送调用请求的数据。它是client端binder通信的核心对象,通过调用transact函数向BD发送调用请求的数据,它的构造函数如下:

    BpBinder(int32_t handle);
        通过BpBinder的构造函数发现,BpBinder会将当前通信中server的handle记录下来,当有数据发送时,会通知BD数据的发送目标。

    ProcessState通过如下方式来获取BpBinder对象:

    ProcessState::self()->getContextObject(handle);

    在这个过程中,ProcessState会维护一个BpBinder的vector mHandleToObject,每当ProcessState创建一个BpBinder的实例时,回去查询mHandleToObject,如果对应的handle已经有binder指针,那么不再创建,否则创建binder并插入到mHandleToObject中。
        ProcessState创建的BpBinder实例,一般情况下会作为参数构建一个client端的代理接口,这个代理接口的形式为BpINTERFACE,例如在与SM通信时,client会创建一个代理接口BpServiceManager.
        
        

    4.IPCThreadState

    IPCThreadState也是以单例模式设计的。由于每个进程只维护了一个ProcessState实例,同时ProcessState只启动一个Pool thread,也就是说每一个进程只会启动一个Pool thread,因此每个进程则只需要一个IPCThreadState即可。
        Pool thread的实际内容则为:
        IPCThreadState::self()->joinThreadPool();

    ProcessState中有2个Parcel成员,mIn和mOut,Pool thread会不停的查询BD中是否有数据可读,如果有将其读出并保存到mIn,同时不停的检查mOut是否有数据需要向BD发送,如果有,则将其内容写入到BD中,总而言之,从BD中读出的数据保存到mIn,待写入到BD中的数据保存在了mOut中。

    ProcessState中生成的BpBinder实例通过调用IPCThreadState的transact函数来向mOut中写入数据,这样的话这个binder IPC过程的client端的调用请求的发送过程就明了了。

    IPCThreadState有两个重要的函数,talkWithDriver函数负责从BD读写数据,executeCommand函数负责解析并执行mIn中的数据。

    5.主要基类

    5.1基类IInterface

    为server端提供接口,它的子类声明了service能够实现的所有的方法;


    5.2基类IBinder
        BBinder与BpBinder均为IBinder的子类,因此可以看出IBinder定义了binder IPC的通信协议,BBinder与BpBinder在这个协议框架内进行的收和发操作,构建了基本的binder IPC机制。
    5.3基类BpRefBase
        client端在查询SM获得所需的的BpBinder后,BpRefBase负责管理当前获得的BpBinder实例。

    6.两个接口类

    6.1 BpINTERFACE

    如果client想要使用binder IPC来通信,那么首先会从SM出查询并获得server端service的BpBinder,在client端,这个对象被认为是server端的远程代理。为了能够使client能够想本地调用一样调用一个远程server,server端需要向client提供一个接口,client在在这个接口的基础上创建一个BpINTERFACE,使用这个对象,client的应用能够想本地调用一样直接调用server端的方法。而不用去关心具体的binder IPC实现。
    下面看一下BpINTERFACE的原型:
        class BpINTERFACE : public BpInterface<IINTERFACE>

        顺着继承关系再往上看
        template<typename INTERFACE>
        class BpInterface : public INTERFACE, public BpRefBase

        BpINTERFACE分别继承自INTERFACE,和BpRefBase;
    ● BpINTERFACE既实现了service中各方法的本地操作,将每个方法的参数以Parcel的形式发送给BD。
    例如BpServiceManager的
        virtual status_t addService(const String16& name, const sp<IBinder>& service)
        {
            Parcel data, reply;
            data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
            data.writeString16(name);
            data.writeStrongBinder(service);
            status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
            return err == NO_ERROR ? reply.readExceptionCode() : err;
        }
    ● 同时又将BpBinder作为了自己的成员来管理,将BpBinder存储在mRemote中,BpServiceManager通过调用BpRefBase的remote()来获得BpBinder指针。

    6.2 BnINTERFACE

    在定义android native端的service时,每个service均继承自BnINTERFACE(INTERFACE为service name)。BnINTERFACE类型定义了一个onTransact函数,这个函数负责解包收到的Parcel并执行client端的请求的方法。

        顺着BnINTERFACE的继承关系再往上看,
            class BnINTERFACE: public BnInterface<IINTERFACE>

        IINTERFACE为client端的代理接口BpINTERFACE和server端的BnINTERFACE的共同接口类,这个共同接口类的目的就是保证service方法在C-S两端的一致性。

        再往上看
            class BnInterface : public INTERFACE, public BBinder

        同时我们发现了BBinder类型,这个类型又是干什么用的呢?既然每个service均可视为一个binder,那么真正的server端的binder的操作及状态的维护就是通过继承自BBinder来实现的。可见BBinder是service作为binder的本质所在。

        那么BBinder与BpBinder的区别又是什么呢?

        其实它们的区别很简单,BpBinder是client端创建的用于消息发送的代理,而BBinder是server端用于接收消息的通道。查看各自的代码就会发现,虽然两个类型均有transact的方法,但是两者的作用不同,BpBinder的transact方法是向IPCThreadState实例发送消息,通知其有消息要发送给BD;而BBinder则是当IPCThreadState实例收到BD消息时,通过BBinder的transact的方法将其传递给它的子类BnSERVICE的onTransact函数执行server端的操作。

    7. Parcel

    Parcel是binder IPC中的最基本的通信单元,它存储C-S间函数调用的参数.但是Parcel只能存储基本的数据类型,如果是复杂的数据类型的话,在存储时,需要将其拆分为基本的数据类型来存储。

        简单的Parcel读写不再介绍,下面着重介绍一下2个函数

    7.1 writeStrongBinder

    当client需要将一个binder向server发送时,可以调用此函数。例如
            virtual status_t addService(const String16& name, const sp<IBinder>& service)
            {
                Parcel data, reply;
                data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
                data.writeString16(name);
                data.writeStrongBinder(service);
                status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
                return err == NO_ERROR ? reply.readExceptionCode() : err;
            }


    看一下writeStrongBinder的实体
    status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
    {
        return flatten_binder(ProcessState::self(), val, this);
    }

    接着往里看flatten_binder
    status_t flatten_binder(const sp<ProcessState>& proc,
        const sp<IBinder>& binder, Parcel* out)
    {
        flat_binder_object obj;
        
        obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
        if (binder != NULL) {
            IBinder *local = binder->localBinder();
            if (!local) {
                BpBinder *proxy = binder->remoteBinder();
                if (proxy == NULL) {
                    LOGE("null proxy");
                }
                const int32_t handle = proxy ? proxy->handle() : 0;
                obj.type = BINDER_TYPE_HANDLE;
                obj.handle = handle;
                obj.cookie = NULL;
            } else {
                obj.type = BINDER_TYPE_BINDER;
                obj.binder = local->getWeakRefs();
                obj.cookie = local;
            }
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = NULL;
            obj.cookie = NULL;
        }
        
        return finish_flatten_binder(binder, obj, out);
    }

        还是拿addService为例,它的参数为一个BnINTERFACE类型指针,BnINTERFACE又继承自BBinder,
        BBinder* BBinder::localBinder()
        {
            return this;
        }
        所以写入到Parcel的binder类型为BINDER_TYPE_BINDER,同时你在阅读SM的代码时会发现如果SM收到的service的binder类型不为BINDER_TYPE_HANDLE时,SM将不会将此service添加到svclist,但是很显然每个service的添加都是成功的,addService在开始传递的binder类型为BINDER_TYPE_BINDER,SM收到的binder类型为BINDER_TYPE_HANDLE,那么这个过程当中究竟发生了什么?
        为了搞明白这个问题,花费我很多的事件,最终发现了问题的所在,原来在BD中做了如下操作(drivers/staging/android/Binder.c):


    static void binder_transaction(struct binder_proc *proc,
                       struct binder_thread *thread,
                       struct binder_transaction_data *tr, int reply)
    {
    ..........................................

        if (fp->type == BINDER_TYPE_BINDER)
            fp->type = BINDER_TYPE_HANDLE;
        else
            fp->type = BINDER_TYPE_WEAK_HANDLE;
        fp->handle = ref->desc;
    ..........................................
    }


    阅读完addService的代码,你会发现SM只是保存了service binder的handle和service的name,那么当client需要和某个service通信了,如何获得service的binder呢?看下一个函数

    7.2 readStrongBinder

    当server端收到client的调用请求之后,如果需要返回一个binder时,可以向BD发送这个binder,当IPCThreadState实例收到这个返回的Parcel时,client可以通过这个函数将这个被server返回的binder读出。


    sp<IBinder> Parcel::readStrongBinder() const
    {
        sp<IBinder> val;
        unflatten_binder(ProcessState::self(), *this, &val);
        return val;
    }


    往里查看unflatten_binder


    status_t unflatten_binder(const sp<ProcessState>& proc,
        const Parcel& in, sp<IBinder>* out)
    {
        const flat_binder_object* flat = in.readObject(false);
        
        if (flat) {
            switch (flat->type) {
                case BINDER_TYPE_BINDER:
                    *out = static_cast<IBinder*>(flat->cookie);
                    return finish_unflatten_binder(NULL, *flat, in);
                case BINDER_TYPE_HANDLE:
                    *out = proc->getStrongProxyForHandle(flat->handle);
                    return finish_unflatten_binder(
                        static_cast<BpBinder*>(out->get()), *flat, in);
            }        
        }
        return BAD_TYPE;
    }


    发现如果server返回的binder类型为BINDER_TYPE_BINDER的话,也就是返回一个binder引用的话,直接获取这个binder;如果server返回的binder类型为BINDER_TYPE_HANDLE时,也就是server返回的仅仅是binder的handle,那么需要重新创建一个BpBinder返回给client。


        有上面的代码可以看出,SM保存的service的binder仅仅是一个handle,而client则是通过向SM获得这个handle,从而重新构建代理binder与server通信。


        这里顺带提一下一种特殊的情况,binder通信的双方即可作为client,也可以作为server.也就是说此时的binder通信是一个半双工的通信。那么在这种情况下,操作的过程会比单工的情况复杂,但是基本的原理是一样的,有兴趣可以分析一下MediaPlayer和MediaPlayerService的例子。

    8. 经典桥段分析

    main_ mediaserver.cpp
    int main(int argc, char** argv)
    {

    //创建进程mediaserver的ProcessState实例
        sp<ProcessState> proc(ProcessState::self());

    //获得SM的BpServiceManager
        sp<IServiceManager> sm = defaultServiceManager();
        LOGI("ServiceManager: %p", sm.get());

    //添加mediaserver中支持的service。
        AudioFlinger::instantiate();
        MediaPlayerService::instantiate();
        CameraService::instantiate();
        AudioPolicyService::instantiate();

    //启动ProcessState的pool thread
        ProcessState::self()->startThreadPool();

    //这一步有重复之嫌,加不加无关紧要。
        IPCThreadState::self()->joinThreadPool();
    }

    9. Java 层的binder机制

    了解了native通信机制后,再去分析JAVA层的binder机制,就会很好理解了。它只是对native的binder做了一个封装。这一部分基本上没有太复杂的过程,这里不再赘述了。

     
     
  • 相关阅读:
    资源利用率提高67%,腾讯实时风控平台云原生容器化之路
    热门分享预告|腾讯大规模云原生平台稳定性实践
    Fluid + GooseFS 助力云原生数据编排与加速快速落地
    基于 Clusternet 与 OCM 打造新一代开放的多集群管理平台
    案例 | 沃尔玛 x 腾讯云 Serverless 应用实践,全力保障消费者购物体验
    SuperEdge 高可用云边隧道有哪些特点?
    kubernetes 降本增效标准指南|ProphetPilot:容器智能成本管理引擎
    公有云上构建云原生 AI 平台的探索与实践
    如何削减 50% 机器预算?“人机对抗”探索云端之路
    SuperEdge 易学易用系列-SuperEdge 简介
  • 原文地址:https://www.cnblogs.com/qingchen1984/p/5212767.html
Copyright © 2020-2023  润新知