参考:
https://juejin.im/post/5acccf845188255c3201100f
https://www.jianshu.com/p/1eff5a13000d
https://blog.csdn.net/andywuchuanlong/article/details/43937493
https://blog.csdn.net/carson_ho/article/details/73560642
Binder 概述
Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现;
字面上来解释 Binder 有胶水、粘合剂的意思,顾名思义就是粘和不同的进程,使之实现通信。
为什么必须理解 Binder ?
作为 Android 工程师的你,是不是常常会有这样的疑问:
- 为什么 Activity 间传递对象需要序列化?
- Activity 的启动流程是什么样的?
- 四大组件底层的通信机制是怎样的?
- AIDL 内部的实现原理是什么?
- 插件化编程技术应该从何学起?等等...
这些问题的背后都与 Binder 有莫大的关系,要弄懂上面这些问题理解 Bidner 通信机制是必须的。
binder使用场景简介:
1, 我们知道 Android 应用程序是由 Activity、Service、Broadcast Receiver 和 Content Provide 四大组件中的一个或者多个组成的。
2, 有时这些组件运行在同一进程,有时运行在不同的进程。
3, 这些进程间的通信就依赖于 Binder IPC 机制。
4, 不仅如此,Android 系统对应用层提供的各种服务如:ActivityManagerService、PackageManagerService 等都是基于 Binder IPC 机制来实现的。
5, Binder 机制在 Android 中的位置非常重要,毫不夸张的说理解 Binder 是迈向 Android 高级工程的第一步。
为什么是 Binder ?
Android 系统是基于 Linux 内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢?
主要是基于性能、稳定性和安全性几方面的原因。
优势 | 描述 |
---|---|
性能 | 只需要一次数据拷贝,性能上仅次于共享内存 |
稳定性 | 基于 C/S 架构,职责明确、架构清晰,因此稳定性好 |
安全性 | 为每个 APP 分配 UID,进程的 UID 是鉴别进程身份的重要标志 |
Linux 下传统的进程间通信原理
了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。
基本概念介绍
上图展示了 Liunx 中跨进程通信涉及到的一些基本概念
- 进程隔离
- 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
- 系统调用:用户态/内核态
系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。
传统的 IPC 通信方式有两个问题:
- 性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝
- 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。
Binder 跨进程通信原理
Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;
在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。
特性: 内存映射。
内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
Binder IPC 实现原理
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
Binder 通信模型
一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。
- Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。
- 其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。
- 其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。
- Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。
- Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。
Binder 通信中的代理模式
假对象,真数据
A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。
- 驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy;
- 这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。
- Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。
手动编码实现跨进程调用
实现进程间通信用的最多的就是 AIDL。
当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。
借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。