29.Binder机制的理解
目录介绍
- 01.Binder的背景介绍
- 1.1 为何会有Binder
- 1.2 什么是Binder
- 1.3 常见进程通信
- 1.4 为什么要用Binder
- 02.Binder工作流程
- 2.1 Binder运行机制
- 2.2 工作流程介绍
- 2.3 工作流程图
- 2.4 举例说Binder通信
- 03.Binder设计思想
- 3.1 Binder设计流程
- 3.2 Binder设计思考
- 04.Binder源码流程
- 4.1 Binder通信原理图
- 4.2 Binder安全校验
00.先思考一些问题
- 01.为什么会有Binder?不在同一个进程中的Activity和Service(比如把服务定义成一个独立进程)是如何通信的?
- 02.Binder的设计思想是什么?多进程通信有哪些方式?为什么在Android系统中要是用Binder通信,有什么优缺点?
- 03.进程通信有共享内存,管道,socket,消息队列。他们的原理大概都是什么样的?Binder中使用mmap是什么东西?
- 04.既然有现有的IPC方式,为什么重新设计一套Binder机制呢。是否用Binder中mmap思想解决你的APP卡顿问题?
- 05.Binder机制:ServiceManager什么时候注册的?这个是用来干嘛的?Binder内核所在的进程是如何找到到Server跟Client进行通信的?
01.Binder的背景介绍
1.1 为何会有Binder
- 每一个应用程序都是由一些Activity和Service组成的,这些Activity和Service有可能运行在同一个进程中,也有可能运行在不同的进程中。
- 作为一名Android开发,我们每天都在和Binder打交道,虽然可能有的时候不会注意到,譬如:
- startActivity的时候,会获取AMS服务,调用AMS服务的startActivity方法
- startActivity传递的对象为什么需要序列化
- bindService为什么回调的是一个IBinder对象
- 多进程应用,各个进程之间如何通信
- AIDL的使用
- 它们都和Binder有着莫切关系,当碰到上面的场景,或者一些疑难问题的时候,理解Binder机制是非常有必要的。这就是要介绍的Binder进程间通信机制。
1.2 什么是Binder
- 1.直观来说,Binder是Android中的一个类,它继承了IBinder接口。
- 2.从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在linux中没有
- 3.从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,etc)和相应ManagerService的桥梁
- 4.从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
1.3 常见进程通信
- 在传统的Linux上,我们还是有很多选择可以用来实现进程间通信
- 如管道、SystemV、Socket、共享内存等。那么Android为什么不使用这些原有的技术,而是要使开发一种新的叫Binder的进程间通信机制呢?
- 共享内存
- 什么是共享内存:共享内存是进程间通信中最简单的方式之一,共享内存允许两个或更多进程访问同一块内存,当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
image - 性能和安全分析:因为共享内存是访问同一块内存,所以数据不需要进行任何复制,是IPC几种方式中最快,性能最好的方式。由于能任意的访问和修改内存中的数据,如果有恶意程序去针对某个程序设计代码,很可能导致隐私泄漏或者程序崩溃,所以安全性较差。
- 管道
- 什么叫做管道:它具有固定的读端和写端,写进程通过写段向管道文件里写入数据,读进程通过读段从读进程中读出数据,构成一条数据传递的流水线。
image - 性能和安全分析:管道一次通信需要经历2次数据复制(进程A -> 管道文件,管道文件 -> 进程B)。管道的读写分阻塞和非阻塞,管道创建会分配一个缓冲区,而这个缓冲区是有限的,如果传输的数据大小超过缓冲区上限,或者在阻塞模式下没有安排好数据的读写,会出现阻塞的情况。
- 消息队列
- 什么叫消息队列:消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。消息队列允许多个进程同时读写消息,发送方与接收方要约定好,消息体的数据类型与大小。
image - 性能和安全分析:消息队列克服了信号承载信息量少、管道只能承载无格式字节流等缺点,消息队列一次通信同样需要经历2次数据复制(进程A -> 消息队列,消息队列 -> 进程B)
- Socket
- 什么是Socket:Socket原本是为了网络设计的,但也可以通过本地回环地址 (127.0.0.1) 进行进程间通信。
image - 一个Socket会拥有两个缓冲区,一读一写,由于发送/接收消息需要将一个Socket缓冲区中的内容拷贝至另一个Socket缓冲区,所以Socket一次通信也是需要经历2次数据复制。
1.4 为什么要用Binder
- 最简单的回答:
- 性能:相比传统的Socket更高效;安全:安全性高,支持通信双方进行身份验证。
- 性能方面的优势
- 在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对出传统的Socket方式,更加高效。
- Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。
- 安全方面优势
- 传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信ip地址是客户端手动填入,很容易进行伪造。
- 而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。
- 还有一些好处,如实现面象对象的调用方式,在使用Binder时就和调用一个本地实例一样。
- 为何说Binder相比传统的Socket性能更高效?
- 跨进程通讯中,只有socket支持Client-Server的通信方式,但是socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。
- 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
- 共享内存虽然无需拷贝,但控制复杂,难以使用,而且相对来说特别不安全。
- 为何说Binder相比传统IPC安全性更高?
- 首先传统IPC的接收方无法获得对方进程可靠的UID和PID(用户ID进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。
- 使用传统IPC只能由用户在数据包里填入UID和PID,但这样不可靠,容易被恶意程序利用。可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。
- 比如命名管道的名称,systemV的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。
- 基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。
- Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。
02.Binder工作流程
2.1 Binder运行机制
- Binder基于Client-Server通信模式,除了Client端和Server端,还有两角色一起合作完成进程间通信功能。
- Binder通信的四个角色:
- Client进程:使用服务的进程。
- Server进程:提供服务的进程。
- ServiceManager进程:ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。
- Binder驱动:驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
- 接触这些概念可能会觉得难于理解,读者可以把四个角色和熟悉的互联网进行类比:
- Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。
2.2 工作流程介绍
- Binder的工作流程是怎样的?
- 1客户端首先获取服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的“引用”,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就像访问本地方法一样。
- 2客户端通过调用服务器代理对象的方式向服务器端发送请求。
- 3代理对象将用户请求通过Binder驱动发送到服务器进程。
- 4服务器进程处理用户请求,并通过Binder驱动返回处理结果给客户端的服务器代理对象。
- 5客户端收到服务端的返回结果。
2.3 工作流程图
- binder工作流程图如下所示:
image
- Binder主要能提供哪些功能?
- 用驱动程序来推进进程间的通信。
- 通过共享内存来提高性能。
- 为进程请求分配每个进程的线程池。
- 针对系统中的对象引入了引用计数和跨进程的对象引用映射。
- 进程间同步调用。
2.4 举例说Binder通信
- 我们知道应用进程与SystemServer进程属于两个不同的进程,进程之间需要通讯。举例子比如说:AMS通信
- android系统采取了自身设计的Binder机制,这里的ActivityManagerProxy和ActivityManagerNative都是继承与IActivityManager的而SystemServer进程中的ActivityManagerService对象则继承与ActivityManagerNative。
- 简单的表示:Binder接口 --> ActivityManagerNative/ActivityManagerProxy --> ActivityManagerService;
- 这样ActivityManagerNative与ActivityManagerProxy相当于一个Binder的客户端
- 而ActivityManagerService相当于Binder的服务端
- 这样当ActivityManagerNative调用接口方法的时候底层通过Binder driver就会将请求数据与请求传递给server端,并在server端执行具体的接口逻辑。
- 需要注意的是Binder机制是单向的,是异步的,也就是说只能通过client端向server端传递数据与请求而不同等待服务端的返回,也无法返回,
- 那如果SystemServer进程想向应用进程传递数据怎么办?这时候就需要重新定义一个Binder请求以SystemServer为client端,以应用进程为server端,这样就是实现了两个进程之间的双向通讯。
03.Binder设计思想
3.1 Binder设计流程
- Binder设计流程图
image
- 通过图来说一下流程
- Server进程向ServiceManager注册,告诉ServiceManager我是谁,我有什么,我能做什么。就好比徐同学(Server进程)有一台笔记本(computer对象),这台笔记本有个add方法。这时映射关系表就生成了。
- Client进程向ServiceManager查询,我要调用Server进程的computer对象的add方法,可以看到这个过程经过Binder驱动,这时候Binder驱动就开始发挥他的作用了。当向ServiceManager查询完毕,是返回一个computer对象给Client进程吗?其实不然,Binder驱动将computer对象转换成了computerProxy对象,并转发给了Client进程,因此,Client进程拿到的并不是真实的computer对象,而是一个代理对象,即computerProxy对象。很容易理解这个computerProxy对象也是有add方法,(如果连add方法都没有,岂不是欺骗了Client?),但是这个add方法只是对参数进行一些包装而已。
- 当Client进程调用add方法,这个消息发送给Binder驱动,这时驱动发现,原来是computerProxy,那么Client进程应该是需要调用computer对象的add方法的,这时驱动通知Server进程,调用你的computer对象的add方法,将结果给我。然后Server进程就将计算结果发送给驱动,驱动再转发给Client进程,这时Client进程还蒙在了鼓里,他以为自己调用的是真实的computer对象的add方法,其实他只是调用了代理而已。不过Client最终还是拿到了计算结果。
3.2 Binder设计思考
- Binder设计思考
- Service Manager是如何成为一个守护进程的?即Service Manager是如何告知Binder驱动程序它是Binder机制的上下文管理者。
- Server和Client是如何获得Service Manager接口的?即defaultServiceManager接口是如何实现的。
- Server是如何把自己的服务启动起来的?Service Manager在Server启动的过程中是如何为Server提供服务的?即IServiceManager::addService接口是如何实现的。
- Service Manager是如何为Client提供服务的?即IServiceManager::getService接口是如何实现的。
3.4 Binder线程管理
- Binder中是如何进行线程管理的?
- 每个Binder的Server进程会创建很多线程来处理Binder请求,可以简单的理解为创建了一个Binder的线程池吧(虽然实际上并不完全是这样简单的线程管理方式),而真正管理这些线程并不是由这个Server端来管理的,而是由Binder驱动进行管理的。
- 一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。理解这一点的话,你做进程间通信时处理并发问题就会有一个底,比如使用ContentProvider时(又一个使用Binder机制的组件),你就很清楚它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程在跑。
- 总结binder讲的是什么?
- 通常意义上来说,Binder就是指Android的通信机制;
- 对于服务端进程来说,Binder指的是Binder本地对象,对于客户端进程来说,Binder指的是Binder代理对象。
- 对于传输过程来说,Binder是可以进行跨进程传递的对象;
04.Binder源码流程
4.1 Binder通信原理图
- Binder底层使用到mmap
- Binder是基于内存映射mmap设计实现的,通过这种方式,直接操作映射的这一部分内存,通过mmap,Binder通信时,只需要经历一次数据复制,从而获得更好的性能。
- 一次Binder IPC通信的过程分为以下几个步骤:
- 首先,Binder驱动在内核空间中开辟出一个数据接收缓冲区
- 接着,在内核空间中开辟出一个内核缓冲区
- 将内核缓冲区与数据接收缓冲区建立映射关系
- 将数据接收缓冲区与接收进程的用户空间地址建立映射关系
- 发送方进程通过copy_from_user将数据从用户空间复制到内核缓冲区
- 由于内核缓冲区与数据接收缓冲区有映射关系,同时数据接收缓冲区与接收进程的用户空间地址有映射关系,所以在接收进程中可以直接获取到这段数据
- 这样便完成了一次Binder IPC通信,它的原理如下图所示:
image
4.2 Binder安全校验
- 下图展示Binder IPC架构的一个简单例子。
image - 每个通过Binder框架实现IBinder接口,允许被访问调用的对象都可被称作Binder对象。对Binder对象的调用在一个Binder事务处理(transaction)内部实现,其中包括一个对目标对象的引用、需执行方法的ID和一个数据缓冲区。
- 如何保证Binder安全性
- Binder驱动会将调用进程的ID(PID)和有效用户ID(EUID)自动添加到事务处理数据。被调用进程可以检查PID和EUID,然后基于内部实现逻辑来决定是否执行所请求的方法。
- 因为PID和EUID是由内核填写的,所以调用者进程不能通过伪造身份标识来获取超出系统允许的权限。这是Android安全模型的核心之一,所有更高层次抽象,比如权限,均是建立在它之上。
- 调用者的PID和EUID通过类android.os.Binder的getCallingPid()和getCallingUid()来访问,他们是Android公开API的一部分。