一、Linux内核的基础知识
1.进程隔离/虚拟地址空间
我们知道在操作系统当中,为了保护操作系统中的某些进程,互不干扰,它就设计了一个叫进程隔离的技术,而这个技术就是为了避免进程A可以去操作进程B的数据情况下去实现的.进程的隔离实现,它用到了虚拟地址空间,进程A的虚拟空间和进程B的虚拟空间,其实是不同的,这样就防止了进程A的数据可以写到进程B里面.操作系统当中,不同进程之间和数据是不共享的,所以说对每个进程来说,它其实认为自己独享操作系统,其实是虚拟的一个空间而已.如果让一个进程同另一个进程进行通信,则需要某种进程间的通信机制,才能完成,也就是在安卓里面Binder通信机制可以完成.
2.系统调用
在Linux内核当中,有一个特别重要的概念叫做系统调用,为什么会有这个调用呢?因为我们对内核会有某些保护机制来告诉应用程序你可以访问某些许可的资源,不许可的资源你是不可以访问的,这也就是把Linux的内核层和上层应用程序抽象分离开,也就是内核层和用户空间,我们用户可以通过系统调用,在用户空间访问内核的某些程序
3.binder驱动
在安卓系统当中,我们知道它是运行在内核空间的,它负责各个用户进程通过Binder通信的内核来进行交互的一个模块,叫做binder驱动.
二、Binder通信机制介绍
1.为什么使用binder
1)Android使用的Linux内核拥有着非常多的跨进程通讯机制
2)性能
在移动设备上,广泛的使用跨进程通信肯定会对通信机制本身提出严格的要求,而binder相当于传统的socket方式,更加的高效
3)安全
由于传统的进程间通信,对于通信双方的身份没有做出严格的验证,只有上层协议才会进行架构,就是说我们socket通信的话,IP 地址是客户端手动填写的,你可以进行人为的伪造,binder机制从协议本身就支持通信双方进行身份校验,这是binder在安全上所做的努力,在这基础上,大大提高了安卓程序的安全性.这binder的身份校验也是安卓的权限模型的基础.
2.binder通信模型
我们可以把跨进程通信的双方,一端称作服务端进程,而另一段称为客户端进程,而我们知道,由于进程隔离的存在,我们是没有办法通过手段在客户端进程访问到服务端进程,如果不进行进程间通信的方式的话,举个栗子:
A同学他要通过通讯录找B同学,我们知道A同学要给B同学打电话,这里一定要有B同学的电话号码,电话号码怎么获取呢?大家都知道电话号码就是从通讯录中获取,而通讯录都在手机里,但道理是一样的,通讯录中保存了每一个人给他起的编号,绰号还有相对应的电话号码,这样A同学通过查找通讯录中B同学的电话号码,就可以给B同学打电话了.但是我们知道,仅仅有B同学的通讯录和电话号码是没有办法进行通讯的,这里我们还需要一个很基础但是很容易忘记的概念,就是电话基站的概念,电话基站是可以用来传递双方电话信号的,这样我们就可以看出一次电话通信的过程,除了通讯的双方A和B,还有两个隐藏的,非常重要的角色,就是我们的通讯录和电话基站,这样类比到我们Binder通讯模型,其实也是一样.两个运行在客户中间的进程要完成通信,必须借助与内核的帮助,而这个运行在内核中的程序,叫做Binder驱动,他的功能类似于我们电话基站,而通讯录叫做ServiceManager,
1)通信录:binder驱动
2)电话基站 : serviceManager
3.binder通信机制原理
通讯的步骤,第一步:serviceManager的建立,serviceManager相当于通讯录,所以说,你首先要有一个进程向驱动提出申请为serviceManager,而那个驱动同意之后,serviceManager负责管理所有的电话号码,这时候还没有同学向serviceManager通讯录中注册,所以说这时候电话号码是没有的.
第二步:A同学想要l联系B同学,这时候需要把联系方式在serviceManager中注册,在每个同学进行启动之后,它就会向serviceManager进行报告,"serviceManager你好,这是我电话号码,你保存一份,这里是service1,这里是内存地址."
第二步的作用就是serviceManager当中建立一个表,对应着各个同学的名字和电话号码.
第三步:A同学想要和B同学进行通信,首先我会查询serviceManager,你告诉我service2的联系方式,而serviceManager收到查询的通知之后,serviceManager会返回给这个同学它所查询的电话号码,然后这个同学就可以拿到电话号码去和他所要联系的同学进行联系.这就是binder通讯模型的三个步骤
那么binder如何进行跨进程通信的呢?那么我们来看这么一张图:
假设我们的client想要调用Server端的返回值是一个object类型的,一个add方法,这其实就是一个跨进程通信的机制
首先,我们的Server它会到serviceManager当中去注册一个表,这里它也会告诉serviceManager,我这里service端有一个object对象,它这里可以执行一个add的方法操作,于是serviceManager就建立好了一张表,这时候,Client端向serviceManager来查询,Server端有没有一个object对象,object对象有没有一个add方法.我们知道进程间的通信它的数据都是在内核空间里面,这时候驱动会在数据流的时候做一些手脚,它并不会返回给Client进程一个真正的Server端object对象,因为这是无法进行操作的,它而是返回object一个代理的对象,而这个代理对象里面它包含了一个add方法,但是大家要注意的是,这个add方法是一个空方法,什么都没有,它没有做add方法的能力,它唯一要做的就是把这个参数包装好之后交给驱动来实现,不过这一层对我们Client端其实是透明的,这就是分层协议的好处,它不知道驱动所做的.它只知道它拿到了这个代理对象add方法,它就调用这个add方法,我们知道add方法什么都不做,调用的时候必然add方法会调用给驱动,这时候驱动收到了client发给它的代理对象的add方法,它一看就明白了,这在我的serviceManager当中有一张表,这个object代理对象它替换了object对象,所以说,我们要访问的是Server当中的object对象中的add方法,于是binder驱动它就会通知Server端,它会调用它的add方法,然后就把结果返回给驱动,驱动又会返回给client,就这样驱动作为client和Server端的一个中介,进行了进程间通信的一个机制.
给大家总结一句话:客户端进程只不过是持有了我们的服务端的一个代理,我们通过代理对象协助驱动,去完成了跨进程通信.
大家记住啊,client端只不过是持有了服务端一个代理对象的引用,然后具体的跨进程通信,都是通过代理对象协助完成的.
到底什么是binder?
1)通常意义,Binder指的是一种通信机制,它是一种跨进程的通讯机制
2)对server进程来说,Binder指的是Binder本地对象/对于Client来说,Binder指的的Binder代理对象
(注:其实客户端接收的是一个Binder代理对象,它不是真正的服务端对象,服务端对象和客户端对象其实是无法进行交互的,而只有通过内核层的serviceManager才可以进行交互)
3)对于传输过程而言,Binder是可以进行跨进程传递的对象
binder驱动会对具有跨进程传递能力的对象做特殊处理,它自动会完成代理对象和服务端对象的转换
三、Aidl的实现
Binder的实例Aidl,我们知道在使用Aidl的时候,编译工具会给我们生成Stub的静态内部类,这个Stub的静态内部类它继承的是我们自己定义的Aidl,说明它是一个Binder的本地对象,它具有了远程服务端承诺给我们客户端数据的能力,而又由于Stub这个抽象类,它是一个抽象的,所以说具体的实现我们要自己来完成,这其实也是Java当中的一个策略模式.
首先我们来看第一个方法asinterface这个方法,我们先看它的参数,它的参数是一个IBinder的参数,IBinder它是一个接口,它代表了跨进程传输的能力,只要实现了这个接口,就能将对象跨进程传递,它是驱动底层知识的.在跨进程数据传递的时候,驱动它会识别onBinder类型的数据,如果是Binder代理对象,那就是Binder的proxy对象.我们来看一下方法的逻辑.
首先,他会进行一个空的判断.再看第二个if语句,这个if的语句就是如果是同一个进程的话,我就使用,它的iin,如果不是同一个进程的话,我就会使用它的proxy,(代理对象)简而言之,如果我们是跨进程的就使用代理对象,不是跨进程的,我就使用同进程的那个对象.
接下来我们看在这个接口当中,我们实现了compute的方法.
在这个compute方法里面,我们可以看到它首先用parcel.把数据序列化了,然后调用了transact这个方法.这个方法是一个native层方法,最终,它还是会调用到OnTransact这个方法
在这里,我们可以看到onTransact它会根据调用号,在跨进程的时候,不会传递参数,只会传递编号,在这里我们可以看到,它就会调用,我们刚才所写的conpute这个跨进程方法,这样就是一个完整的Aidl跨进程调用.