为了加深对android中binder进程间通讯流程的记忆,这里记录一下自己对binder的理解思路。
首先Android中binder的进程间通讯其实可以类比java中线程间的通讯,只不过binder的进程间通讯多了一些约束而已。具体流程看下面;
与线程间通信类比
假设现在有线程Thread1
和线程Thread2
需要通讯,Thread1
需要把一个数据data
传递给Thread2
那最简单的方法就是利用一个静态变量做一下中转,这里就把这个静态变量定为MiddleWare.temp
,那具体流程就可以用下面这段伪代码来表示;
MiddleWare.temp = thread1.data;
thread2.data = MiddleWare.temp;
那么再进行如下类比
-
MiddleWare
类比Binder驱动(Binder驱动可以简单的理解成内核中的一个应用) -
Thread1
类比进程1 -
Thread2
类比进程2
那上面伪代码中的两个流程就是Binder驱动进程间通信的核心逻辑了,是不是很简单。总结一下Binder的作用就是暂存一下数据并且交给另一方;
mmap的理解
Binder和其它进程间通讯手段有一个不同就是Binder可以实现一次copy来完成数据通信,这里继续通过上面那个类比来说明一下mmap的原理;
普通跨进程通信两次拷贝方案
上面的例子中通过类比了解了Binder(也就是例子中的MiddleWare)的角色,说白了其实就是一个中转站,而Thread1
和Thread2
则是真正通信的对象。但是这里类比中有一个关键的地方线程和进程是不同的;就是进程之间的内存是不共享的;
这里先补充2个背景知识:
- Linux内存分布:Linux内存分为内核区和用户区的,我们一般的进程(如app应用)都是跑在用户区的,而一些系统进程(如Binder驱动)则是跑在内核区的;
- 内核区和用户区区别:这里只要明白内核区是可以访问(通过Copy)用户区的数据,用户区是无法访问内核区的数据就可以了;当然用户区的不同进程自然不可以互相访问数据,不然要搞出一个Binder有啥用;
这里对内核区用户区做一个简单的总结:Binder所在的内存区域权限最高,Binder可以拿到进程1的数据也可以拿到进程2的数据,同理也可以把数据给进程1和进程2,而进程1和进程2拿不到Binder所在内存的数据,进程1和进程2的数据是互相不共享的;
继续上面用线程来类比,那现在的情况就是这样的:
-
MiddleWare
独自在一个线程; -
Thread1
在自己的线程1; -
Thread2
在自己的线程2; -
MiddleWare
需要通过数据Copy来获取或赋予Thread1``Thread2
的数据
那具体流程就变成了下面这段伪代码:
MiddleWare.temp.copyFrom(thread1.data);
MiddleWare.temp.copyTo(thread2.data);
这里就是一般的跨进程通信的核心流程了,而从上面分析可以看出一般跨进程通信需要对数据进行两次Copy
mmap数据的一次拷贝
Android为什么用Binder作为跨进程通信的手段自然是因为Binder的高性能了,Binder通过mmap来将两次Copy优化至一次Copy;
mmap是什么?mmap的全称为MemoryMap也就是内存映射,简单的说可以把内核区也就是Binder所在的内存空间中的内存映射到用户区,这样用户区拿这部分数据就可以不用Copy了。
mmap既然可以直接映射内存把内核区的映射到用户区那假如按照如下方案:
如图按照上图的做法是不是只要两次映射,都不需要Copy就完成数据的传输也就是跨进程的通讯了?这里我一开始也是这样想的,但事实不是这样的;
这里有一点需要注意,就是mmap内存映射后,用户区的数据要到内核区还是需要通过Copy才能完成的,而用户区获取数据则可以直接通过mmap内存映射读取内核区的数据,不再需要再经过Copy(好像还需要一次浅拷贝,替换一下内核区的引用?)
那继续就上面这个类比的例子目前经过mmap优化过以后的伪代码就是这样的:
//把Thread1的数据 从用户区Copy到 Thread2在MiddleWare中
//的映射区域MiddleWare.thread2MapData中去
MiddleWare.thread2MapData.copyFrom(thread1.data);
//Thread2通过mmap内存映射获取对应映射区
//域MiddleWare.thread2MapData的数据
thread2.data = MiddleWare.thread2MapData;
这里再附上一个图帮助理解
(Thread MiddleWare的类比关系应该不用在说了吧)
这里就可以清楚的看到Binder中mmap是怎么通过一次Copy来完成数据的传输的;
至于Binder的C/S架构谁是Client谁是Server,在当前的例子中自然是Thread1(进程1)是Client,Thread2(进程2)是服务端了。当然这个角色都是会发生变化的,比如Thread1发送数据后Thread2处理完数据要把结果返回给Thread1,那此时Thread2就是Client端,Thread1就是Server端了。这里不变的依据就是谁接受数据并且处理数据谁就是Server端,谁发送数据谁就是Client端;
上面就是我自己对Binder通信流程的一部分理解和总结了,通过线程间通讯来类比也是为了方便记忆。当然这里还没有完,这里还有几个疑问:
- 进程1是如何找到进程2的,或者说找到进程2在内核区映射的内存地址?
- 相对于Native层Framework层Binder的架构又是怎样的?
- Binder看起来很虚,那我们平时要怎么用Binder这个东西?(AIDL?它的实质是什么)
上面这些疑问我后面会陆续整理的,至于BpBinder、BBinder、BinderProxy、IBinder这些乱七八糟的也都是为了完成这个架构而提出的,有时间会再整理说明。