【Android】Binder机制原理学习

【Android】Binder机制原理学习

Binder 是 Android 系统进程间进行跨进程通信的主要方式。我们都知道 Android 系统是基于 Linux 的,那么 Linux 已经提供了如管道(Pipe)、信号(Signal)、套接字(Socket)、报文队列(Message)、共享内存(Share Memory)、信号量(Semaphore)等方式来进行 IPC,为何Android却要重新设计一套名为 Binder 的机制来进行 IPC 呢?

比较

通信方式

讲到通信方式,想必大家都会想到 C/S 模型,也就是客户/服务器模型。基于 C/S 模型的通信方式被人们广泛运用于各种通信场景下。在 C/S 模型下,Client 端只需要建立与 Server 的连接,便可以轻松地使用 Server 所提供的服务。目前在Linux支持的那么多的 IPC 方式中只有 Socket 支持了 C/S的通信方式。虽然我们可以在其它方式的基础上进行封装来支持这样的模型,但这样大大增加了系统的复杂性。

而 Binder 则很好的支持了 C/S 模型,它定义了四个角色,分别是:Server、Client、ServerManager 及 Binder驱动。其中前三者都运行在用户空间中,而 Binder驱动 则运行在内核空间。用计算机网络来做对比的话,Server、Client 分别是服务器和客户端,而 ServerManager 则类似 DNS(域名服务器),Binder驱动 则类似路由器。在使用Binder的时候,Client 只需要与 Server 建立连接,便可以轻松地使用 Server 所提供的服务。

这也是为什么Android系统使用了 Binder 而不是效率更高的共享内存的原因。

传输性能

前面提到了 Socket 也是一种基于 C/S 模型的 IPC 方式,但由于其传输效率低,开销较大,主要用于跨网络的跨进程通信中。

而消息队列和管道两种方式都采用了存储-转发的方式。当要进行 IPC 过程时,它们需要先将数据拷贝到内核中建立的缓存区,然后再将数据从内核中的缓存区拷贝到接收方的缓存区中。这中间进行了两次拷贝,效率会比较低下。

而共享内存的方式虽然不需要进行数据的拷贝,效率及其高。但其不支持 C/S 模型并且控制非常复杂,很难使用。

而 Binder 的传输效率是所有 IPC 方式中仅次于共享内存的。它在跨进程通信过程中只进行了一次从发送方向接收方的数据拷贝,因此传输性能非常好。

安全性

在传统 IPC 中,没有任何安全措施来防止一些恶意程序进行偷窥隐私数据等恶意行为,完全依赖于上层的协议进行确保。在传统 IPC 中接收方无法获得发送方的进程的 UID(用户ID)和 PID(进程ID),无法鉴别发送方的身份。而在Android系统中,系统为每一个进程都分配了唯一的 UID,可以通过这个UID来鉴别发送方以保证安全性。通过 Binder,很好地保证了传输过程中的安全性。

由于上面三点原因,Android 建立了一套新的 IPC 机制——Binder 来满足对通信方式、传输性能、安全性的要求。总结一下 Binder 的特点就是:Binder 基于 C/S 通信模型,传输过程只需一次拷贝,为发送发添加 UID/PID 身份,既支持实名 Binder 也支持匿名 Binder,安全性高。

Binder中的面向对象

在 Binder 机制中,Binder 就是 Server 提供给 Client 的某些服务的访问接入点。Client 通过它向 Server 发送请求使用服务。在 Client 中想要使用某个 Server 所提供的服务就必须与 Server 建立连接并获取对应的 Binder 的接口。Binder 使用了面向对象的思想描述接入点的 Binder 及在 Client 中的入口:Binder 是一个位于 Server 中的对象,这个对象提供了一套方法用于实现对 Server 的请求。Client中的入口可以看成指向这个 Binder 对象的指针,获得了这个指针后就可以通过它来访问 Server。从 Client 角度来看,通过 Binder 的指针调用其提供的方法和调用当前进程的其他对象的方法并没有太大区别。通过这样面向对象的设计,就成功将 IPC 转化为了通过 Binder 对象的引用调用 Binder 的方法,Binder 就是一种可以跨进程引用的对象。

Binder中的通信模型

