Linux和Android的IPC机制种类
IPC全名为 inter-Process Communication,意思是进程间通信,是指两个进程之间进行数据交换的过程。
Linux中的IPC机制种类
Linux中提供了很多进程间通信机制,主要有:管道、信号、信号量、消息队列、共享内存、套接字等
Android中的IPC机制种类
序列化
Serializable/Parcelable
Messenger
Messenger是一种轻量级的IPC方案,并对AIDL进行了封装
AIDL
全名为 Android Interface Definition Language,即Android接口定义语言。Messenger是以串行的方式来处理客户端发来的信息,如果有大量的消息发到服务端,服务端仍然一个一个的处理再响应客户端显然是不合适的。另外还有一点,Messenger用来进程间进行数据传递但是却不能满足跨进程的方法调用,这个时候就需要使用AIDL了。
Bundle
实现了序列化接口,所以它可以方便的在不同的进程间传输。Acitivity、Service、Receiver都是在Intent中通过Bundle来进行数据传递。
ContentProvider
为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder,但是使用起来比AIDL要容易许多。系统中很多操作都采用了ContentProvider,例如通讯录,音视频等,这些操作本身就是跨进程进行通信。
Binder
最为重要的一种IPC机制。
Linux和Binder的IPC通信原理
Linux 预备知识
进程隔离
简单来说就是:操作系统中,进程与进程间内存是不共享的。两个进程之间是没有办法直接访问相互的数据的,这就是进程隔离的通俗解释。两个进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)。
用户空间(User Space)/内核空间(Kernel Space)
为了保证用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间和内核空间。
简单来说,内核空间是系统内核运行的空间,用户空间是用户程序运行的空间。
为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不会受到影响。内核空间的数据是可以进程间共享的,而用户空间则不可以。
系统调用
虽然用户空间和内核空间之间是隔离的,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。
系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。
不同进程的用户空间可以通过如下系统函数和内核空间进行交互。
- copy_from_user() // 将数据从用户空间 copy 到内核空间
- copy_to_user() // 将数据从内核空间 copy 到用户空间
内核态
当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。
用户态
当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。
动态内核加载模块
由于Linux属于单内核,所以为了弥补单内核扩展性与维护性差的缺点,Linux引入动态可加载内核模块,该模块可以在系统运行期间加载到内核或从内核卸载。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。
模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
因此,Android系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通讯。
在Android 系统中,这个运行在内核空间,负责各个用户进程通过Binder实现通信的内核模块就叫做Binder驱动(Binder Dirver)
内存映射(mmap)原理解析
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动反映到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
Linux的IPC通信原理
通过上图我们知道一次进程间通讯的流程是:
- 将消息发送方要发送的数据放在内存缓存区中, 通过系统调用进入内核态
- 内核程序在内核空间分配内存, 开辟一块内核缓存区, 调用 copy_from_user() 函数将数据从用户空间的内存缓存区 copy 到内核空间的内核缓存区中
- 接收方进程在接收数据时在自己的用户空间开辟一块内存缓存去, 然后内核程序调用 copy_to_user() 函数将数据从内核缓存区 copy 到接收进程的内存缓存区
缺点
- 性能低下,一次数据传递需要经历2次数据拷贝。
- 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。
Binder的通信原理
Binder 是基于开源的OpenBinder实现的,OpenBinder最早并不是由Google公司开发的,而是Be Inc公司开发的,接着由Palm,Inc.公司负责开发。后来OpenBinder的作者Dianne Hackborn加入了Google公司,并负责Android平台的开发,顺便把这项技术也带进了Android。
Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程通常是这样:
- Binder 驱动在内核空间创建一个数据接收缓存区;
- 在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
整个过程只使用了1次拷贝,不会因为不知道数据的大小而浪费空间或者时间,效率更高。
Binder通信模型
一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。
Client/Server/ServiceManager/驱动
Binder是基于C/S架构的,即指客户端(Client)和服务端(Server)组成的架构,Client端有什么需求,直接发送给Server端去完成。
Binder的通信模型有4个角色:Binder Client、Binder Server、Binder Driver(Binder驱动)、ServiceManager。
- Binder Client是请求服务的进程;
- Binder Server是提供服务的进程;
- Binder驱动承载进程间通信的数据转发;
- ServiceManager维护映射关系表,都有哪些服务,每个都是什么、有什么方法可调用。
ServiceManager、Binder Client、Binder Server处于不同的进程,他们三个都在用户空间,而Binder驱动在内核空间。
它们之间的关系,如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。
Binder通信过程
- 首先,一个进程使用BINDERSETCONTEXT_MGR 命令通过Binder驱动将自己注册为ServiceManager;
- Server通过驱动向ServiceManager中注册Binder(Server中的Binder实体),表明可以对外提供服务。驱动为这个Binder创建位于内核中的实体节点以及ServiceManager对实体的引用,将名字以及新建的引用打包传给ServiceManager,ServiceManager将其填入查找表;
- Client通过名字,在Binder驱动的帮助下从ServiceManager中获取到对Binder实体的引用,通过这个引用就能实现和Server进程的通讯。
Binder有什么优势
性能方面
性能方面主要影响的因素是拷贝次数,管道、消息队列、Socket的拷贝次数都是两次,性能不是很好,共享内存不需要拷贝,性能最好,Binder的拷贝次数为1次,性能仅次于共享内存。
稳定性方面
Binder是基于C/S架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。
共享内存没有进行分层,难以控制,并发同步访问临界资源时,可能还会产生死锁。
从稳定性上来说,Binder是优于共享内存的。
安全方面
Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于 Android 平台而言极其重要。
传统的IPC接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对法身份。Android为每个安装好的APP分配了自己的UID,故而进程的UID是鉴别进程身份的重要标志。另外,Androiod系统中的Server端会判断UID/PID是否满足访问权限,而对外只暴露Client端,加强了系统的安全性。
语言方面
Linux是基于C语言,C语言是面向过程过的,Android应用层和Java Framework是基于Java语言,Java语言是面向对象的。Binder本身符合面向对象的思想,因此作为Android的通信机制更合适不过。
感谢
http://liuwangshu.cn/framework/binder/1-intro.html
https://zhuanlan.zhihu.com/p/35519585