一、Linux跨进程有哪些方式

在介绍binder之前,先回顾一下Linux跨进程有哪些方式(为什么在Android体系中要问Linux系统,因为Android系统本质上是Linux系统,只是Linux系统上运行了诸多便于上层使用的各种服务进程),我们知道操作系统的各个进程通常运行在不同的内存空间,因此无法直接相互访问,而需要跨进程的通信。:

1、共享内存:

两个用户态的进程访问同一块内核态的内存,这就叫共享,也是一次复制提高效率的原因。

step1、创建内存共享区:比如在Linux系统中,进程1使用shmget(key_t key,size_t size,int shmflg)函数来生成共享内存块,而生成的内存块与第一个参数key进行绑定。
step2、映射内存共享区:step1已经成功创建了内存共享区,接下来使用shmat来映射到进程1的空间
step3:进程1创建了以后,进程2可以使用shmget来访问这个内存共享区,将step1中的key传进去就行,然后进程2也使用shmat映射后就能正常使用这块共享内存,
step4、此时两个进程都可以访问同一块内存,也就实现了跨进程的通信,需要注意的是,内存共享没有同步的机制,因此需要各进程自己控制同步事宜
step5、使用完共享内存区以后,需要撤销它,使用shmdt来实现
step6、删除内存共享区,shctl函数可以实现

详细的实现可以百度来学习,这里只是解释大概的逻辑。

2、管道

Pipe管道也是常用的进程通信方式,进程1和2通过管道进程数据交换,它有如下特性:

1、进程1和2分立管道俩边,进行数据传递
2、管道是单向的,也就是说数据传递的方向是固定的,要不就是1向2传递数据,要不就是2向1传递,一条管道不能既读又写,能理解,现实生活中的水管只有一个水流方向,不可能来回流(也就是说进程1在write,进程2只能read)
3、管道是有容量的,在2.6.11版本以前的Linux内核,管道容量是一个page的size,之后是65535个字节

但是管道有个致命的问题,在Linux中,int pipe(int pipefd[2],int flags);这样打开一个管道,管道的文件描述符无法给两个不相关的进程间传递(只有父子进程这样在一起的进程1和2,才能同时拿到管道的文件描述符),如果完全是不相干的俩进程,管道就无法实现,因此后来FIFO类型的named Pipe才得以发展,读者可以查阅详细资料

3、socket

socket应当说我们很熟悉了,就不在解释,可以翻之前的博客看看

4、RPC

RPC机制下的两个进程通常不在同一个机器上,由系统负责应用的数据包传递,内核来完成客户端到服务端的交互,它负责将数据传递给服务器端的内核,再传递给服务器端的应用进程上。可以概况为隐藏了实际通信细节的一种IPC方法(IPC就是跨进程通信,RPC就是跨机器远程的IPC)

二、binder是什么

binder是Android系统中跨进程通信(IPC)御用的技术,在源码中无处不在,因此掌握binder通信的必要性无须赘言。从一个初步的印象或者说理解角度来看,可以从TCP网络连接的角度来类比理解:

binder

TCP网络连接

c端

binder客户端

TCP网络客户端

s端

binder服务端

TCP网络服务端

router

binder驱动

TCP网络路由器

DNS

serviceManager

DNS服务器

回想我们在使用socket套接字在做TCP通信时的代码实现,是不是需要一个socket客户端,一个socket服务端,连接的时候通过connect网络来进行相互数据传递,而binder通信也是这样架构的,当然这是从框架的角度来描述其组成部分,而从代码语言角度来说,则可分为三层:

  1. 最底层的binder驱动,保持与Android源码中的其余驱动程序相同的接口,与真正的硬件驱动不同的是,binder本质上无硬件,只是为了保证代码的一致性,纯软件方式参照硬件驱动模式设计的一套框架
  2. frameworks层,分为native(c++)和Java API封装两个模块方向
  3. 应用真正使用的部分,Java语言的应用程序是用AIDL实现,而c++语言的应用程序也可以直接使用binder的IInterface接口开发,换句话说,只要在Android体系中的应用程序,native进程也好,纯Java的apps进程也好,都能放心使用binder提供跨进程通信接口框架,而且native中的c++进程也是非常方便,完全不必要担心需要开发者手动实现代码逻辑,与Java如出一辙,注意是逻辑相同。

三、跨进程是如何传递数据的

我们知道,如果是基本数据类型,可以直接复制到目标进程即可,但如果是对象怎么办?在单进程中对象的复制是引用、指针地址,而进程间的内存隔离机制导致目标进程不认识原进程中的引用,因此跨进程传递数据时传地址值是无效的,进程间传递数据是binder非常重要的一环,我们能看到Java语言在使用AIDL跨进程的时候也是用Parcel,因此在讲解binder前有必要先说明,它就是Parcel。

Parcel是数据的载体,用于打包承载希望通过IBinder发生的相关信息(包括基本数据和对象引用)。既然对象在内存的地址值无法传递,那是不是可以把对象在进程A中的内存数据打包起来,传递给进程B,然后进程B在复现这个对象呢?其实这就是parcel的作用

parcel提供了一系列的接口函数来设置各种属性,包括基本数据类型和各种类型的数组,应用使用Parcel.obtain()接口来获取一个Parcel对象,obtain函数中维护了一个大小为6的parcel池,如果不够用的时候就会new一个parcel对象,在构造函数中使用jni在本地代码中实现,也可以看出来Android的逻辑大多数是Java封装,jni调用本地native实现,进行parcel的初始化赋值。
接下来使用一个例子来阐述parcel打包数据的过程,这个例子是ServiceManagerProxy中的getService()方法中对Parcel的操作:

Parcel data = Parcle.obtain();//生成一个parcel
...
data.writeInterfaceToken(IServiceManager.descriptor);//传入Ibinder接口标志
data.writeString(name);//传入需要向serviceManager查询的service名称

说到底,writeInterfaceToken就是指定想要访问的那个service,以write为前缀的函数,例如writeString等就是给parcel里放携带的数据,然后parcel将这些数据打包传递给目标service。接下来我们会从以下几个方面来展开:
一、binder驱动与协议

二、“DNS服务器”—ServiceManager

三、native层的c++如何通过binder获取sm服务

四、application层的java应用程序如何通过binder获取sm服务