一、进程角度看IPC机制
在Android系统中,每个进程只能运行在自己所拥有的虚拟地址空间。例如,一个4GB的虚拟地址空间,包含3GB的用户空间和1GB的内核空间,内核空间的大小可以通过参数配置进行调整。两个进程之间的用户空间是彼此独立的,但内核空间是可共享的。Server 进程与 Client 进程之间就是利用共享的内核空间进行通信的,Server 端与 Client 端主要使用 ioctl 等方法跟内核空间的驱动进行交互。
二、Binder原理
2.1 使用Binder的优点
在传统的Linux中存在多种IPC的方式,例如SystemV、管道、Socket等。但是Android系统没有大量采用原有的技术方式,而是开发了 Binder 并在系统中大量使用。主要原因有如下几点:
1.性能方面
- 跨进程通讯中,只有socket支持Client-Server的通信方式,但是socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。
- 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
- 共享内存虽然无需拷贝,但控制复杂,难以使用。
- 广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对于传统的Socket方式,有更高的性能,它只需要1次数据拷贝。
2.安全方面
- 首先传统IPC的接收方无法获得对方进程可靠的UID和PID(用户ID进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。使用传统IPC只能由用户在数据包里填入UID和PID,但这样不可靠,容易被恶意程序利用。可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,systemV的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。
3.是否易用
- Binder使用面向对象的设计,基于C/S架构,进行一次远程调用和直接调用本地调用一样,使用方便。并且Binder驱动通过引用计数法实现了管理跨进程传递的代理对象的生命周期。
基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送方添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。
2.2 Binder架构
Binder采用 C/S 架构,其中的角色主要包括Client、Server、ServiceManager和Binder驱动。
- Client进程:使用服务的进程。
- Server进程:提供服务的进程。
- ServiceManager进程:ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。
- Binder驱动:驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
系统首次启动 ServiceManager 后,当 Client 和 Server 通信时都需要先获取 ServiceManager 接口。因此无论是注册服务还是获取服务,都需要 ServiceManager,需要主要的是此处的 ServiceManager 是指 Native 层的 ServiceManager(C++)。
1.注册服务:Server 进程要先注册 Service 到 ServiceManager。该过程,Server是客户端,ServiceManager 是服务端。
2.获取服务:Client 进程使用某个 Service 前,需要先向 ServiceManager 中获取相应的 Service。该过程,Client 是客户端,ServiceManager 是服务端。
3.使用服务:Client 根据获得的 Service 信息建立与 Service 所在的 Server 进程通信的通路,之后就可以与 Service 交互了。该过程:Client 是客户端,Server 是服务端。
2.3 Binder的工作流程
- Server 进程向 ServiceManager 注册,告诉 ServiceManager 我是谁,我有什么,我能做什么。假如 Server 进程有一个 computer 对象,这个对象有个add方法。这时映射关系表就生成了。
- Client 进程向 ServiceManager 查询,我要调用 Server 进程的 computer 对象的 add 方法,这时候 Binder 驱动就开始发挥他的作用了。当向 ServiceManager 查询完毕,Binder 驱动将 computer 对象转换成了 computerProxy 对象,并转发给了 Client 进程,因此,Client 进程拿到的并不是真实的 computer 对象,而是一个代理对象,即 computerProxy 对象。很容易理解这个 computerProxy 对象也是有 add 方法。
- 当 Client 进程调用 add 方法,这个消息发送给 Binder 驱动,这时驱动发现,原来是computerProxy,那么 Client 进程应该是需要调用 computer 对象的 add 方法的,这时驱动通知 Server 进程,调用你的 computer 对象的 add 方法,将结果给我。然后 Server 进程就将计算结果发送给驱动,驱动再转发给 Client 进程,最终 Client 进程拿到了计算结果。
2.4 Binder的线程管理
- 每个 Binder 的 Server 进程会创建很多线程来处理 Binder 请求,而真正管理这些线程并不是由这个 Server 端来管理的,而是由 Binder 驱动进行管理的。
- 一个进程的 Binder 线程数默认最大是 16,超过的请求会被阻塞等待空闲的 Binder 线程。理解这一点的话,你做进程间通信时处理并发问题就会有一个底,比如使用 ContentProvider 时(又一个使用 Binder 机制的组件),你就很清楚它的CRUD(创建、检索、更新和删除)方法只能同时有 16 个线程在跑。