十指相扣:陪binderIPC度过的漫长岁月(1)

来源:互联网 发布:鲸蜡醇乙基己酸酯 知乎 编辑:程序博客网 时间:2024/04/30 02:14

Android中的ipc一直被称作系统设计中一个比较复杂的地方,近来特意研究了一下ipc,感受了一下这个伟大的设计。

说到ipc,肯定不会离开binder,binder是什么呢,它的中文名叫粘合剂,两个不同内存空间的进程能够得到进行通信,必然需要一样东西将两者进行“粘合”,其实我们在activity中开启一个service时,如果我们使这个服务成为远程服务(remote),这个服务就会运行在一个另一个独立的进程,我们知道我们本来的activity所在的是zygote建立的一个app进程,而且在kernel层利用内存共享进行ipc,并有着cow的原则。

那么一个进程怎么和一个远程进程进行“粘合”呢,如果是内存共享,内核可以有mmap()进行内存映射,我们首先自己想像一下,我们要完成通信,也就是在当前app进程空间的数据拷贝到驱动层的buffer,然后驱动层再把这些数据拷贝到目标进程,当然期间会发生cow,所以我们需要远程进程的上下文信息(context),至少你要知道内存地址吧,然后映射到的物理地址,然而,Android设计的比较谨慎严密,考虑了安全性之后,就不会这么鲁莽的暴漏所有服务进程的上下文给一个app进程(这样也太可怕了,意味着安装一个app可能会莫名其妙控制掉其他进程服务)。

没有地址提供怎么办呢,这个问题之前,先说明ipc的设计是C/S架构的,ok,我虽然不会鲁莽地把地址给每一位client,但我可以给一位总代理啊,这种中介角色就是ServiceManager(SM),SM维护着一个服务进程的list,通过查找list就可以得到对应远程进程的信息了,那么,如果我要在client远程调用server端的方法呢,这里比较巧妙,我们可以在本地进行调用,因为有binder。

看来是时候分析一下binder了,binder有关的类可谓是一大堆,关系比较复杂,在java层中binder是实现了IBinder接口的一个类,我们在进行跟远程服务通信时都会将数据序列化成一个parcel对象,在调用transact()方法就可以了。大概的过程就是下面这个样子:

这里写图片描述

我这里主要分析C++层的实现,毕竟java主要设计业务逻辑,很多binder框架的实现都是通过jni调用C++代码才能操作driver,binder在C++中又两个关键的角色,一个叫BpBinder类,一个叫BBinder类,这两个类都继承自一个抽象类IBinder,这个抽象声明了transact(),linkToDeath()等一些虚函数,具体的实现就交给这些binder子类了,所以每一个binder实体都有着不同的关于通信行为的实现,我们如果把binder看成是ipc的proxy,这就是一种proxy-stub的设计模式了~,我们先来看看这两个类。

重点研究下trancast()函数的实现,BpBinder类跟BBinder类在这个实现上有一些不一样,先来看看源代码:

