IPC(三) Binder深入前

如果看完前文,大家都觉得太简单啦!!! Binder的跨进程,天真。面向对象的思维让Binder这个错中复杂的词汇变得尤为“简单”,用户不用关心实现细节,比如远程的Binder蒙蔽不单单是客户端,有可能是正在编码的你!?接下来在基于源码的前提下通过文字进一步了解Binder

进程隔离:

进程隔离是为了保护操作系统中进程互不干扰而设计的一组软件和硬件的技术, 这样做是为了避免进程A写入进程B,因为A,B的虚拟位置不一样,所以这样可以防止A将数据写入B。
自维基百科;操作系统的不同进程之间,数据不共享;对于每个进程来说,它都天真地以为自己独享了整个系统,完全不知道其他进程的存在;(有关虚拟地址,请自行查阅)因此一个进程需要与另外一个进程通信,需要某种系统机制才能完成。

关于用户态/内核态

上篇文章已经讲解了有关于用户态和内核态的定义,这里讨论一下Linux中跨进程的相关方式:

  • scoket和管道都是通过内核的支持。
  • 但是Binder并不是Linux的内核一部分,他是怎么做的操作呢? Linux有一个动态可加载内核模块LKM,这个模块是一个单独功能的程序,他可以单独的编译,但是不能单独的运行,这意味着它运行的时候会被连接到内核态当做内核态的一部分来运行

超重要:在安卓系统中, 运行在内核空间的,负责负责各各用户进程间通信的内核叫Binder驱动

什么是驱动:驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作;

为什么使用Binder

主要有两点,性能安全
在移动设备上,广泛地使用跨进程通信肯定对通信机制本身提出了严格的要求;Binder相对出传统的Socket方式,更加高效;另外,传统的进程通信方式对于通信双方的身份并没有做出严格的验证,只有在上层协议上进行架设;比如Socket通信ip地址是客户端手动填入的都可以进行伪造;而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。这个也是Android权限模型的基础。

Binder模型

首先跨进程的双方,服务端客户端由于上述的所谓的进程隔离,每个进程的设置的都是为避免不干扰的而设定的,所以两端如果不通过某种介质,是无法进行通信的,所以这里要内核进行帮助而这个运行在内核的程序叫做Binder驱动,而还有一个类似与“通讯录”的东西叫做SM,ServiceManage。

image

建立通信过程

  1. 建立通信录,首先一个进程向驱动申请成为SM,驱动同意后向成为管理Service的SM。
  2. 每个servise启动向SM申请注入通信录,这样SM就会建立一张表注入服务和相关的地址(当然这里的说法比较通俗和实际情况有出入)
  3. 当Client端想与服务端进行通信的时候,首先会询问SM,这样Client会接收到“电话号码”然后进行通信。

这就是一个简单的流程,中间比较多恰当的地方,因为面向对象的思想,让很多细节或者过程直接省略掉,但是这上面讲述得就是一个比较完美的通信方式。

通信过程

上面只是简单的进行通信需要建立的过程,下面开始真实的介绍通信的过程。但是我们得记住四个主角:SM, Client, Service,driver(Binder驱动)
**
一般情况因为内核可以访问两端的任何一个数据,所以最直接的方式就是,把Acopy到内核中,然后在从内核中copy到B中,这样实现了Binder的通信。(
这里值得注意的一点是,用户空间想操作内核空间只能通过系统调用,所以这里的系统调用分为copy_from_user, copy_to_user**)。

但是,Binder并不会这样做,这里就有请神秘嘉宾Binder驱动登场。

Binder驱动为我们做了跨进程的通信一切。

image

上面一张图很好的诠释了该过程,首先Server向SM注册时候他会给查找表相关信息如文所说obj的add方法。

