Binder-原理

binder 代码

core/java/android/os/下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Binder.java  remote object
IBinder.java remote object interface
ServiceManager.java final class. core class


ServiceManagerNative.java contain ServiceManagerProxy
@UnsupportedAppUsage
public static IServiceManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
// ServiceManager is never local
return new ServiceManagerProxy(obj);
}

Binder 原理

Binder 原理说白了很简单,就是通过 mmap 来进行内存映射。
进程通信一般几种途径。通过 socket,通过磁盘文件,通过共享内存,管道 pipe。
传统的拷贝是用户空间到内核空间,然后再从内核空间到别的用户空间,管道要4次拷贝,而共享内存要2次拷贝。socket 的拷贝一般来说最少也要4次(简化了网卡)。很明显,最好的是共享内存方式进行进程通信。
而在 android,因为进程通信的频繁。因为 android 的特性,activity 启动,service 启动等等都和跨进程通信息息相关,不过它们通信的是系统进程,且 android 内部多进程配置很简单,应用内使用的也不少。所以 android 思考在这块是否可以在进一步优化。
Binder 的诞生就基于此。Binder 通过 mmap 来映射。不过它是在内存空间的映射,它主要是发送进程内核空间缓冲区,然后另外接收进程内核缓存区映射发送进程。接收进程用户空间在映射自己内核空间的。所以唯一的一次拷贝,是发送进程从自己用户空间拷贝到内核空间。

Binder 设计

跨进程面临问题:

  1. 如何发现对方
  2. 通过什么方式来约定通信协议或者说内容
    其实这是不是和 http 请求一样。你知道网址,但是如何找到对方呢,以及中间的协议内容如何规定。
    在 http 中,是通过域名服务器来帮助你找到地址,就算是 ip,你也要在通过之前通过注册来让别人能在互联网大海里面找到你。总结来说需要一个权威的地方来帮助定位。
    而问题2则是 http 协议规定了通信协议。然后做为接收方,要对外提供端口、api,不提供的话,明显外界只能靠猜测。

我的设计

我之前也设计了一套跨进程的方式,目的是为了解决多进程环境下配置一致性的问题。
一开始我想的是用广播,由 app 来通过配置决定哪个进程来处理配置升级等等问题,然后其他进程启动时候,动态注册接收配置更新的广播,发送广播表示它起来了,然后配置进程接收到后,就存储列表表示当前在线的进程。当配置更新的时候,由配置进程发送广播,各个进程收到后,发送已接收的广播。配置进程接收到全部后则发布广播各个进程各自从文件读取,更新配置。如果配置进程没收到全部的在线进程的广播,则它等待广播超时后就会在进程列表里删除未达到的进程,在发布重新上线的广播,以便来更新进程列表,在重复上述过程。(ps:配置进程是静态注册的广播,以便别的进程上线能拉起配置进程)
但这套机制因为广播太重了(因为广播实际上就是在 AMS 上注册,然后 AMS 接收后按注册列表分发),会加大 anr 概率等原因,这套方案被 ps 了。
既然广播太重了,那我觉得换一种方式,不在使用广播,而使用 binder 来取代广播。它的优势是轻量级的,省去了系统 AMS 工作压力。而且 binder 有一个特性,就是可以注册监听,一旦它死亡,就会回调,来方便配置进程及时删除死亡进程(它的原理没细看,大概是进程注册到监听进程,然后监听进程在死亡时候会在 release时发送通知)。
这么一来就比较完美了,每个进程把自己的 binder 交给配置进程。配置进程在配置更新的时候来遍历列表刷新配置。

android 内部设计

binder 设计基础分为 client、server、driver。通信协议是 server 规定的,client 首先得拿到 binder proxy 对象,然后按协议规定传输数据,数据经由 binder driver 直接复制到 server 的内核空间,因为 server 内核空间已经和自己的用户空间形成映射,所以整个过程只需要拷贝一次即可。即从 client 用户空间拷贝到内核空间。而协议的实现方式,采用了可以由 java 层来定义接口,driver 负责本地 binder 和远程 binder 的传递转化,从而将细节完全隐藏。
在 java 层看来,AIDL 方式显得很奇妙。定义一个 AIDL,然后生成接口文件,只要创建了 Stub 的 Binder 类型对象,就可以拿着这个 Binder 跨进程传递,到处浪了。其他进程获得这个 Binder 就可以调用该进程的方法,其他进程获取到的实际已经是 BinderProxy 对象,C++ 层是 BpBinder 对象,最后实现通过 IPCThreadState 来发送数据并等待返回。
为了可以全局使用,设计了 ServiceManager。ServiceManager 承担类似 dns 的作用,它是在最开始启动的,为其他进程提供查找对应 binder 的服务。

内部使用示例

在 start Activity 时候它调用一开始的 IActivityManager,它中转到唯一一个 ActivityManagerService。

1
2
3
4
5
6
7
8
9
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

结论

  1. Binder 将实现隐藏在了 C++ 代码里,导致 Java 层显得很清晰,使用简单。
  2. Binder 原理说简单其实很简单,就是利用 mmap 内存映射在跨进程时候只进行一次拷贝;但复杂也很复杂,对象转换,在 Java 层无感知的情况下,在反序列化中将对象替换。Server 进程会通过 Binder 线程池里 ioctl 监听来提供服务,Client 进程中线程会等待返回数据,期间涉及到查找服务,数据传递等等。
  3. Binder 里面一些命令 BC_ 开头命令代表进程传递到 driver,BR_ 开头命令代表 driver 传递到进程。