第二章:Binder整体框架的介绍
前言
我一直在想是从上往下讲Binder架构,还是从下往上讲,最后还是决定从下往上讲,那我们先来聊聊Binder驱动,这里不和你讲太多的源码,比如用户空间拷贝数据到内核空间具体实现,Binder线程池的具体实现。我们从宏观角度来分析一下Binder驱动要怎么设计。
如何找到两个进程可以数据共享的区域?
在第一章中,我们设计的Binder是通过SD卡来实现数据共享区域,但是有一个几个问题,第一SD卡的读写速度太慢,第二SD卡的文件安全性不够。所以Binder驱动采用了内核空间共享内存的方式。什么是内核空间,一个4G的手机,在运行的时候3G是给用户空间,1G是给内核空间,所有进程对内核空间是共享的,所以Binder用内核空间来共享数据。
Binder驱动要做的几件事?
1.提供一个设备节点(dev/binder),让用户空间进程可以操作内核空间。
2.提供接口给用户空间进程,开辟一块内核空间作为共享内存区域,将内核空间的物理内存地址和用户空间的虚拟内存地址映射,这样子用户空间的进程就可以对这个内核空间进行读写了。
2.提供接口给用户空间进程往这个共享区域读写数据。
用一个图简单解释一下
屏幕快照 2018-11-14 下午1.56.14.png
Binder驱动提供的接口有哪些?
1.最底层的接口:open,mmap,ioctl,大家有个印象就好,一般人不会直接使用
open:打开设备节点
mmap:分配共享区域
ioctl:对共享内存读写数据,并且是一些binder调用的事务管理。
2.中层的接口:这也是用的比较多的,尤其是native的服务,我们来分析开机动画,因为它最简单:
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
waitForSurfaceFlinger();
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
ALOGV("Boot animation set up. Joining pool.");
IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}
注意上面的几个代码
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
两个重要的对象ProcessState和IPCThreadState
ProcessState是一个singleton类,每个进程只有一个对象,这个对象负责打开Binder驱动,开辟一个共享空间,建立线程池,让其进程里面的所有线程都能通过Binder通信
每个线程都有一个IPCThreadState实例登记在Linux线程的上下文附属数据中,主要负责Binder的读取,写入和请求处理框架。
3.最上层的接口BpBinder(客户端)和BBinder(服务端),以及BinderProxy.java(客户端)和Binder.java(服务端):
对象ProcessState和IPCThreadState,可能会有人比较难理解,说白了就是对Binder驱动最底层的封装接口,方便Native的进程来使用。所以如果你是native的进程完全可以只通过ProcessState和IPCThreadState来进行Binder通信,但是程序员就是喜欢偷懒,Binder又提供了BpBinder和BBinder来封装ProcessState和IPCThreadState,但是BpBinder和BBinder不是java的接口,所以上层有提供了BinderProxy.java和Binder.java通过JNI封装BpBinder和BBinder给Java层使用。
用以下的图来标识(可惜图中没有显示ProcessState和IPCThreadState,其实他们都在Native IPC层的BpBinder和BBinder都在这一层)
屏幕快照 2018-11-14 下午2.46.03.png小结
对于普通的工程师来说,基本不会去改Binder驱动的代码,源码中的各色各样的服务,都是去继承BpBinder和BBinder,或者BinderProxy和Binder来实现跨进程通信,最后当然会调用到Binder驱动中。我们来简单描述一下一次跨进程的通信,我们以最简单的BinderProxy和Binder为例。
1.在应用A中new 一个BinderA对象继承于Binder,调用ServiceManager的addService方法,将BinderA对象的存储到SM中,把BinderA对象的唯一标识(int值)存下来,其实这一步已经有一个Binder IPC通信了,就是上图中的1.注册服务
这个过程中应用A是client端,SM是server端
2.在应用B中调用ServiceManager的getService的方法,拿到了BinderA对象的唯一标识(int值)。这一步也是跨进程通信,就是上图中的2.获取服务。并根据这个BinderA对象的唯一标识,创建的BinderProxyA对象
3.应用B中用BinderProxyA对象去通过Binder驱动调用BinderA对象的接口,就是上图中的3.使用服务。
思考
1.目前是通过IBinder定义的接口来IPC通信,说白了需要以下几个参数,那我们如何做到RPC呢,说白了就是看起来直接调用方法一样容易,这个我就会在第三章《AIDL到底是什么》给各位解释。
handle-binder的唯一标识符
code-方法名
date -方法的参数
reply-方法的返回值