接着如果客户端向SM查询的时候,然后我们联系该进程的对象,重点来了,所有跨进程的通信都会运行在驱动里,驱动在这个时候在SM表中查询到了相关进程的对象不会直接返回对象,他在数据的流通时候,进行了相关的小手段,是返回一个看起来跟object一模一样的代理对象objectProxy(这个对象我们在上一张AIDL的自动生成接口里以及看到过了),这个代理对象也有和原生Obj相同的方法(如上文add方法),上文已经讨论过了傀儡对象objectProxy的add方法只是把相关的参数打包传给驱动,(?????),可是天真的Client端还傻乎乎的以为调用的是真的Object的add方法,所以线程挂起等待,该对象的返回,在另一端驱动就调用相对应的对象的方法,然后把打包的数据传给服务端,等待服务端返回结果给我,然后驱动这个结果传给Client端,然后Client端拿到结果线程唤醒在,全程客户端还傻呼傻呼的以为调用的是本地对象方法,然后继续执行,该简单的通信流程也就基本上完成了。

评价:

由于驱动对返回的代理对象和该本地对象太相似了,“给人的感觉好像把Server端的对象传递给Client进程”,因此隐晦的说”Binder对象是一个可以进行跨进程传递的对象”。Binder好像一个处处留情的情种,为了不让每个情人发现,他就给每一个情人设置了一个自己的分身,每个情人都可以通过分身给他聊天,有没有像你周围某人的QQ,列表有很多暧昧的女孩呢?(典型的代理模式),一句话总结就是:Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。

上文补充:

  1. 对于Binder的访问,我们在得到Binder时候,会调用函数asInterface(),改方法会判断我们传入的Binder所属的位置是不是统一进程,如果是统一进程,就直接返回本地的Binder,否则就返回代理对象。
  2. 上文隐藏了某一细节,以简化过程,让此通俗易懂,SM和Server并不是同一进程,所以相当于注册SM的时候我们也必须进行跨进程通信,所以暗箱操作也适用于Server端,及Server中存在的SM对象也是代理对象,当然Client端相当于有两个代理对象了:SM,Server。

Binder是什么(总结)

上一篇文章已经介绍过Binder是什么的问题,不过只是笼统讨论的只是系统不同层级的Binder,现在从工作的原理来判断Binder是什么:

  • 通常情况我们所谓的Binder是一种通信的机制,比如AIDL是基于Binder来进行进程间通信的。
  • 对于Server进程来说Binder是本地对象
  • 对于Client进程来说Binder是代理对象,但是其实我们基于面向对象的思想不用关心到底是不是本地对象,源码对自动进行判断,且操作都没有区别
  • 对于传输过程而言,Binder代表了具有跨进程传输对象的能力,Binder驱动会对具有会对跨进程的对象进行特殊的处理,自动完成代理对象和本地对象的转换。
  • 在驱动中,为了完成代理对象和本地对象自由转换,Binder驱动必须保存跨进程对象的相关的信息,驱动中本地对象的数据结构叫做binder_node,Binder的代理对象叫做binder_ref,当然很多地方叫法不一,我们来统计一下,本地对象又会叫做Binder实体,Binder的代理会叫做Binder句柄或者Binder引用。

java层的稍微深入

我们知道Proxy对于add方法的实现如下:

Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);//包名写进去
_data.writeInt(a);//
_data.writeInt(b);

BinderProxy            
 
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

把参数写入Parcel中调用远程的transact,这是一个本地方法;它的实现在native层,具体来说在frameworks/base/core/jni/android_util_Binder.cpp文件,里面进行了一系列的函数调用,调用链实在太长这里就不给出了;要知道的是它最终调用到了talkWithDriver函数;看这个函数的名字就知道,通信过程要交给驱动完成了这个函数最后通过ioctl系统调用,Client进程陷入内核态,Client调用add方法的线程挂起等待返回;驱动完成一系列的操作之后唤醒Server进程,调用了Server进程本地对象的onTransact函数(实际上由Server端线程池完成)。

推荐阅读更多精彩内容