为什么要进程隔离?
我们知道每个进程在运行的时候,都会需要一定的计算机内存空间,但是如果我们将每个程序都装进计算机的内存,那么就会导致计算机的利用率相当的低,而且不用进程在进入和退出内存的过程中,会造成效率非常的低。还有就是当不同的进程直接载入内存的时候,可能会出现A进程的地址内容被B进程修改,从而造成程序错误。
怎么样实现进程隔离?
为了实现进程的隔离,在实际中,我们使用了虚拟地址空间,它为每个进程分配4G的虚拟地址空间,其中1G为内核空间,其他的为用户空间如下图所示。内核空间的安全权限更高,不允许随便访问,只有许可的资源才能被访问。这样每个进程独享4G的虚拟地址空间,它们之间的数据时不共享的,那么,如果它们之间需要通信,就需要某种系统机制来完成。
如何访问内核空间?
上面我们提到内核空间是不允许随便访问的,但是我们的总会有需要去访问内核的资源啊!这个时候,我们就会利用我们经常听说的一个动词:系统调用。通过这个统一的接口,所有资源的访问都在内核的控制下执行,以免导致用户程序对系统资源的越权访问,从而来保证系统的安全性和稳定性。
进程之间通信方式?
通过系统调用,可以实现用户空间对内核空间的访问,那么两个进程之间的通信如何实现?Android的底层是基于Linux实现的,目前Linux支持的跨进程通信(IPC)包括传统的管道、System V IPC(消息队列/共享内存/信号量)、socket,其中socket是基于C/S模式的。但是我们在Android中常用的binder通信机制是传统的Linux系统不支持的,所以它并不是内核的一部分,那么如何实现内核的访问呢?Linux具有动态可加载内核模块机制,具体是指模块是具有独立功能的程序,它可以被单独编译,但是不能运行,它在运行的时候是被链接到内核作为内核的一部分在内核空间运行的。这样,Android系统就可以通过添加一个内核模块模块运行于内核空间,用户空间通过这一桥梁,实现通信。
在Android中,这一内核模块就是binder驱动。
为什么要使用Binder?
Android的内核Linux已经有很多进程间的通信机制了,为什么还需要Binder呢?主要基于两个方面的考虑,性能与安全!
我们的Binder机制也是基于C/S模式的,相对于Socket来说,它会更加的高效,它传输的过程当中,只需要一次数据拷贝,至于为什么只需要一次数据拷贝,我们后面详细说明。共享内存不需要拷贝,但是控制复杂,需要其他机制支持。消息队列与管道都需要两次数据的拷贝。在安全方面,Android为每个安装好的应用程序都分配了唯一的UID,Binder机制中,该身份标记由机制本身在内核中添加,而且接入点是私有的,支持通信双方的身份校验,从而提高安全性。这个也是Android权限模型的基础。
Binder通信模型?
Binder框架由四个角色组成:Server、Client、ServiceManager(以下简称SM)以及Binder驱动,其中前面三个位于用户空间,驱动位于内核空间。它们四个角色的关系和互联网类似:Server是具体网站,Client是客户终端,SM是域名服务器(DNS),驱动是路由器。
我们在日常的上网的过程中,我们客户终端输入一个网址,也就是域名,必须通过域名服务器的解析,将域名个相对应的IP地址转换,才能访问到具体的网站,当连接建立,就完成了通信。这其中,少不了一直后面默默工作的路由器,如果没有它,我们是无法找到链路的。
Binder的通信机制也是类似的:两个运行在用户空间的进程要完成通信,必须借助内核的帮助,这个运行在内核里面的程序叫做Binder驱动,它的功能类似于路由器;域名服务器就是ServiceManager了。
Binder是什么?
Binder使用的C/S模式,要实现该模式,必须实现以下两点:一是server必须有确定的访问接入点或者说地址来接受Client的请求,并且Client可以通过某种途径获知Server的地址;二是制定Command-Reply协议来传输数据。
参照我们上面提到的模型,对Binder而言,Binder可以看成Server提供的实现某个特定服务的访问接入点(也就是路由器), Client通过这个‘地址’向Server发送请求来使用该服务;对Client而言,Binder可以看成是通向Server的管道入口,要想和某个Server通信首先必须建立这个管道(即数据链路)并获得管道入口。
Binder的设计采用了面向对象的思想,其实四个角色都是”Binder”,只是对于不同的角色,表述不一样而已。对于Binder通信的使用者而言,Server里面的Binder和Client里面的Binder没有什么不同,一个Binder对象就代表了所有,它不用关心实现的细节,甚至不用关心驱动以及SM的存在;这就是抽象。
对于Server进程来说,Binder指的是Binder本地对象。
对于Client来说,Binder是一个代理对象(有的时候叫做句柄或引用),它只是Binder本地对象的一个远程代理;对这个Binder代理对象的操作,会通过驱动最终转发到Binder本地对象上去完成。对于同一个本地对象,可以有多个引用,遍布于系统,这个就会引出引用计数的概念,实现在驱动之中。
对于通信的过程,Client中的Binder与Server中的代理是没有区别的,可以在本地代表远端的Server来为Client提供服务。这一功能的转换由Binder驱动来完成。面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体(本地对象)位于一个进程中,而它的引用(代理对象)却遍布于系统的各个进程之中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文里的原意。
Binder机制跨进程原理是什么?
那么跨进程通信的过程是怎么样呢?
假设Client进程想要调用Server进程的object对象的A方法。
首先,Server进程会向SM注册,告诉SM它是谁?它能干什么?于是SM会建立一个表:zhangsan这个名字对应进程Server。
然后Client向SM查询,我需要联系一个叫做zhangsan的进程里面的object对象,这个时候:进程之间通信的数据都会经过运行于内核空间的驱动,驱动会在数据流过的时候做一些手脚,并不会给Client返回一个真正的object对象,而是返回一个objectproxy代理对象,这个代理也会有A方法,但是没有A方法的能力。那么,A方法就会将参数包装之后,返回给驱动,驱动见到这个代理,就会知道真正需要做的事情,他就会让Server进程调用object对象的A方法,最后将结果返回给驱动,驱动再将结果发送给Client进程。这样,整个通信过程就完成了。
这样,Binder跨进程通信传输并没有将一个真正的对象传输给另一个进程,而是传递的一个影子,这个模式在设计模式当中叫做代理模式。Client对Binder的访问,如果在同一个进程,就会直接返回Binder实体;如果是在不同的进程里面,就给的一个代理。
为什么是需要一次数据拷贝?
在两个进程通信的过程中,一般都会需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。但是binder是怎么做到只需要一次数据拷贝的。
在binder_mmap()函数中,实现了同时使用进程虚拟地址空间和内核虚拟地址空间来映射同一物理页面。既然同一物理页面被虚拟地址空间阿和内核虚拟地址空间同时映射了,那么,进程与内核之间就会减少一次数据的拷贝,提高进程间通信的效率。所以在Binder中,值需要copy_from_user()拷贝到内核空间,这个时候也就相当于拷贝到了接收方的用户空间,这个就是Binder只需一次拷贝的秘密。
Servicemanager与实名Binder的关系是啥?
和域名服务器类似,SM的作用是将字符形式的Binder名字转化为Client对该Binder的引用,使得Client能够通过Binder名字获得对Server中的Binder实体的引用。注册名字之后的Binder叫做实名Binder。Server创建了Binder实体,会将这个Binder连同名字以数据包的形式通过Binder驱动发送给SM,通知SM注册。驱动会为这个进程边界的Binder创建内核中的实体节点以及SM对实体的引用,将名字和新建的引用打包传递给SM。SM收到数据包之后,从中取出名字和引用填入一张查找表。
ServeiceManager是如何注册的?
在上面的过程中,ServiceManager是一个进程,Server也是一个进程,它们之间的通信也是进程之间的通信。那么,这将是一个无限的循环啊,Android是如何解决这个无限循环的呢?
Binder的实现如下:SM与其他进程的通信同样采用的binder通信机制,SM是Server端,有自己的Binder实体,其他进程都是Client,需要通过这个Binder引用来实现Binder的注册、查询和获取。SM的Binder没有名字,也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成SM时,Binder驱动会自动为它创建Binder实体。其次是这个Binder的引用在所有Client中都固定为0而无须通过其他手段获取。也就是说,一个Server若要向SM注册自己Binder必须通过0这个引用号和SM的Binder通信。
驱动里面的Binder
我们现在知道,Server进程里面的Binder对象指的是Binder本地对象,Client里面的对象值得是Binder代理对象;在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换;因此Binder驱动必然保存了每一个跨越进程的Binder对象的相关信息;在驱动中,Binder本地对象的代表是一个叫做binder_node的数据结构,Binder代理对象是用binder_ref代表的;有的地方把Binder本地对象直接称作Binder实体,把Binder代理对象直接称作Binder引用(句柄),其实指的是Binder对象在驱动里面的表现形式。
本文是对下面三篇的总结的,强烈建议读下面三个人的博客!
参考博客:
http://weishu.me/2016/01/12/binder-index-for-newer/
http://blog.****.net/universus/article/details/6211589
http://blog.****.net/luoshengyang/article/details/6618363