1 Binder 概述
Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现;
OpenBinder 起初由 Be Inc. 开发,后由 Plam Inc. 接手。
1.1 为什么必须理解 Binder ?
- 作为 Android 工程师的你,是不是常常会有这样的疑问:
- 为什么 Activity 间传递对象需要序列化?
- Activity 的启动流程是什么样的?
- 四大组件底层的通信机制是怎样的?
- AIDL 内部的实现原理是什么?
- 插件化编程技术应该从何学起?等等...
- 这些问题的背后都与 Binder 有莫大的关系,要弄懂上面这些问题理解 Bidner 通信机制是必须的。
1.2 为什么是 Binder ?
Android 系统是基于 Linux 内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。
那为什么 Android 还要提供 Binder 来实现 IPC 呢?主要是基于性能、稳定性和安全性几方面的原因。
1.2.1 性能
Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。
消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
共享内存虽然无需拷贝,但控制复杂,难以使用。
**Binder **只需要一次数据拷贝,性能上仅次于共享内存。
IPC方式 | 数据拷贝次数 |
---|---|
共享内存 | 0 |
Binder | 1 |
Socket/管道/消息队列 | 2 |
1.2.2 稳定性
- Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。
- 共享内存虽然无需拷贝,但是控制负责,难以使用。
- 从稳定性的角度讲,Binder 机制是优于内存共享的。
1.2.3 安全性
- 传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。
- 首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。
- Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。
- 传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。
- 可靠的身份标识只有由 IPC 机制在内核中添加。
- 其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。
同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。
1.2.4 Binder 的优势:
优势 | 描述 |
---|---|
性能 | 只需要一次数据拷贝,性能上仅次于共享内存 |
稳定性 | 基于 C/S 架构,职责明确、架构清晰,因此稳定性好 |
安全性 | 为每个 APP 分配 UID,进程的 UID 是鉴别进程身份的重要标志 |
基于上述原因,Android 需要建立一套新的 IPC 机制来满足系统对稳定性、传输性能和安全性方面的要求,这就是 Binder。
2 Binder 跨进程通信原理
2.1 动态内核可加载模块
- 跨进程通信是需要内核空间做支持的。
- 传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。
- Binder 并不是 Linux 系统内核的一部分,能够通信得益于Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;
- 模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。
- Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。
- 在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。
2.2 内存映射
- Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,**mmap() **是操作系统中一种内存映射的方法。
- 内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。
- 映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
- 内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。
- 两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。
2.3 Binder IPC 实现原理
- Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。
- 进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。
- Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程通常是这样:
- 1.首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 2.接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 3.发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
3 Binder的工作机制
3.1 binder的工作流程
- 通过上图,我们可以看出binder的工作流程是这样实现的:
- (1)Client发送请求给Server,在Server未返回结果前,Client处于阻塞状态。请求的数据和相应操作交给Client的Proxy处理。
- (2)Proxy代理将数据封装后,交给Binder驱动程序进行数据的传输调度。(transact())
- (3)Binder驱动程序将数据发送个Server,Server对数据进行解析,并根据相应的操作标识进行相应的操作处理。
- (4)Server操作完成之后,将执行之后结果数据封装。然后将数据交给Binder驱动程序调度。(onTransact())
- (5)Binder驱动程序将结果数据交给Client的Proxy代理,Proxy代理将数据解析后将最终结果返回给Client。
- (6)Client接收到数据之后,结束阻塞状态。
3.2 Server向ServiceManager注册服务
- 在Binder的工作机制中,我们是依赖binder驱动程序去执行数据的调度,发送方依赖binder打包数据,接收方依赖binder回传数据
- 注册流程是这样的:
- (1)Server在自己的进程中向Binder驱动程序申请创建一个作为自己Service的Binder实体。
- (2)Binder驱动程序为这个Service创建位于内核中binder实体节点和binder引用,然后将Service的名称和binder的引用传给ServiceManager,通知ServiceManager注册相应的Service服务。
- (3)ServiceManager接收到数据之后,根据Service的名称和binder引用填写入一张表中。
3.3 Client从SericeManager获取Service的远程接口
- 如果这时Client向Server发送申请该Service服务请求时,ServiceManager就会通过该服务的名称,查找表获取相应服务的binder引用,返回给Client。
3.4 总结
总结:作为数据发送方,它持有Binder的实体;作为数据接收方,它持有Binder的引用。
4 Binder 通信模型
一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。
4.1Client/Server/ServiceManager/BinderDriver驱动
- Binder 是基于 C/S 架构的。由一系列的组件组成,包括Client、Server、ServiceManager、Binder 驱动。
- Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。
- 其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。
- Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。
4.2 网页请求模型
- Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。
- 通常我们访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了 www.google.com 对应的 ip 地址 10.249.23.13,然后通过这个 ip 地址才能放到到 www.google.com 对应的服务器。
4.3 流程
- Binder Driver位于内核空间中,其以字符设备中的misc类型注册
- 用户可以从/dev/binder设备文件节点上,通过open和ioctl文件操作函数与Binder Driver进行通信,其主要负责Binder通信的建立,以及其在进程间的传递和Binder引用计数管理/数据包的传输等。
- 1.当手机启动后,ServiceManager会先向Binder Driver进行注册,它在Binder Driver中是最先被注册的,其注册ID为0。
- 2.当其他的服务想要注册到Binder Driver时,会先通过这个0号ID获取到ServiceManager所对应的IBinder接口,该接口实质上的实现逻辑是由BpBinder实现的。
- 3.获取到对应的接口后就回调用其中的transact方法,此后就会在Binder Driver中新注册一个ID 1来对应这个服务。
- 4.如果有客户端想要使用这个服务,那么,它先会像 Binder Driver 一样获取到ID为0的接口,也就是ServiceManager所对应的接口,并调用其transact方法要求连接到刚才的服务,这时候Binder Driver就会将ID为1的服务回传给客户端并将相关消息反馈给 ServiceManager 完成。
5 Binder架构
- Java应用层: 对于上层应用通过调用AMP.startService, 完全可以不用关心底层,经过层层调用,最终必然会调用到AMS.startService.
- Java IPC层: Binder通信是采用C/S架构, Android系统的基础架构便已设计好Binder在Java framework层的Binder客户类BinderProxy和服务类Binder;
- Native IPC层: 对于Native层,如果需要直接使用Binder(比如media相关), 则可以直接使用BpBinder和BBinder(当然这里还有JavaBBinder)即可, 对于上一层Java IPC的通信也是基于这个层面.
- Kernel物理层: 这里是Binder Driver, 前面3层都跑在用户空间,对于用户空间的内存资源是不共享的,每个Android的进程只能运行在自己进程所拥有的虚拟地址空间, 而内核空间却是可共享的. 真正通信的核心环节还是在Binder Driver.
参考
https://blog.csdn.net/universus/article/details/6211589
https://juejin.im/post/5acccf845188255c3201100f?utm_medium=an&utm_source=weixinqun
https://www.jianshu.com/p/82cdb9d53ca3
https://juejin.im/post/5a181ed151882512a86100e5