Android 是如何实现跨进程通信的,大家熟的Binder 是什么,怎么设计的,进程间的数据是如何发送接收的?
因为要实现跨进程通信,那么,数据是如何传输的,怎么组织的。
两个进程之间是如何知道对方的标识(引用)的,这一系列问题,都由Binder驱动解决,每个进程需要为其他进程提供服务(API调用),都需要向Binder驱动注册,其他进程才能知道自己的数据传向哪里。
这里大家先忽略ServiceManager的特殊身份。只讨论Binder驱动的觉得定位即可。
这样看来,其实Binder驱动就是一个多个进程之间的中枢神经,支撑起了Android中进程间通信,它内部的设计,与应用程序进程中的业务,不存在任何耦合关系,只负责实现进程间数据通信。
可以用如下图来理解Binder驱动与应用程序进程之间的关系。
当然,Android里的Binder架构应该还有ServiceManager这个系统服务
ServiceManager 下文简称SM,是一个Android操作系统提供的一个系统进程。
那么为什么要单独提他呢,因为这个进程里,记录了所有Binder实体(提供服务的Binder实现对象)的信息。
也就是说,SM是用来给应用程序查找其他应用程序的数据中心与校验中心,保障进程间通信的安全新,合法性。
SM是系统服务,在系统启动后,SM便启动,并执行以下事情:
SM处理的消息类型有:
注册Binder实体信息到SM的时候,请求数据中需要写到Binder实体的描述信息,之后进行查询的时候就是根据描述信息来获取到对应的Binder应用编号。
到这里,我们可以看出,其实整个Binder架构就是一个Client,Server,DNS的结构,当然Binder驱动就扮演了一个路由器的角色。
这个结构的前提,就是DNS需要提前注册。
就是DNS需要提前注册。也就是说SM进程需要第一个注册到Binder驱动中,
而且,Client和Server都知道SM的引用编号(0),能够直接通过SM获取其他进程提供的Binder引用编号
对于应用程序进程来说,打开驱动和内存映射动作由Native类ProcessState完成,该类为单利,在构造方法中进行,先打开,再执行内存映射。
为什么与共享内存进行对比(性能),是因为共享内存管是unix中最快的一种IPC机制。
共享内存为什么快,是因为共享内存相当于是将两个进程的虚拟地址空间指向了一块物理内存,两个进程对该内存区域的修改,能够直接反应到对方进程中,也就是不需要对数据进行拷贝。
前面说到,Binder是通过mmap来实现的,
理论上,mmap也可以让两个进程映射到同一段物理内存区域(文件)上。
但是Binder没有这样实现,如果这样的话,和共享内存就一样了。
那Binder又是如何实现的呢。
首先,Binder有驱动程序,所有数据传输和接收,都是通过Binder驱动来操作的。
这就带来一个问题,Binder驱动是运行在内核态的,那么数据在使用Binder驱动传输时,是需要在内核内存空间与用户内存空间进行拷贝操作的。
试想下,A进程与B进程进行通信,A进程给B进程发送数据data,按照上面的分析,数据data需要先从A进程的用户空间拷贝到Binder驱动的内核空间,再通过Binder驱动写入到(具体实现后面说)B进程的Binder驱动内核空间,最后从Binder驱动再拷贝的B进程的用户空间。如此一来,数据进行了两次拷贝。
其实,Binder驱动内部并不需要两次数据的拷贝,原因在于Binder将内核内存空间与用户内存空间进行了内存映射操作,
具体如下图:
首先,我们从数据接收进程看,内核与用户内存空间,通过mmap映射到了同一块物理内存上。
也就是说对该块物理内存的修改,将会提现到数据接收进程的用户空间和内核空间。
再看数据发送进程,左边的数据发送进程,只是将内核的内存空间映射到了物理内存上。
接着,当数据发送进程需要向数据接收进程传递数据时,数据只需要从数据发送进程的用户内存空间拷贝到数据发送进程的内核内存空间,
此时,因为数据发送进程的内核内存空间与物理内存进行了映射,而数据接收进程的用户内存空间与内核内存空间同时都映射到了同一块物理内存上,所以此次拷贝,直接将数据发送进程的用户空间数据,拷贝到了数据接收进程的用户内存空间。
通过上面的分析,也就能理解,为什么说Binder传输数据时需要拷贝1次数据,共享内存不需要拷贝数据
完成对Binder跨进程通信底层IPC实现分析之后,需要思考,Android如何让两个进程建立联系(如何找到通信进程),那就需要一个系统进程,所有应用程序都知道它,并能联系到它,从这个系统进程那边,能够查找到(通过Service名字符串)需要通讯的进程。
最终,Android采用了Client、Server、ServiceManager的实现架构,其中Client需要从ServiceManager中找到Server,然后Client与Server之间即可进行通信
那么什么进程能够在ServiceManager中注册呢,就是在Android操作系统中注册过(APP清单文件中的Service)的那部分服务才能注册,到这,也就能理解Android为什么采用这种架构模式了,在安全上又进一步约束。
首先要知道Binder驱动是运行在内核态下,内核态的内存是所有进程共享的。
任务一:存储所有进程的Binder信息(引用编号,Server端的虚拟内存地址)
任务二:进程间数据传递
Binder的具体实现应该是在Server进程,也就是说Client进程是无法拿到该实现对象的地址信息的。
那么Binder在Client中代表的仅仅是一个引用(驱动给的)编号,Client能够通过该编号向远端Server发送数据。
驱动,是Binder架构在最核心的一部分,驱动需要做的事情很多
Binder实体(Server端)在驱动中的表述
Binder实体需要在驱动中进行注册,注册时,驱动需要在内核中为Binder实体创建一个结构体binder_node
Server端Binder实体在所有实体链表中的节点结构体
说明:每个Server进程都对应有一个链表,用来存储所有的Binder实体节点,以Binder实体对象的内存地址为索引进行查找。
Binder引用在驱动中以binder_ref结构体的形式存在。
该结构体中存储的主要数据为:
说明:每个Client进程都对应有两个链表,
一个是以Binder实体在驱动中的结构体地址为索引建立的链表,
一个是以Binder实体在驱动中的引用号为索引简历的链表。
虽然Binder实体和Binder引用都在驱动中有不同的结构体来标识,
但是Client和Server在于Binder进行通信时,并不是通过传递这两个结构体来代表不同的Binder的,
而是通过另一个统一的结构体flat_binder_object
来代表本次通信对应的Binder。
既然使用的是同一个结构体,那么这个结构体中应该有的内容:
其中Binder类型有以下几种:
那么flat_binder_object里的内容填充方式具体是怎样的呢?
比如Server将Binder传递给Client,Server发送的flat_binder_object,类型应该是BINDER_TYPE_BINDER,
此时,驱动将会在内核中为Server进程创建对应的binder_node结构,
并且将flat_binder_object中的Binder实体的内存地址保存起来。
接着驱动需要在内核中为Client进程创建一个binder_ref结构,因为Server传过来的Binder实体的内存地址在Client进程是无效的,所以驱动需要为Client进程创建一个Binder对应的引用编号,并将此编号存入binder_ref结构中。
同时,需要将flat_binder_object中的类型改成BINDER_TYPE_HANDLE,以及存储引用编号。
当Client需要使用Server传递过来的Binder的时候,向驱动传递的数据包中,就需要用到Binder的引用编号,驱动将会对引用编号进行校验,这样就能在安全性上得到保障。
当一个Server进程创建了一个Binder实体,之后,这个实体在各个环境中的表述情况为:
本文学自:《Android Binder 实现㳀析》