Binder架构
一、架构
如上图,binder 分为用户层和驱动层两部分,用户层有客户端(Client)、服务端(Server)、服务管理(ServiceManager)。
从用户空间的角度,使用步骤如下(三者关系):
- Server 往 ServiceManager 注册服务;
- Client 从 ServiceManager 获取服务;
- Client 使用获取到服务功能。
但其实,三者并不是直接通信的,而是各自调用 binder 驱动(ioctl),由 binder 驱动来做通信转发。这一部分是封装在 Binder API 里的,应用使用时完全不需要关心。
为什么这么设计?
比如,我们开发有一个进程A,想开发一个功能,让手机的手电筒打开;但是,手电筒只有进程B的 OpenLed 函数可以控制;这在商业项目中非常常见,模块高内聚,低耦合,职责非常单一。要做到进程A直接调用进程B的 OpenLed 函数(前面说的RPC),这个时候,进程A就是Client,进程B是Server;进程A,首先找到Binder的大管家ServiceManager获取进程B的句柄handle(进程B提前已经告诉(注册)大管家自己的句柄id是啥了),接着,进程A将 OpenLed 需要的参数进行打包封装,然后通过ioctl拷贝给内核里面的 Binder驱动,驱动拿到这些数据之后,寻思着这是发给谁的呢?一看数据包裹里面的handle就明白了,于是将数据转给(这儿其实是mmap,先有点印象别计较)进程B,进程B拿到之后,看看进程A是想调用我哪个函数?里面有个code给函数编号了,于是乎,进程B就去调用自己本地的OpenLed ,手电筒就打开了。
当然,进程B打开手电筒之后还可以将函数的执行结果信息(返回值,出参啥的)返回给进程A,这个不影响理解,先不管。
下面看看这三个进程分别干了什么:
Client进程:
open驱动;
获取服务(向ServiceManager查询服务,获得一个handle);
向handle发数据;
ServiceManager进程:(下文简写为sm)
open驱动;
告诉驱动,我是service manager,我是大管家,以后所有的服务要想使用binder,
必须告诉我他们叫啥,handle是多少。
然后就是循环:
从驱动读取数据;
解析数据;
处理数据;其实我们使用sm的功能相对单一,就是两种:注册服务
(将服务名添加到自己的链表中)和获取服务(从链表查找服务,返回服务handle);
Server进程:
open驱动;
注册服务;将自己的服务名发给大管家sm;
循环:
从驱动读取数据;
解析数据,并调用对应的函数;
返回数据;
二、Binder的作用
到此,我们理解了 CS 架构的优点,以及它的大致原理,但是可能会产生一个疑问:
CS架构看起来并不是和 Binder 强绑定的,使用任意的进程通信方法都可以支撑这个CS架构,那为啥非 Binder 不可呢?
原因就是两个字:效率。
这里包含了运行效率和使用效率。
运行效率高,指的是 Binder 仅需要进行一次数据拷贝。什么意思呢?通常来说,进程间的通信都需要拷贝两次数据:
“进程A --(拷贝到)-- 内核”,“内核 --(拷贝到)-- 进程B”。
而 Binder 的 Server 端,用户空间和内核空间的是做了内存映射的,所以内核和 Server 应用之间的拷贝就可以省了。别小看这一次拷贝,这在 Binder 作为主力 IPC 方法而被大量频繁调用的安卓系统中,节省下来的开销还是很客观的。
相信有人可能又有疑惑了,那共享内存不是更节省开销吗,共享内存一次拷贝都不需要了。这里就不得不提到第二点“使用效率”了。
贡献内存最大的缺点之一就是难以同步,这得在两端约定一套协议规则才行,并且这在服务端对应多个客户端的时候,变得更加麻烦。
相信没人希望没实现一个功能,就要对大量的基础通信的开发调试吧?
所以,Binder 帮大家做好了,Binder 就像是是“半个共享内存 + 进程同步管理”。
三、通信原理
1、Binder底层使用到mmap
Binder是基于内存映射mmap设计实现的,通过这种方式,直接操作映射的这一部分内存,通过mmap,Binder通信时,只需要经历一次数据复制,从而获得更好的性能。
2、一次Binder IPC通信的过程分为以下几个步骤:
首先,Binder驱动在内核空间中开辟出一个数据接收缓冲区
接着,在内核空间中开辟出一个内核缓冲区
将内核缓冲区与数据接收缓冲区建立映射关系
将数据接收缓冲区与接收进程的用户空间地址建立映射关系
发送方进程通过copy_from_user将数据从用户空间复制到内核缓冲区
由于内核缓冲区与数据接收缓冲区有映射关系,同时数据接收缓冲区与接收进程的用户空间地址有映射关系,所以在接收进程中可以直接获取到这段数据
3、这样便完成了一次Binder IPC通信,它的原理如下图所示: