Binder原理是什么?

什么是Binder?

直观来说,Binder是Android中的一个类,它继承了IBinder 接口从IPC角度来说,Binder是Android
中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder
该通信方式在linux中没有。
从Android Framework 角度来说,Binder是ServiceManager连接各种
Manager (ActivityManager、 WindowManager,etc)和相应ManagerService 的桥梁
从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService 的时候,服务端
会返回一个包含了服务端业务调用的 Binder对象,通过这个Binder对象,客户端就可以获取服务端
供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务
基于C/S架构,由Client、Server、ServerManager和 Binder驱动组成。进程空间分为用户空间和内核
空间。用户空间不可以进行数据交互;内核空间可以进行数据交互,所有进程共用一个内核空间.
Client、Server、ServiceManager均在用户空间中实现,而 Binder驱动程序则是在内核空间中实现的

为什么要使用Binder?

性能:移动设备中如果广泛的使用跨进程通信机制肯定会对通信机制提出严格的要求,而 Binder 相
较传统的进程通信方式更加的高效。
安全:由于传统进程通信方式没有对通信的双方和身方做出严格的验证,只有上层协议才会去架构
如socket连接的IP地址可以人为的伪造。而Binder 身份校验也是android权限模式的基础。

Binder的线程管理

每个Binder的Server进程会创建很多线程来处理 Binder请求,可以简单的理解为创建了一个Binder
的线程池吧(虽然实际上并不完全是这样简单的线程管理方式),而真正管理这些线程并不是由这个
Server端来管理的,而是由Binder 驱动进行管理的

一个进程的 Binder 线程数默认大是 16,超过的请求会被阳塞等待空闲的 Bider 线。理这一点的
话,你做进程间通信时处理并发问题就会有一个底,比如使用ContentProvider 时(又一个使用
Binder 机制的组件),你就很清楚它的 CRUD(创建、检索、更新和删除)方法只能同时有16个线程
在跑。

Binder的工作流程

客户端首先获取服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的“引用,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就像访问本地方法一样。
客户端通过调用服务器代理对象的方式向服务器端发送请求
代理对象将用户请求通过Binder驱动发送到服务器进程
服务器进程处理用户请求,并通过 Binder驱动返回处理结果给客户端的服务器代理对象
客户端收到服务端的返回结果

AIDL的工作流程

服务端
服务端首先要创建一个远程Service 用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,后在Service中实现这个AIDL接口即可。
客户端
首先绑定服务端的Service,绑定成功后,将服务端返回的 Binder对象转化成AIDL接口所属的类型
接着就可以调用AIDL中的方法了。

Binder有什么优势?   性能方面

共享内存0次数据拷贝
Binder1次数据拷贝
Socket/管道/消息队列2次数据拷贝稳定性方面
Binder:基于 C/S 架构,客户端(Client) 有什么需求就丢给服务端(Server)去完成,架构清晰、职
责明确又相互独立,自然稳定性更好
共享内存:虽然无需拷贝,但是控制复杂,难以使用从稳定性的角度讲,
Binder机制是优于内存共享的。

安全性方面:

传统的IPC没有任何安全措施,安全依赖上层协议来确保
传统的IPC方法无法获得对方可靠的进程用户ID/进程UI(UD/PTD),从而无法鉴别对方身份
传统的IPC只能由用户在数据包中填入UID/PID,容易被恶意程序利用。
传统的IPC访问接入点是开放的,无法阻止恶意程序通过猜测接收方地址获得连接
Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

Binder是如何做到一次拷贝的?

主要是因为Linux是使用的虚拟内存寻址方式,它有如下特性
用户空间的虚拟内存地址是映射到物理内存中的
对虚拟内存的读写实际上是对物理内存的读写,这个过程就是内存映射这个内存映射过程是通过系统
调用mmap0来实现的
Binder 借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射
就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝

Binder机制是如何跨进程的?

Binder驱动
在内核空间创建一块接收缓存区
实现地址映射:将内核缓存区、接收进程用户空间映射到同一接收缓存区
发送进程通过系统调用(copy_from_user) 将数据发送到内核缓存区。由于内核缓存区和接收进程用
户空间存在映射关系,故相当于也发送了接收进程的用户空间,实现了跨进程通信。

为什么Intent不能传递大数据?

Intent 携带信息的大小其实是受 Binder 限制。数据以Parcel对象的形式存放在 Bider传递缓存中。如
果数据或返回值比传递buffer大,则此次传递调用失败并抛出TransactionTooLargeException异常。
Binder 传递缓存有一个限定大小,通常是1Mb。但同一个进程中所有的传输共享缓存空间。多个地方
在进行传输时,即时它们各自传输的数据不超出大小限制,TransactionTooLargeException 异常也可
能会被抛出。在使用Intent 传递数据时,1M并不是安全上限。因为 Binder 中可能正在处理其它的传
输工作。不同的机型和系统版本,这个上限值也可能会不同。

Binder IPC实现原理?

BinderIPC正是基于内存映射(mmap)来实现的,但是mmap0)通常是用在有物理介质的文件系统上
的。
比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户
区域,需要两次拷贝(磁盘->内核空间->用户空间);通常在这种场景下 mmap0就能发挥作用,通过
在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代1/0 读写,提高文件读
取效率。
而Binder 并不存在物理介质,因此 Binder 驱动使用mmap0并不是为了在物理介质和用户空间之间建
立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的BinderIPC通信过程通常是这样
首先 Binder 驱动在内核空间创建一个数据接收缓存区;接着在内核空间开辟一块内核缓存区,建
立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程
用户空间地址的映射关系;
发送方进程通过系统调用copytromuser0)将数据copy到内核中的内核缓存区由于内核缓存区和
接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样
便完成了一次进程间的通信。

Binder的内存拷贝过程:

相比其他的IPC通信,比如消息机制、共享内存、管道、信号量等,Binder仅需一次内存拷贝,即可
让目标进程读取到更新数据,同共享内存一样相当高效,其他的IPC 通信机制大多需要 2次内存拷贝
Binder内存拷贝的原理为:A为 Binder客户端,在IPC调用前,需将其用户空间的数据拷贝到Binder
驱动的内核空间,由于进程B在打开 Binder 设备(/dev/binder)时,已将 Binder驱动的内核空间映射
(mmap)到自己的进程空间,所以进程B可以直接看到 Binder驱动内核空间的内容改动。

Binder拷贝问题

进程的 fork,是拷贝一个和原进程一摸一样的进程,其中的各种内存对象自然也会被拷贝。所以用来接收消息去 fork进程的 binder对象自然也会被拷贝。但是这个拷贝对于APP层有用吗?那自然是没用的,所以就凭白多占用了一块无用的内存区域。
说到这你自然想问,如果通过 socket的方式,不也平白无故的多占用一块 Socket 内存区域吗? 是的
确实是,但是fork出APP进程之后,APP进程会去主动的关闭掉这个 socket,从而释放这块区域。
那我释放掉 Binder 内存不就一样了吗? Binder的特殊性在于其是成对存在的,其分为Client 端对和
Server端对象。假设我们使用 binder,那么因为APP端的 binder 是拷贝自 Zygote进程的,所以如果要释放掉APP的Server端binder引用对象,就必须释放掉AMS中的Client端binder对象,那这样就会导致AMS失去binder从而无法正常向Zygote发送消息

你可能感兴趣的:(binder)