Android Binder机制原理
说起Binder,其实在我们开发中总是没有感觉到它的存在,但是往往在很多地方都间接的在使用它,比如我们再startAcivity的时候,必定要去调用ActivityManagerService,因为我们都知道Activity并不是我们直接new出来的,而是通过这个service创建出来的,然而这个service是处于另外的进程,我们就必须要通过binder获取这个service,告知它去创建一个Activity出来。
诸如binder的使用,在很多地方比比皆是,媒体播放,视音频捕获,调用传感器等等,
Binder是Android系统进程间通信(IPC)方式之一,但是为什么Android不使用Linux本生具有的IPC方式?
优点
- 性能方面
在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对出传统的Socket方式,更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。 - 安全方面
首先传统IPC的接收方无法获得对方进程可靠的UID和PID(用户ID进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。其实Binder机制主要是在本机内进行跨进程通信,而socket等IPC方式主要是用于跨网络的通信手段,因而socket不能限制了入口和出口,所以从使用场景来看,socket给用户提供的使用范围更广,而binder由于为了保证它的数据安全性,必须限制于android系统的机子中,而且在内核中就已经对UID进行了验证。
原理
为了保护进程空间不被别的进程破坏或者干扰,Linux的进程是相互独立的(进程隔离),而且一个进程空间还分为用户空间和内核(Kernel)空间,相当于把Kernel和上层的应用程序抽像的隔离开。这里有两个隔离,一个进程间是相互隔离的,二是进程内有用户和内核的隔离。
Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。这四个角色的关系类似:Server是服务器,Client是客户终端,ServiceManager是服务注册中心(类似房屋中介)。
要进行Client-Server之间的通信,从面向对象的角度,在Server内部有一个Binder实体,在Client内部有一个Binder对象的引用,其实就是Binder的一个代理,Client通过对Binder引用间接的操作Server内部的Binder实体,这样就实现了通信。
但是现在的问题是会有很多个提供不同服务的Server(比如有媒体播放服务,音视频捕获服务等),而且会有很多Client(比如多个应用都要调用媒体播放服务),那么我们怎么才能够实现正确的Client调用正确的Server呢?就好比房客怎么才能够租到自己想要租的房子(联系上房东),这个时候中介就起到重要作用,房东想要出租自己的房子就必须要到中介注册,房客想要租房子就要去中介那里找,同样的道理,这里Client就是房客,Server就是房东,ServiceManager就是房屋中介,每个Server如果要提供服务就必须要去ServiceManager那里去注册,ServiceManager在一张查找表中记录一个Server的名字,对应着Server的引用。Client想要获得Server,必须通过名字到ServiceManager取找Server的引用,获得这个Server的binder引用,通过这个binder引用去和Server通信。
以上便是Binder的架构设计,这种架构设计在很多地方都用到过,如果大家熟悉服务器分布式服务调用就可以很容易理解,dubbo框架其实也就是这种架构,需要一台服务器专门提供服务管理(zookeeper),所有的服务都必须要在zookeeper中注册,其他的节点服务要调用该服务都需要在zookeeper中取取,好的扯远了,我们继续来看具体client-server是怎么实现跨进程通信的。
这就是binder驱动,它为上层Client,Server,ServiceManager之间的通信提供了基础。
fd = open(“/dev/binder”, O_RDWR);
mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
这里使用了linux的系统调用方法mmap(),这里使用mmap创建数据接收的缓存空间。这里必须要说一下mmap方法,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间,因而使用copy_from_user()将发送方的缓存数据拷贝到mmap中的内存空间时,其实接收方就可以直接获取这块内存空间了,这样就只需要拷贝一次,而不需要像传统的IPC方式那样,需要copy_from_user将进程1的用户空间拷贝到内核空间,再使用copy_to_user将数据从内核空间拷贝到进程2的用户空间。
上面我们一直在说Client与Server之间的IPC,其实Server与ServiceManager也要通过Binder进行通信,因为本身Server与ServiceManager就在不同的进程中,那么问题来了,刚刚说Server去ServiceManager注册,这个过程是怎么实现的呢,其实这个通信还是binder实现的,只是ServiceManager充当了Server的角色不用注册而已,我们注册的的目的是为了让Client找到正确的Server,但是ServiceManager只有一个,我们这个时候就可以将ServiceManager创建的binder引用定死,android系统在启动的时候就已经创建了ServiceManager的binder(通过调用BINDER_SET_CONTEXT_MGR告诉系统该进程是ServiceManager进程),而且每个Client都知道这个binder的引用代号为0,所以每个Client就可以直接获得这个binder引用,而不需要从其他地方获取。
Binder的线程管理
每个Binder的Server进程会创建很多线程来处理Binder请求,可以简单的理解为创建了一个Binder的线程池吧(虽然实际上并不完全是这样简单的线程管理方式),而真正管理这些线程并不是由这个Server端来管理的,而是由Binder驱动进行管理的。
一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。理解这一点的话,你做进程间通信时处理并发问题就会有一个底,比如使用ContentProvider时(又一个使用Binder机制的组件),你就很清楚它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程在跑。