前面提到了,Binder中定义了四个角色,分别是 Server、Client、 ServerManager 及 Binder 驱动, 下面我们简单介绍一下 Binder 的通信模型:

Binder驱动

我们一般听到的驱动都是和硬件设备有关的,为硬件设备提供操作接口的程序。而这里的 Binder驱动 其实和硬件并没有太大关系,仅仅是实现方式和驱动程序相同,因此称其为 Binder驱动。它工作于内核态,负责进程间 Binder 通信的建立、Binder 在进程间的传递、Binder 的引用计数管理、数据包在进程间的传递等底层支持。是 Binder 实现的核心。

ServerManager

ServerManager 可以将字符形式的 Binder 的名字转换为 Client 对这个 Binder 的引用,从而使得 Client 可以通过 Binder 的名字来获得具体的引用。这种在 ServerManager 中注册了名字的 Binder 叫做实名 Binder。Server 创建了 Binder 后,将它的名字和引用通过 Binder驱动 发送给 ServerManager,ServerManager 收到数据包以后,取出名字和引用填入一张查找表。

现在有个问题,ServerManager 和 Server 各是一个进程,那么向 ServerManager 注册 Binder 应该也是一个 IPC 过程。那么这里就有点矛盾了,要进行 IPC 首先需要一次 IPC,显然是比较奇怪的,那么这里 Binder 是怎么处理的呢?是不是用了其他 IPC 方式呢?其实 ServerManager 与其他进程也是用 Binder 进行通信的,ServerManager 是 Server 端,有自己的 Binder,相对而言的其他进程则是 Client,通过这个 Binder 的引用进行注册、查询和获取。ServerManager 的 Binder是比较特殊的,不需要名字也不需要注册,当一个进程将自己注册为 ServerManager 时,Binder 驱动会自动为其创建 Binder。这个 Binder 在其它 Client 中的引用都固定为 0,通过 0 这个引用号就可以与 ServerManager 进行通信。

匿名 Binder

不是所有 Binder 都需要给 ServerManager 进行注册,Server 可以通过已经创建的 Binder 将新 Binder 传递给 Client,这种没有通过 ServerManager 注册的 Binder 是一种匿名 Binder,为通信的双方之间建立了一条私密通道。

Binder中的内存映射

在传统的 IPC 方式中,使用的一般都是存储-转发机制。数据往往是先从发送方的缓存区将数据复制到内核缓存区,然后接收方提供一块缓存区,内核再将数据从内核缓存区拷贝到接收方提供的缓存区并唤醒接收线程。这样的机制有两个缺点:

  1. 效率低下,需要进行两次拷贝。
  2. 缓存区由接收方提供,但接收方不知道要提供多达的缓存空间,只能开辟一块大空间来接收消息头获取消息体大小,再进行适当的空间开辟。

而Binder则使用了一种全新的策略,由 Binder 驱动负责管理数据的接收缓存, 数据通过内存映射功能来实现。通过 mmap() 方法,将用户空间的一部分内存区域映射到内核空间。这样映射关系建立后,对这块内存区域的修改可以直接反映到内核空间中,对内核空间的内存的操作也会直接反映到用户空间。

通过上面的内存映射功能,成功地减少了数据的拷贝次数,两个空间各自的修改反映到映射的内存空间,从而被及时的感知到。

具体原理

前面提到,Binder 的 IPC 是通过内存映射(mmap)来实现的,但是 mmap() 实际上通常是用在有物理介质的文件系统上的。而 Binder 不存在物理介质,因此 Binder 驱动 使用 mmap() 不是为了在物理介质和用户空间之间建立映射,而是在内和空间创建数据接收的缓存空间。

下面是一次完整的 Binder 跨进程通信的过程:

  1. Binder 驱动在内核空间创建一个数据接收缓存区。
  2. 在内核空间开辟一块内核缓存区,建立内核缓存区与内核中数据接收缓存区的映射关系,以及内核数据接收缓存区与接收进程用户空间地址的映射关系。
  3. 发送方进程通过系统调用 copy_from_user() 将数据拷贝到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,就相当于把数据发送到了接收进程的用户空间,从而完成了跨进程通信。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

%d 博主赞过: