从AIDL了解Binder
前面简单学习了一下AIDL的用法,接下来就从AIDL入手,探究一下Binder机制。
在学习的过程中,看了以下几篇文章,觉得很有价值:
彻底理解Android Binder通信架构
Binder学习指南
Android Bander设计与实现 - 设计篇
背景知识
首先要知道的是,在Linux系统中,存在很多进程,不同进程之间,数据是不会共享的,他们各自有自己的空间。因此两个进程之间要想交换数据,需要一种机制来做一条数据通路。
还有一点,在Linux系统中,存在内核空间和用户空间两个概念。Linux内核是需要高度安全机制保护起来的,而我们的应用程序则只能运行在开放的用户空间中。如果应用程序需要访问内核空间,需要通过系统调用来实现。
因此,要想实现进程间的通信,我们可以通过在内核空间做一个“枢纽”,不同的进程虽然互相没法访问各自的内存,但是他们都可以用过系统调用来访问我们创建的“枢纽”。Binder机制就是这样的一个“枢纽系统”。
既然Android系统基于Linux系统,那么为什么要自创一套Bidner机制,而不用Linux现成的呢?在知乎上有篇回答很好:为什么Android系统要采用Binder机制做IPC?
Binder通信模型
首先在内核中,存在一个作为枢纽的东西,叫做Binder Driver(Binder驱动)。在Binder机制中,每一个进程最终都是需要在这里完成数据的交接。
其次,还有一个作为核心服务的东西叫做ServiceManager,与Binder Driver不同的是,他处于用户空间。
这两者,构成了Binder机制的核心。
举个例子,现在又两个进程A和B,A要访问B中的一个对象obj的方法f(),这就是跨进程通信了。那么在这之前,进程B会将自己注册在ServiceManager中,也就是说在这里存在一个表,进程B首先会把自己的信息作为一条数据插入在表中。之后,进程A要访问进程B,只需要访问这个ServiceManager,在这里查找B的先关信息,然后他就可以得到这个对象obj,之后就可以直接调用方法f()。
在这个流程里面,实际上并没有真的获取到对象obj,只是获取了一个obj的代理对象。实际对象还是在进程B中。调用f()时,传入的参数只会交给这个代理对象,然后代理对象再负责把数据交给真实对象。而在这整个流程之中,A和B还有ServiceManager都是进程,他们之间的数据交换都是要直接交给Binder驱动的。说起来有点乱,画个图就看出来了:
图中,虚线表示两者之间不是直接交互,因为这三者之间的交互实际上都是通过实线做的。
从AIDL到Binder
AIDL说白了其实就是帮我实现了一个可以用作Binder通信的类,抛开AIDL,我们自己也可以写一个差不多的,也可以用。通过AIDl自动生成的类,定义了一个内部类,并让内部类继承了Binder类。所以,我们只需继承Binder,也可以做简单的IPC了。
从Client开始
从MyAidlClient开始,探寻一下Binder通信的流程。
首先,如图所示
通过已经获得的IBinder对象,这里调用asInterface()方法,返回了一个IMyAidlInterface类的对象,调用他的add()方法。那就从这里开始:
IMyAidlInterface.java:
1 | /** |
再来
1 | //首先要知道,这个类实现了那个接口 |
调用asInterface()方法到这里就结束了,可以看到实际上返回来一个代理对象Proxy,那么之后调用add()方法也就是调用了代理对象的add()方法,也就是Proxy类的add()方法:
1 |
|
这个IBinder类是个接口,而在这获取的对象实际上是Binder.java中的内部类BinderProxy类的对象(什么?Proxy?对没错,这里又是一个代理)。那么获取到这个BinderProxy对象之后,如上所示调用了他的transcat()方法,将参数传入,之后数据传到Server那里,经过实际对象的add()之后会取得返回值。所以这里就是Binder的起点了:
1 | //参数code为前面的Stub.TRANSACTION_add,作用是做一个标识,后面会用到 |
看这个方法的定义:
1 |
|
从这里开始,就进入了native层:
在android_util_Binder.cpp中:
1 | static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, |
然后是BpBinder.cpp中的transact()方法:
1 | status_t BpBinder::transact( |
到了IPCThreadState.cpp的transact()方法:
1 | tatus_t IPCThreadState::transact(int32_t handle, |
这里的write方法显然是写入数据
1 | //写入数据 |
可以看出,mOut用来将数据写入
那么在将数据写入之后,来到了这个waitForResponse()方法,等待应答
1 | status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) |
还有talkWithDriver()方法:
1 | //交给Driver处理 此时mOut已经有了数据,mIn还没有,这里来处理数据 |
所以talkWithDriver()方法是直接去和Binder驱动通信了,其核心是ioctl()方法。
在通信结束之后,回到最初的起点,等待得到返回值(如果有的话),最后从mIn拿到返回的数据。
Server端做了什么
现在已经知道了,数据通过Binder代理,现在已经到了Server端,主要处理过程在内部类Stub中的onTransact()方法内:
1 |
|
曾记否,在Server端重写了add()方法,对没错,这里就是调用了那个add()方法
总结
总体上来说,Binder的整个流程就是:
- Client
- 创建binder_transaction_data
- 填code
- 参数填入data.buffer
- 填入target.handle(Client端的引用)
- BC_TRANSACTION发送给Binder驱动
- 查找目标,填写target.ptr(Server端的实体)
- 到接受线程
- 调用onTransact()方法
- Server
我们说,Bidner相较于其他IPC机制的一个优势就在于只存在一次拷贝。那么这是怎么一回事呢?
通过mmap()映射了一片缓存池,数据拷贝时,binder_transaction_data是可以分为很多部分,但是其中只有一个叫做buffer的部分是大小不可预料的,其他的部分其实大小是限定的。因此除了buffer之外的其他部分由接收方自己提供,而buffer的存储区由缓存池提供,这样就完成了数据的“一次拷贝”,给人的感觉就是完整的数据直接从Client端拷贝到了Server端,实际上还是借助了内核。