从源码解析-Android中进程间通信Binder机制之Binder驱动 【二】
从源码解析-Android中进程间通信Binder机制之Service Manager 启动【三】
广义上讲,进程间通信(Inter-process communication IPC)是指运行在不同进程(不论是否在同一台机器上)中的若干线程间的数据交换
我们知道,在操作系统中各个进程通常运行在独立的内存空间中,并且有严格的进程隔离机制来防止进程间的数据访问,因为不这样做就会引起很多的数据问题,这里会涉及到进程空间的概念:
但是问题又来了,那两个进程间怎么通信呢?其实传统的进程间通信机制的原理就是:
传统IPC方法有如下几种:
这是一种常用的进程间通信机制,由于两个进程可以直接共享访问同一块内存区域,减少了数据的复制操作,因而在速度上有优势,实现方式:
管道也是操作系统中常见的一种进程间通信方式,Pipe这个词很好的诠释了通信双方的方式,
RPC涉及的通信双方通常运行在不同的机器中,在RPC机器中,开发人员不需要关心具体的中间传输过程是如何实现的,实现步骤:
还有Socket,消息队列,信号量等其它多种方式,这里不多赘述了。
但是Android并没有采用这些通信方法而选择了Binder(其实有少许地方使用到了其它方式,比如Zygote在fork进程的时候使用的是Socket),为什么呢,个人理解为
这里有一个内存映射的概念,它的出现也是因为传统进程通信方式的弊端;我们知道操作系统分为用户态和内核态,而用户态不能直接与硬件打交道,需要走硬件—>内核—>用户,这就出现了两次拷贝;使用了内存映射后,就只用一次拷贝,大大提高效率。
解释内存映射就要先说虚拟内存,说虚拟内存的概念先说为什么需要虚拟内存
我们知道程序代码和数据需要被解释在内存中才能得以运行,早期计算机内存容量是比较小的,但是当时的程序更小,基本都是很简单的那种,所以把程序直接解释到内存中没有问题,不影响程序运行;但是随着互联网的飞速发展,计算机中装的软件越来越多,而且软件也越来越大,如果这时候还像以前那样把程序全部都解释到内存中,显然这种方式是行不通的,将会导致很多程序无法得到很好的运行,何况后来多任务的操作系统。
有什么解决办法呢,第一个想到的就是切片,将程序分成片段,只让当前需要的那一部分留在内存中,其它的放在外部存储上,执行结束后再调下一个需要运行的片段;在以前的一些古董系统里靠这方法解决一些大程序,并且这个分片工作是由程序员完成的;但是时代在发展,各种语言层出不穷,操作系统也越来越透明,程序员对底层技术的依赖逐渐消失,谁还能这样去操作系统呢;现在还让程序员这样做,不光程序难以运行,可能把机器都能搞蹦,所以系统需要一种新的按需分配内存且不用程序员管理的技术,就这样虚拟内存技术就应运而生
计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换
#####虚拟内存原理
虚拟内存是将系统硬盘空间和系统实际内存联合在一起供程序使用,给程序提供了一个比实际内存大得多的虚拟空间。比如Win操作系统,只有4个G内存,然后把系统盘通常是C盘作为外部存储,这样就能给程序提供远大于4个G的虚拟内存使用。在程序运行时,只把虚拟内存的一小部分存储到内存,其余都存储在硬盘上(也就是说程序虚拟空间就等于实际物理内存加部分硬盘空间)。当被访问的虚拟地址(也就是虚拟内存对应的地址)不在内存时,则说明该地址未被存储到内存,而是被存贮在硬盘中,因此需要的虚拟地址随即被调入到内存;同时当系统内存紧张时,也可以把当前不用的虚拟内存换出到硬盘,来腾出物理内存空间。系统如此周而复始地运转——换入、换出,而用户几乎无法查觉,这都是拜虚拟内存机制所赐。
学习数学的时候肯定学过映射的概念,映射就是将两个事物建立一种一一对应的关系,只是逻辑上的一种关系,在物理上是不存在的;就是说你脑海中的想象,并不是真实存在的。
在这里就是把程序的虚拟内存的一块区域与某物理内存块之间建立一一对应的联系,这就是映射,然后操作程序的虚拟内存就是操作这块物理内存,就不需要经过内核转换;在内存映射过程中,并没有实际的数据拷贝,只是逻辑上放入了内存,具体到代码,就是建立并初始化了相关的数据结构,这个过程由系统调用mmap()实现,所以映射的效率很高。
上面说这么多Linux相关的知识都是为了Binder机制做铺垫
我们知道,Binder是Android中使用最广泛的IPC机制,那作为开发者怎么理解Binder这个东西呢,从字面理解为【捆绑】,也就是将两个需要相互联系的对象用某种力量绑定在一起,那这些参与进来的对象有哪些呢?
Binder机制就是把这些东西绑定在一起,其中Client、Server和Service Manager运行在用户空间,Binder驱动程序运行内核空间
Binder中的核心组件就是Binder驱动,Service Manager提供了辅助管理的功能,Client和Server正是在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信
四个组件中的Binder驱动和Service Manager,Android已经替开发者封装好了,开发者只要写好Client和Server就可以了
从上面的分析是不是感觉Binder原来就这么回事,实际上我感觉Binder是Android系统中最难理解的的一个机制了;那怎么以一个通俗易懂的例子来理解呢?
从组成Binder机制的的四个组件可以看出来,这其实与我们天天接触的TCP/IP网络非常相似
现在就客户端从浏览器访问Google的例子我们来分析
这些步骤中没有提到Router,但是它的作用很重要,将数据包发到用户设定的目标IP中,也会获取Server端发送过来的数据包转发给Client端,所以Router可以说是整个网络通信的基础
在这里我们知道DNS其实不是必须要每一个完整通信过程都要存在的,它只是帮人们将繁杂的IP和好记的域名联系起来
从这个通信过程中我们可以看出IP地址对于每个用户来说是唯一的,是Client和Server进行通信的凭证
Binder的本质就是进程A(客户端)要与进程B(服务器)进行通信,但是因为是跨进程(跨网络),所以必须借助于Binder驱动(路由器)来把请求正确发送到对方所在进程中,而通信的进程们需要持有Service Manager(DNS)颁发的唯一标志(IP)。
类比于TCP/IP网络,其实Binder当中的DNS也不是必须的,因为如果客户端进程能记住服务端进程的Binder标志(IP),就不需要通信前通过查询DNS了。
但是这个通信标志其实是动态改变的,也就是说即使客户端进程记住了这一次通信的服务端进程的标志,但下一次就不管用了,所以还是需要Service Manager来统一管理双方进程的标志。
上面的类比理解中,提到了其实Client端和Server端也是需要知道DNS服务器的ip的,要不然没办法给DNS发请求进行查询,所以在网络接入的时候提前设置好;到Binder机制中,Service Manager是DNS,那它的IP是多少呢?Binder机制做了特别规定,Service Manager在Binder通信过程中的唯一标志永远是0。
Service Manager和其它进程同样采用Binder通信,Service Manager是Server端,有自己的Binder对象(实体);其它进程都是Client,需要通过这个Binder的引用来实现Server的注册,查询和获取。Service Manager提供的Binder比较特殊,它没有名字也不需要注册,当系统启动时,一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成Service Manager时,Binder驱动会自动为它创建Binder实体。其次这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。也就是说,一个Server若要向Service Manager注册自己的Binder就必需通过0这个引用号和Service Manager的Binder通信。
假设Client进程想要调用Server进程的object对象的add方法,对于这个跨进程通信,我们看看Binder机制是如何做的
由于驱动返回的ObjectProxy与Server进程里面原始的Object是如此相似,给人感觉好像是直接把Server进程里面的对象Object传递到了Client进程;因此,我们可以说Binder对象是可以进行跨进程传递的对象
但事实上我们知道,Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程好像是Binder跨进程穿越的时候,它在一个进程留下了一个真身,在另外一个进程幻化出一个影子(这个影子可以很多个);Client进程的操作其实是对于影子的操作,影子利用Binder驱动最终让真身完成操作。
理解这一点非常重要;务必仔细体会。另外,Android系统实现这种机制使用的是代理模式, 对于Binder的访问,如果是在同一个进程(不需要跨进程),那么直接返回原始的Binder实体;如果在不同进程,那么就返回Binder的引用(影子);一个Binder实体可以有很多Binder引用,因为可以有很多Clinet要使用Server;我们在系统源码以及AIDL的生成代码里面可以看到很多这种实现。
Server进程里面的Binder对象指的是Binder本地对象,Client里面的对象指的是Binder代理对象;在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换;因此Binder驱动必然保存了每一个跨越进程的Binder对象的相关信息;在驱动中,Binder本地对象的代表是一个叫做binder_node的数据结构,Binder代理对象是用binder_ref代表的;有的地方把Binder本地对象直接称作Binder实体,把Binder代理对象直接称作Binder引用(句柄)
一句话总结就是:Client进程只不过是持有了Server端的代理对象;代理对象协助驱动完成了跨进程通信。
接下来可以查看下面这篇AIDL文章,相信你看完后会理解的更透彻
Android通过AIDL达到进程间通信