status_t BBinder::transact(    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){    data.setDataPosition(0);    status_t err = NO_ERROR;    switch (code) {        //这个是一种ping的给driver的命令,表示查询server是否存在,一般发生在跟context manager通信的情况下,当handle为0,即processState对象为这个handle创建的本地通信接口(BpBinder)的引用。          case PING_TRANSACTION:        //像内核空间写入操作            reply->writeInt32(pingBinder());            break;        default:        //这个看似回调的函数就是这样被调用的,没错,服务端检测到数据马上调用onTransact()函数。            err = onTransact(code, data, reply, flags);            break;    }    if (reply != NULL) {        reply->setDataPosition(0);    }    return err;}//主要是检测binder对象是否死亡,死亡就是不可用的操作status_t BBinder::linkToDeath(    const sp<DeathRecipient>& /*recipient*/, void* /*cookie*/,    uint32_t /*flags*/){    return INVALID_OPERATION;}

这个很明显看出来是server端的binder,Server端也需要定义一个Binder抽象类处理来自Client的Binder请求数据包,其中最重要的成员是虚函数onTransact()。该函数分析收到的数据包,调用相应的接口函数处理请求,那么各个Binder实体的onTransact()又是什么时候调用呢,这就需要驱动参与了,Binder实体须要以Binde传输结构flat_binder_object形式发送给其它进程才能建立Binder通信,而Binder实体指针就存放在该结构的handle域中。驱动根据Binder位置数组从传输数据中获取该Binder的传输结构,为它创建位于内核中的Binder节点,将Binder实体指针记录在该节点中,至于binder通信结构规范,相应的文档会有说,注意版本问题即可。此外它的那个抽象的父类IBinder继承了RefBase引用计数基类,这是智能指针的做法,Android的设计大多采用智能指针管理引用以便进行GC。

再来看看BpBinder类,同样作为binder一族,它肯定也实现了那些虚函数,来看看源代码:

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;}

这个函数里面,首先判断作为binder,是否还存活,有可能ipc结束,整个binder作为这一次通信的token就没什么事了,(在data结构中,binder一般作为本次ipc的token,对于binder在内核空间中的地址是唯一的,这个引用可以成为索引,然而在用户空间比如进程的内存空间中,就不是唯一的了,有可能由于相对偏址而导致地址的重合),接下来,直接调用了IPCThreadState对象的transact()函数进行通信,在判断一下这次ipc是否标记为死亡,整个逻辑比较简单,那么IPCThreadState对象是怎么回事。

其中有一个线程的单词,我们可以得知,这是一个线程有关的对象,是的,在每一个参与ipc的进程中,系统都会分配一个用于发送与接收数据包的线程池,为什么要用线程池呢,首先跟binder的同步机制有关,假设P1进程的T1线程向P2进程发送消息,ping一下远程进程后就可以得到远程P2的handle,这里本地的代理接口 BpINTERFACE就要上场了 。看看它的定义:

class BpINTERFACE : public BpInterface<IINTERFACE> 

继承自BpInterface<IINTERFACE> 看来还有模板类,再看看这个父类的定义:

template<typename INTERFACE>     class BpInterface : public INTERFACE, public BpRefBase 

继承一个模板类INTERFACE,和我们的BpBinder类,所以也有BpBinder向BD发送数据包的功能,ok,我们要构造这样一个本地代理接口对象,需要一个BpBinder对象作为参数,BpBinder对象可以通过handle构造,这个下面会分析,这里跳过这一步,有了BpBinder对象,很自然开始调用transact()函数进行与BD的数据传输,这时P2的ProcessState 对象有个比较勤奋的成员mIn会不停地检测BD是否已经被写入数据,这是一个轮询操作,没有办法,检测到了BBinder的transact()函数就开始被调用了,如上面所说,回调函数onTransact()也被调用,(这些虚函数作为ipc binder协议在IBinder基类被统一定义,CS不同实现,体现了一次ipc的一致性),然而P1的T1线程在完成向BD写入数据后在干什么呢,不错,它进入了等待状态,准确来说,进入了T1的私有等待队列(to-do queue),也就是一种阻塞状态,这个T1已经在队列中排队了,如果此时T2返回了数据T1当然可以成功接收,问题是P3进程的T3发送数据来P1,谁来接收,现在姑且假设P1只有一个线程T1,很明显,这种情况需要线程池这样的多线程模型,所以在ipc中异步请求会被缓存在BD中,(binder毕竟是同步为主,异步老是被歧视),优先处理同步,避免一下子突发过多的异步请求耗尽线程池中的线程。

知道了线程池的存在,BpBinder的transact()函数也就基本清楚了,通过线程池调用一个可用(空闲状态)的线程向BD发送数据包,然后server端就可以接收到数据包了。

基本的流程如下:

这里写图片描述

在这里BpINTERFACE代理接口实际上完成了远程方法的本地调用,比如我要跟SM通信,这个接口对象就会变成BpServiceManager,它会继承一个IServiceManager的抽象类(作为模板参数),以及BpBinder类,所以它既可以跟BD进行数据交互,实现了IServiceManager声明的虚函数,看下源代码:

class BpServiceManager : public BpInterface<IServiceManager>{public:    BpServiceManager(const sp<IBinder>& impl)        : BpInterface<IServiceManager>(impl)    {    }    virtual sp<IBinder> getService(const String16& name) const    {        unsigned n;        for (n = 0; n < 5; n++){            sp<IBinder> svc = checkService(name);            if (svc != NULL) return svc;            ALOGI("Waiting for service %s...\n", String8(name).string());            sleep(1);        }        return NULL;    }

由于篇幅问题,省略部分,这是一个SM的查询服务的虚函数实现,以名字为键值查询哈希表,还5顾茅庐。

说到BpINTERFACE接口类,不得不提到与其类似的一个在server端继承的一个BnINTERFACE类,基本的思路其实跟BpINTERFACE类一样,它也有一个父类,父类也是多重继承,继承同一个模板接口类以确保ipc的一致性,不过后者继承BBinder类,看到这里,大家也许知道了每一个service的本质,很明显就是一个跟BBinder有亲缘关系咯,可以说BBinder是service的奶奶。。。

ok,到这里关于BpBinder以及BBinder的工作原理就基本介绍完了,下一篇文章将会分析线程池的一些机制以及内核层的原理,更多精彩还在后面~本文结束。

0 0
原创粉丝点击