开篇
Binder是一个Android开发从初级进阶的必经之路,想来自己做Android开发也有两年多的时间了,尽然还没有研究过这一块的东西,真是惭愧。 从这篇文章开始会有一个系列对Binder进行分析,涵盖了Binder的使用、构成、原理以及Binder牵连的一些知识点。注意:如果想完全搞懂每一个细节是不现实的,因为binder是系统层的东西,里面牵扯到很多linux的东西,除非你把linux的原理搞懂。
IPC简介
IPC全名为inter-Process Communication,含义为进程间通信,是指两个进程之间进行数据交换的过程。
为什么要进程间通信呢?
因为在Linux或者所有的操作系统中,不同的应用运行在自己独自的进程中,而进程间是隔离的,这很好理解,如果数据是进程间共享,那么所有的应用都是相互透明的,微信可以使用支付宝的数据,这你敢想,但是应用间又不能完全不通信,所以这时候就需要一些方法来进行进程间通信。
Linux中的IPC机制种类
Linux中提供了很多进程间通信机制,主要有管道(pipe)、信号(sinal)、信号量(semophore)、共享内存(Share Memory)、套接字(Socket)、文件(File)等。
Android中的IPC机制
Android系统是基于Linux内核的,在Linux内核基础上,又拓展出了一些IPC机制。Android系统除了支持套接字,还支持序列化、Messenger、AIDL、Bundle、文件共享、ContentProvider、Binder等。
- 序列化:序列化指的是Serializable/Parcelable,Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。Parcelable接口是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高。
- Messenger:Messenger在Android应用开发中的使用频率不高,可以在不同进程中传递Message对象,在Message中加入我们想要传的数据就可以在进程间的进行数据传递了。Messenger是一种轻量级的IPC方案并对AIDL进行了封装。
- AIDL:AIDL全名为Android interface definition Language,即Android接口定义语言。Messenger是以串行的方式来处理客户端发来的信息,如果有大量的消息发到服务端,服务端仍然一个一个的处理再响应客户端显然是不合适的。另外还有一点,Messenger用来进程间进行数据传递但是却不能满足跨进程的方法调用,这个时候就需要使用AIDL了。
- Bundle:Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输。Acitivity、Service、Receiver都是在Intent中通过Bundle来进行数据传递。
- 文件共享:两个进程通过读写同一个文件来进行数据共享,共享的文件可以是文本、XML、JOSN。文件共享适用于对数据同步要求不高的进程间通信。
- ContentProvider:ContentProvider为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder,但是使用起来比AIDL要容易许多。系统中很多操作都采用了ContentProvider,例如通讯录,音视频等,这些操作本身就是跨进程进行通信。
IPC通信原理
为了更好的理解Binder,我们先来介绍两个Linux进程通信方式的原理,这对我们理解Binder有很大的好处。(这里会讲到一些Linux系统的东西)
1.文件
Linux的很多进程通信方式都牵扯到了文件,就连Binder都是基于文件进行进程间通信的,我们着重来讲一下他。通过访问同一个文件可以实现进程间通信,看似很简单,但是文件是由系统管理的,进程如何访问文件呢,这就要讲到Linux的内核空间和用户空间,User space(用户空间)和 Kernel space(内核空间)。
为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间和内核空间。Linux 操作系统将最高的1GB字节供内核使用,称为内核空间,较低的3GB 字节供各进程使用,称为用户空间。
内核空间是Linux内核的运行空间,用户空间是用户程序的运行空间。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不会受到影响。内核空间的数据是可以进程间共享的。
内核空间是直接映射的物理内存地址,所以他直接可以访问物理内存,而用户空间分配的是虚拟地址,需要经过转换才能得到物理地址;进程间通信说白了就是进程1去访问进程2的内存地址,但是他们的地址都是虚拟的又怎么能访问到呢?这也从侧面说明了为什么需要进程间通信。
文件管理驱动是在内核空间中的,用户空间的进程要访问文件就需要访问内核空间,用户空间访问内核空间,需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。
- copy_from_user:将用户空间的数据拷贝到内核空间。
-
copy_to_user:将内核空间的数据拷贝到用户空间。
现在我们就知道linux操作文件的方式: - 写入:用户空间——>copy_from_user——>内核空间(写入文件)
-
读取:用户空间——>copy_from_user——>内核空间(读取文件)——>copy_to_user——>用户空间
这样的话文件实现进程间通信的步骤是:进程A——>copy_from_user——>内核空间(写入文件)——>copy_to_user——>进程B
通过内核空间作为中介,间接实现了进程间的通信,这只是进程通信的其中一种方式,接下来再介绍一下共享内存实现进程间通信的原理。
2.共享内存
通过上面的文件我们知道想要访问一个文件必须通过内核空间实现,这样必须将数据拷贝到内核空间,而内核空间拿到的数据也必须拷贝到用户空间,这样是非常耗时的,所以linux有另一种进程通信方式共享内存,共享内存的原理是内存映射mmap,内存映射可以将用户空间的一块内存映射到文件,这样我们就可以在用户空间直接操作文件,而不需要经过内核空间,这样就不需要数据的拷贝,从而大大缩短了通信的时间,提高了通信的效率。我们只需要将两个进程映射到同一个文件(不是真实的文件,相当于内存)上就可以实现通信了,其实内存映射并不是在用户空间直接操作文件,而是让我们的代码运行到内核空间也就是处于内核态,这样就相当于在内核空间操作文件,这里很多人可能不懂,不用完全搞懂,前面也说了不可能完全明白的,这是系统层面的东西了。
既然共享内存这么好为什么不直接用它呢?它当然也有缺点,首先是安全性问题,共用一块内存,就相当于完全透明;其次是同步问题,我写的时候你也在写,这样有一个人的数据就会被覆盖或者发生其他的异常。总之共享内存也不是那么完美
3.Binder
简单来说Binder是文件和共享内存的合体,数据接收进程建立一个内核空间的内存映射区,数据发送进程通过copy_from_user将数据拷贝到内存映射区,这样就完成了通信。
创造Binder的原因
既然Linux已经有那么多进程间通信的方式了,为什么Android还要自己创造一个呢?主要有下面几点原因:
- 性能方面:性能方面主要影响的因素是拷贝次数,管道、消息队列、Socket的拷贝次书都是两次,性能不是很好,共享内存不需要拷贝,性能最好,Binder的拷贝次书为1次,性能仅次于内存拷贝。
- 稳定性方面:Binder是基于C/S架构的,这个架构通常采用两层结构,在技术上已经很成熟了,稳定性是没有问题的。共享内存没有分层,难以控制,并发同步访问临界资源时,可能还会产生死锁。从稳定性的角度讲,Binder是优于共享内存的。
- 安全方面:Android是一个开源的系统,并且拥有开放性的平台,市场上应用来源很广,因此安全性对于Android 平台而言极其重要。传统的IPC接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),无法鉴别对方身份。Android 为每个安装好的APP分配了自己的UID,通过进程的UID来鉴别进程身份。另外,Android系统中的Server端会判断UID/PID是否满足访问权限,而对外只暴露Client端,加强了系统的安全性。