Android Binder 原理解析
一.跨进程通信
跨进程通信(IPC)是指,不同进程间不能互相直接访问,那么操作系统是如何做到的呢?
1.进程隔离
进程隔离是操作系统为了保护进程之间不互相干扰而设计的,避免进程A写入进程B的情况发生,其实现使用了虚拟地址空间,两个进程虚拟地址不同,就可以防止A进程写入数据岛B进程。
也就是说,操作系统的不同进程之间,数据不共享,在每个进程看来,自己都独享了整个系统空间,完全不知道其他进程的存在,因此一个进程想要与另一个进程进行通信,需要某种系统机制才能完成。
2.用户空间/内核空间
Linux Kernel是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
对于Kernel这么一个高安全级别的东西,显然是不容许其它的应用程序随便调用或访问的,所以需要对Kernel提供一定的保护机制,这个保护机制用来告诉那些应用程序,你只可以访问某些许可的资源,不许可的资源是不能被访问的,于是操作系统就把Kernel和上层的应用程序抽像的隔离开,分别称之为Kernel Space和User Space,即内核空间和用户空间。
3.系统调用/内核态/用户态
虽然从逻辑上抽离出用户空间和内核空间,但是不可避免的是,总有那么一些用户空间需要访问内核的资源:比如应用程序访问文件、网络是很常见的事情,怎么办呢?
用户空间访问内核空间的唯一方式就是系统调用,通过这个统一入口,所有的资源访问都是在内核的控制下执行,以免导致用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
当一个进程执行系统调用,陷入内核代码中执行时,我们就称进程处于内核态,此时处理器处于特权级最高的内核代码中执行。当进程在执行用户自己的代码时,则称其处于用户态,此时处理器在特权级最低的用户代码中运行。处理器在特权等级高的时候才能执行那些特权CPU指令。
4.内核模块/驱动
通过系统调用,用户空间可以访问内核空间,那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢?
很自然想到的是让操作系统内核添加支持,传统的Linux通信机制,比如Socket、管道等都是内核支持的,但是Binder并不是Linux内核的一部分,那它是怎么做到访问内核空间的呢?
Linux的动态可加载内核模块机制解决了这个问题:模块是具有独立功能的程序,它可以被单独编译,但不能独立运行,它在运行时被链接到内核作为内核的一部分运行在内核空间。这样,Android系统可以通过添加一个运行在内核空间的内核模块,用户进程之间通过这个模块作为桥梁,就可以完成通信了。
在Android系统中,这个运行在内核空间,负责各个用户进程之间通信的内核模块叫做Binder驱动(驱动就是操作硬件的接口)。
二.为何使用Binder
Android使用的Linux系统内核,拥有着非常多的跨进程通信机制,比如管道、Socket等,为什么还需要单独搞一个Binder出来呢?主要有两点,性能和安全。
-
Binder相对于传统的Socket方式,更加高效
-
比如Socket通信ip地址是客户端手动填入的,都可以进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性
三.Binder通信原理
下面我们来看看Binder是如何实现跨进程通信的。
Android系统中有许多的服务,比如Clipboard剪切板服务、ActivityManagerService服务、Vibrator针对服务等等,这些服务都是独立的进程,通常我们的客户端程序想要使用这些系统服务,会通过ServiceManager获取对应的接口对象进行访问,这时就需要跨进程通信,也就是需要借助Binder了,那Binder的通信流程是怎样的呢?
我们把每一个服务端称为Server端,访问他的客户端称为Client端,存储Server端信息的ServiceManager称为SM,假设Client端想要访问一个叫做Test的Server端,调用其A对象的add方法,其过程如下:
-
首先,当Server端进程初始化时,要向SM中注册自己的信息,类似于一个map的结构:我叫Test,有一个A对象,其中有一个add方法
-
Client端想要访问时,先向SM端查询,是否有一个叫Test的Server端,我要请求里面的A对象,此时是有的,并要将"A对象"返回
-
上面说过,进程间通信时,数据会流经内核控件中的Binder驱动,此时驱动会对要传递的数据做一些手脚:如果是同一个进程要获取对象,则返回对象本身接口(不需要做跨进程通信);如果是不同进程要获取对象,则将返回一个和A对象看似一样的AProxy代理对象,这个代理对象也有add方法,其方法只是包装参数,回调给Binder驱动;此处Android的设计是使用代理模式。
-
Client端拿到这个和A"一样"的AProxy对象,无需关注其是否为代理对象,照常调用add方法传递参数
-
如上所述,代理对象AProxy内部的add方法会直接交给Binder驱动处理
-
Binder驱动拿到数据,并知道AProxy对应的真实Server端其实是Test的A对象(数据流经时Binder驱动会记录每个Server端及其对象的信息),于是乎,调用Test端的A对象的add方法,并将参数传递即可
-
Test端将add方法的返回结果返回给Binder驱动,Binder驱动再返回给对应的Client端的调用处,即可完成一次跨进程通信
综上我们可以发现:
-
Binder机制使用代理模式,在Server端的对象是实际对象,其他各个进程端所持有的都是Proxy代理对象,由代理对象和Binder驱动完成数据传递
-
Proxy和实际对象具有同样的接口来实现数据通信,而不用关注其实现细节,因此让我们觉得Binder对象可以直接进行跨进程调用,其实是个数据中转的过程
初次之外,SM也是一个单独的进程,其与各个Server端的注册通信,也是通过同样的Binder跨进程机制完成的,Binder驱动也会对这个过程动手脚,也就是说,SM存放的各个Server端的对象,也都是Proxy代理对象。
一句话总结Binder机制就是:Client进程持有了Server端对象的代理;代理对象系统Binder驱动完成了跨进程通信。
四.Binder实现
知道了Binder通信的原理和流程,下面我们来看看java层是如何支持Binder的吧。
1.IBinder/IInterface
Java层提供了IBinder、IInterface两个接口
-
IBinder:提供了Binder跨进程通信的基础方法,如transact()方法用来传递数据;如queryLocalInterface()方法用来获取实际功能对象
-
IInterface:提供了Binder通信对象的基本类型,我们Server端所提供的功能对象都要实现该接口,其asBinder()方法可以获取对应的Binder对象,也是可以用来传输Binder对象的类型标识
2.Binder/BinderProxy
(1)Binder
我们要实现一个Server端的提供服务的对象,首先要实现IBinder接口,对Server端来说,其提供服务的对象都是实际的Binder对象(也叫本地对象)而不是代理对象,而Binder类就是实现了部分Server端本地对象的处理逻辑的类,也就是说Binder类就是Server端本地对象的基类,我们来看看它实现了哪些处理逻辑:
public class Binder implements IBinder {
private IInterface mOwner;
private String mDescriptor;
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
@Override
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
@Override
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
protected boolean onTransact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (code == INTERFACE_TRANSACTION) {
reply.writeString(getInterfaceDescriptor());
return true;
} else if (code == DUMP_TRANSACTION) {
...
return true;
} else if (code == SHELL_COMMAND_TRANSACTION) {
...
return true;
}
return false;
}
}
-
queryLocalInterface()方法里面判断,如果descriptor,也就是IInterface(服务对象)的类名相同的话,就会返回owner,而这个owner和descriptor是服务对象初始化时通过attachInterface()方法传递进来的,也就是说,如果调用服务对象本身的该方法,拿到的就是实际的服务对象本身(本地对象),因为是同一个进程不需要进行跨进程通信
-
transact()方法,将要传递的数据,包括参数以及要调用的方法(一个int协议值)通过序列化Parcel的方式,传递并调用onTransact()方法
-
onTransact()方法就是Server端的Binder本地对象要处理的入口方法,其会根据方法的协议int值,决定调用本地对象的哪个方法
(2)BinderProxy
Server端的Binder对象有了,下面来看看Client端的Binder对象,上面说过了,Client端拿到的Binder对象是一个代理对象BinderProxy,由Binder驱动生成并返回,我们就来看看这个类是如何进行跨进程通信的。
final class BinderProxy implements IBinder {
@Override
public IInterface queryLocalInterface(String descriptor) {
return null;
}
@Override
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
...
try {
return transactNative(code, data, reply, flags);
} finally {
if (tracingEnabled) {
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
}
}
}
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
}
我们可以看到,BinderProxy也实现了IBinder接口,说明其可以和Binder对象一样进行跨进程通信。
-
queryLocalInterface()方法,这里由于是本地Binder对象的代理类,所以并不是本地对象,所以这里返回null,需要通过转换为IInterface对