十指相扣:陪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的工作原理就基本介绍完了,下一篇文章将会分析线程池的一些机制以及内核层的原理,更多精彩还在后面~本文结束。
- 十指相扣:陪binderIPC度过的漫长岁月(1)
- 十指相扣:陪binderIPC度过的漫长岁月(2)
- 十指相扣:陪binderIPC度过的漫长岁月(3)
- 陪喆度过漫长岁月
- 陪你度过漫长岁月
- 【腾讯TMQ】陪你度过漫长岁月:WiFi管家测试一纸芳华诉流年
- 陪我走过的漫长岁月---2015年总
- 漫长岁月
- 漫长的岁月,沧海历经的人生
- 从Activity的启动细窥BinderIPC(1)
- 从Activity的启动细窥BinderIPC(2)
- 从Activity的启动细窥BinderIPC(3)
- 哄起我来爸爸在前线战斗的这段漫长岁月里
- 十年--献给逝去的岁月(1)
- 走向J2EE,漫长的道路(转载)
- 2014/10/1 国庆日的度过
- 俞敏洪:度过有意义的生命(上)
- 度过有意义的生命(上)
- 对ICMP错误消息的处理
- spark源码学习(一)---sparkContext(1)
- Android 开发环境下载地址 -- 百度网盘 adt-bundle android-studio sdk adt 下载
- 个人笔记 js 15 js字符串的转换
- GZIP:response压缩
- 十指相扣:陪binderIPC度过的漫长岁月(1)
- 生产者与消费者
- 32位float在内存中的存储主要分成三部分:1bit符号位,8bit指数位(127+指数),23bit尾数位..0.0f存0。。1.0f存0x3f800000.
- 【推荐】公共领域高质量公开数据集列表
- 我在小公司前端要做什么?
- JDBC之简单事务处理
- 这是一篇记事
- 2015年终总结
- Dom及JavaScript图片库