2. Binder通信机制
2.1 Binder通信模型
我们把跨进程通信的双方叫做Server进程(简称Server),Client进程(简称Client);由于进程隔离的存在,它们之间没办法通过简单的方式进行通信,那么Binder机制是如何进行的呢?
回想一下日常生活中我们通信的过程:假设A和B要进行通信,通信的媒介是打电话(A是Client,B是Server);A要给B打电话,必须知道B的号码,这个号码怎么获取呢?通信录。
这个通信录就是一张表;内容大致是:
- B -> 12345676
- C -> 12334354
先查阅通信录,拿到B的号码;才能进行通信;否则,怎么知道应该拨什么号码?回想一下古老的电话机,如果A要给B打电话,必须先连接通话中心,说明给我接通B的电话;这时候通话中心帮他呼叫B;连接建立,就完成了通信。
另外,光有电话和通信录是不可能完成通信的,没有基站支持;信息根本无法传达。
我们看到,一次电话通信的过程除了通信的双方还有两个隐藏角色:通信录和基站。Binder通信机制也是一样:两个运行在用户空间的进程要完成通信,必须借助内核的帮助,这个运行在内核里面的程序叫做Binder驱动,它的功能类似于基站;通信录呢,就是一个叫做ServiceManager的东西(简称SM)。
整个通信步骤如下:
- SM建立(建立通信录);首先有一个进程向驱动提出申请为SM;驱动同意之后,SM进程负责管理Service(注意这里是Service而不是Server,因为如果通信过程反过来的话,那么原来的客户端Client也会成为服务端Server)不过这时候通信录还是空的,一个号码都没有。
- 各个Server向SM注册(完善通信录);每个Server端进程启动之后,向SM报告,我是zhangsan, 要找我请返回0x1234(这个地址没有实际意义,类比);其他Server进程依次如此;这样SM就建立了一张表,对应着各个Server的名字和地址;就好比B与A见面了,说存个我的号码吧,以后找我拨打10086;
- Client想要与Server通信,首先询问SM;请告诉我如何联系zhangsan,SM收到后给他一个号码0x1234;Client收到之后,开心滴用这个号码拨通了Server的电话,于是就开始通信了。
那么Binder驱动干什么去了呢?这里Client与SM的通信,以及Client与Server的通信,都会经过驱动,驱动在背后默默无闻,但是做着最重要的工作。驱动是整个通信过程的核心,因此完成跨进程通信的秘密全部隐藏在驱动里面;这个我们稍后讨论。
2.2 Binder机制跨进程原理
两个运行在用户空间的进程A和进程B如何完成通信呢?内核可以访问A和B的所有数据;所以,最简单的方式是通过内核做中转;假设进程A要给进程B发送数据,那么就先把A的数据copy到内核空间,然后把内核空间对应的数据copy到B就完成了;用户空间要操作内核空间,需要通过系统调用;刚好,这里就有两个系统调用:copy_from_user
, copy_to_user
。
Binder机制是如何实现跨进程通信的呢?Binder驱动为我们做了一切。
假设Client进程想要调用Server进程的object对象的一个方法add;对于这个跨进程通信过程,我们来看看Binder机制是如何做的。 (通信是一个广泛的概念,只要一个进程能调用另外一个进程里面某对象的方法,那么具体要完成什么通信内容就很容易了。)
首先,Server进程要向SM注册;告诉自己是谁,自己有什么能力;在这个场景就是Server告诉SM,它叫zhangsan,它有一个object对象,可以执行add 操作;于是SM建立了一张表:zhangsan这个名字对应进程Server;
然后Client向SM查询:我需要联系一个名字叫做zhangsan的进程里面的object对象;这时候关键来了:进程之间通信的数据都会经过运行在内核空间里面的驱动,驱动在数据流过的时候做了一点手脚,它并不会给Client进程返回一个真正的object对象,而是返回一个看起来跟object一模一样的代理对象objectProxy,这个objectProxy也有一个add方法,但是这个add方法没有Server进程里面object对象的add方法那个能力;objectProxy的add只是一个傀儡,它唯一做的事情就是把参数包装然后交给驱动。(这里我们简化了SM的流程,见下文)
但是Client进程并不知道驱动返回给它的对象动过手脚,毕竟伪装的太像了,如假包换。Client开开心心地拿着objectProxy对象然后调用add方法;我们说过,这个add什么也不做,直接把参数做一些包装然后直接转发给Binder驱动。
驱动收到这个消息,发现是这个objectProxy;一查表就明白了:我之前用objectProxy替换了object发送给Client了,它真正应该要访问的是object对象的add方法;于是Binder驱动通知Server进程,调用你的object对象的add方法,然后把结果发给我,Sever进程收到这个消息,照做之后将结果返回驱动,驱动然后把结果返回给Client进程;于是整个过程就完成了。
由于驱动返回的objectProxy与Server进程里面原始的object是如此相似,给人感觉好像是直接把Server进程里面的对象object传递到了Client进程;因此,我们可以说Binder对象是可以进行跨进程传递的对象
但事实上我们知道,Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程好像是Binder跨进程穿越的时候,它在一个进程留下了一个真身,在另外一个进程幻化出一个影子(这个影子可以很多个);Client进程的操作其实是对于影子的操作,影子利用Binder驱动最终让真身完成操作。
理解这一点非常重要;务必仔细体会。另外,Android系统实现这种机制使用的是代理模式, 对于Binder的访问,如果是在同一个进程(不需要跨进程),那么直接返回原始的Binder实体;如果在不同进程,那么就给他一个代理对象(影子);我们在系统源码以及AIDL的生成代码里面可以看到很多这种实现。
另外我们为了简化整个流程,隐藏了SM这一部分驱动进行的操作;实际上,由于SM与Server通常不在一个进程,Server进程向SM注册的过程也是跨进程通信,驱动也会对这个过程进行暗箱操作:SM中存在的Server端的对象实际上也是代理对象,后面Client向SM查询的时候,驱动会给Client返回另外一个代理对象。Sever进程的本地对象仅有一个,其他进程所拥有的全部都是它的代理。
一句话总结就是:Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。
- 通常意义下,Binder指的是一种通信机制;我们说AIDL使用Binder进行通信,指的就是Binder这种IPC机制。
- 对于Server进程来说,Binder指的是Binder本地对象
- 对于Client来说,Binder指的是Binder代理对象,它只是Binder本地对象的一个远程代理;对这个Binder代理对象的操作,会通过驱动最终转发到Binder本地对象上去完成;对于一个拥有Binder对象的使用者而言,它无须关心这是一个Binder代理对象还是Binder本地对象;对于代理对象的操作和对本地对象的操作对它来说没有区别。
- 对于传输过程而言,Binder是可以进行跨进程传递的对象;Binder驱动会对具有跨进程传递能力的对象做特殊处理:自动完成代理对象和本地对象的转换。
3. Android 接口定义语言 (AIDL)
AIDL(Android 接口定义语言)与您可能使用过的其他 IDL 类似。 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。 在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。
摘录自: