背景
Binder机制,是Android系统跨进程协作的核心。我们知道,每个应用独立地运行在自己的进程虚拟地址空间中,就像是独自占有CPU、内存等资源,相互之间不可见。Android系统中的一个个应用,亦如一个个信息孤岛。正是Binder这样的跨进程访问机制,提供了孤岛之间沟通的桥梁。
如上图所示,鉴于安全和效率的考虑,Android系统服务提供是一种典型的C/S架构,如果希望获取系统服务,就必须进程IPC请求,而Binder正是数据交互的核心。
平时做应用开发,其实很少会操心这种底层机制,但是设计思想却是通用的,为了学习它的这种思想,同时弄懂Binder机制,更好地进行应用开发,这几天找了很多资料。现在开始,才有那么点绕出来的感觉。这里,不谈源码实现,通过对各种资料的汇集,更多的以图的形式通俗展现Binder的内部机制。
Binder通信基础
就整个通信过程,将围绕着数据传递的前提,数据如何传递,以及传递背后的协议来展开。
用户空间和内核空间---谁来中转数据
我们知道,进程运行在虚拟地址空间,鉴于安全考虑,这块空间被分为用户空间和内核空间。其中,用户空间的执行权限较低,凡是需要访问物理设备、IO等,都需要通过系统调用的方式,由内核代码在内核空间运行。如下图所示:
由上图可知,每个进程通过系统调用进入内核,Linux内核空间由系统内的所有进程共享。从进程的角度看,每个进程拥有4G的虚拟空间。每个进程有各自的私有用户空间(0-3G),这个空间对系统的其他进程是不可见的。最高的1G内核空间则为所有进程以及内核所共享。这段共享的内核空间,就构成了传统Linux系统中数据不同进程之间数据交换的基础。更进一步,是依赖两个函数:
数据通过 copy_from_user 来到内核空间,通过copy_to_user 返回给用户空间。
由上可知,数据的传输过程至少需要对数据进行两次拷贝,但是在 Android 中又有不同,为了数据的高效传输,它对数据的拷贝仅需要一次,原因在于:Server 进程在通过 open 打开 Binder 驱动(在此处传入了进程信息,即 binder_proc),使用 mmap 进行内存映射的时候,映射出的物理内存同时存在于内核空间和 Server 所运行的 Binder 进程的用户空间。而内核空间和用户空间都是虚拟逻辑空间。这样,当数据 copy_from_user 之后存在内核空间,这页内存空间所在的实际物理空间实际上也对应于用户空间。这样,就无需在执行 copy_to_user的操作。
序列化与反序列化---什么样的数据完成进程穿透
那么,什么样的数据,能够方便、安全地穿透进程间壁垒,完成进程间通信呢?
序列化数据。在应用层,就是一个Parcel对象。
关于序列化和反序列化,下面的这张图,描述的很清楚。
如上图所示,我们都做过将一张纸裁剪成正方体的游戏。现在将正方体拆开,还原成平面的纸,然后通过传真机发送到远端,在远端接收后,安装纸面上的轨迹,还原这个正方体。
实际上,这给了我们启示,在面向对象的世界里,一个复杂的对象该如何在不同进程中传递的思路,这是说我们需要把一个复杂的对象的简化了,变得平滑,转换成基础数据结构(int ,float,long等),发送到远端后,再从远端复原成复杂对象。
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}
//转换成Parcel对象,完成序列化
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
//从Parcel转换成原对象,完成反序列化
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
来一发服务请求---协议保证
什么是协议?协议就是双方的约定,就是规矩,就是格式。下面,我们来看看一个实际远程调用过程的数据穿透协议。
上图实际执行了系统服务MediaPlayer的setVolume(float,float)函数调用过程,而执行的实体实际上是远端的服务进程。
数据write_buffer实际上是通过copy_from_user传递到内核空间。传递的内容,在上图中一目了然,主要包括类(android.media.IMediaPlayer),方法(通过数字26映射),参数(1.0,1.0)。以上,就是函数调用从用户空间被传递到内核空间,通过Binder驱动发送到实际执行的远程服务进程完成执行操作。
Binder通信机制
上面,我们该对整个通信过程有了一个大概的印象,应该已经清楚,数据从哪里来,如何穿透进程壁垒以及穿透的基础。接下来,我们先来通过下图澄清几个概念。
- Binder驱动:一个内核层级的驱动,促成了进程间通信。有人把它比作网络通信中的路由器,而路由器的作用,就是从原地址存储转发数据目的地址,很贴切。
- Binder对象:是IBinder接口的实现。实际上就是远程服务动作的实际执行者。
- BinderToken:也有叫 handler 的。实际上就是一个32位的整型值,唯一的代表了一个远端Binder对象。
- Binder Service:实际持有Binder对象,处理业务逻辑。
- Binder Client:请求Binder服务者。
- Proxy:实现了AIDL接口,能够序列化/反序列化数据结构,同时能够通过Binder引用进程远程调用。
- Stub:能够序列化和反序列化数据,并把转换过程映射到实际的服务端的方法调用。
- Context Manager:一个注册 handler(句柄)为0的特殊Binder对象。通过name到handler的映射关系,注册和查找别的Binder对象。也有人把它对应为DNS服务器。DNS服务器的作用,就是通过IP地质到域名的映射,提供查找和注册。即我们向DNS服务器注册自己的IP,并把它映射到一个域名;这样,别人就可以通过域名来访问我们的主机,因为DNS服务器将把这个域名转换为IP。
把上图再细致一点,放大,如下图所示:
Client:
- 首先,客户端从ContextManager中通过服务名,拿到远程服务的handler(句柄),也就是binder token。
- 调用远程服务的 foo 方法,然后序列化参数;
- 通过 transact 把调用相关的资料提交给 libbinder处理;
- 由 libbinder 通过ioctl进程系统调用,将foo调用请求提交给binder驱动;
- binder驱动通过handler找到真正的远端服务进程,然后通过ioctl函数将foo调用传递给远端服务进程的 libbinder;
- 远端libbinder将调用交给Stub;
- 在Stub中,反序列化调用信息,还原;
- Stub找到实际服务提供者,执行客户端的请求;
- 将结果序列化
......
再通过Binder驱动,返回给客户端。
Server:
主要是在ContextManager中完成注册(name -> handler);等待接收Binder驱动发来的请求。
Binder限制
在使用Binder进程跨进程调用的时候,有两个重要的限制需要注意:
- 一个服务进程中最多同时支持15个binder线程处理请求;
- 一个进程中用户交互穿透数据的缓存大小最多为1M,这就意味着,在传递的数据是有限的,如果资源耗尽,会抛出异常。
最后
作为一个应用开发者,对本质对底层的C实现机制,有些技痒,但是久未使用过C,想从源码的角度深入分析,需要花费更多精力,目前似乎又没有这个必要,那么,就先浅尝辄止,不求甚解。如有不对的地方,还请指出。
收获,就在于对序列化、反序列化的认识;对数据结构化的认识;从Binder框架的认识;以后再读源码的时候,不会被各种服务调用搞的晕头转向;再有就是对AIDL的使用上,不用再死记硬背如何通过AIDL进程跨进程调用了。
后面,会写一下AIDL跨进程调用过程。
本文参考链接:
https://blog.checkpoint.com/wp-content/uploads/2015/02/Man-In-The-Binder-He-Who-Controls-IPC-Controls-The-Droid-wp.pdf
https://events.linuxfoundation.org/images/stories/slides/abs2013_gargentas.pdf
https://www.dre.vanderbilt.edu/~schmidt/cs282/PDFs/android-binder-ipc.pdf
想要深入源码和数据结构的同学,可以参考以下链接:
http://gityuan.com/2015/11/01/binder-driver/
http://www.cloudchou.com/android/post-507.html
http://www.jianshu.com/p/1050ce12bc1e
http://blog.csdn.net/universus/article/details/6211589#comments
https://github.com/xdtianyu/SourceAnalysis/blob/master/Binder%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md
或者,自己去找binder.c 和 binder.h 这两个文件来看吧。