Android 要采用 Binder 作为 IPC 机制,所以在了解Binder之前我们先来了解下什么是IPC机制先…
IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
在Linux系统之中,进程间IPC机制包含多种,如下表格:
IPC机制 | 优缺点 |
---|---|
管道 | 在创建时分配一个page大小的内存,缓存区大小比较有限 |
消息队列 | 信息复制需要两次,额外的CPU消耗,不合适频繁或信息量大的通信 |
共享内存 | 无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快,但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决 |
套接字 | 作为更通用的接口,传输效率低,主要用于跨网络的通信 |
信号 | 不适用于信息交换,更适用于进程中断控制 |
信号量 | 常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源,主要作为进程间以及同一进程内不同线程之间的同步手段 |
Android的内核也是基于Linux内核,为何不采用Linux现有的IPC机制,而选择了Binder作为IPC机制呢?答案在下面,接着往下看…
1、从性能的角度
Binder数据拷贝只需要复制一次,而管道、消息队列、Socket都需要2次(为什么是2次,下文讲解),但共享内存方式一次内存拷贝都不需要。
2、从稳定性的角度
Binder是基于C/S架构的,什么是C/S架构呢?简单来说,就是指客户端(Client)和服务端(Server)组成的架构。客户端有什么需求的话,直接发给服务端,让服务端解决,Server端与Client端相对独立,稳定性较好。
3、从安全的角度
Android为每个安装好的应用程序分配了自己的UID(用户id),故进程的UID是鉴别进程身份的重要标志。传统的Linux系统IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份,而Android作为一个开源的开放体系,面对广大的开发平台,手机的安全显的格外的重要。
Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行。Android 6.0之前,当用户第一次启动App的时候,所有的权限对话框一一弹出让用户进行选择,就连用不上的信息权限也会包括在内,让用户无法拒绝,一旦拒绝的话整个App也就无法使用了,如果授权的话,这不就是相当于应用可以胡作非为了嘛,所以Android 6.0之后便有了动态申请权限这东西了,需要哪个权限的时候在弹出对应的全选对话框,对权限做了更细地控制,让用户有了更多的可控性。
传统IPC只能由用户在数据包里填入UID/PID;另外,可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。从安全角度,Binder的安全性更高。
所以说,Binder相当于是Android中最具有特色的跨进程方式了。
Linux进程间的通信原理如下所示:
IPC 过程中的通信调用方和被调用放分别称为数据发送方和数据接收方,数据发送方进程将数据放在内存缓存区,通过系统调用(系统调用是用户空间访问内核空间的唯一方式)进入内核态 。内核程序在内核空间开辟一块内核缓存区,通过 copy_from_user 函数将数据从数据发送方用户空间的内存缓存区拷贝到内核空间的内核缓存区中,数据接收方进程在自己的用户空间开辟一块内存缓存区,内核程序将内核缓存区中通过 copy_to_user 函数将数据拷贝到数据接收方进程的内存缓存区,这也就是我们上文所提到的数据为什么会复制两次(从应用层拷到内核,从内核拷到应用层)。
1、由于进程与进程间内存是不共享的,因此导致了进程隔离
2、 Linux 采用了虚拟地址空间技术,操作系统在逻辑上将虚拟内存分为用户空间(User Space)和内核空间(Kernel Space),普通应用程序运行在用户空间,系统内核运行在内核空间,为了控制应用程序的访问范围、保证系统安全,用户空间只能通过系统调用的方式去访问内核空间。
Andorid引入了Binder机制,Binder在Android中相当于是进程与进程之间的桥梁,完美的解决的进程间的通信。Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间,映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间,反之内核空间对这段区域的修改也能直接反应到用户空间。内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。
Binder IPC 通信过程如下:
Binder 驱动在内核空间创建一个数据接收缓存区 ,然后在内核空间开辟一块内存缓存区并与数据接收缓存区建立映射关系,数据发送方通过系统调用 copy_from_user 函数将数据从内存缓存区拷贝到内核缓存区,由于内核缓存区通过数据接收缓存区跟数据接收方的内存缓存区存在间接的映射关系,相当于将数据直接拷贝到了接收方的用户空间,这样便完成了进程与进程的通信(简单来说就是数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,所以只要拷一次)。
Binder机制是 Android系统中进程间通讯(IPC)的一种方式,Android中ContentProvider、Intent、AIDL都是基于Binder来实现的。
一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。
由上图我们可以看出, Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。
这里需要注意的是:ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager 时 ,Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。
Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。
这也就说明了原本从ServiceManager中拿到的Binder对象,通过Binder驱动层的处理之后,返回给了Client一个代理对象,如果Client和Server处于同一个进程之中,那么返回的就是当前的Binder对象,反之返回的就是Binder的代理对象。
总结下: