Binder 总体架构及相关代码浅析

1、总体架构

我们知道,同一个程序间的数据交互、函数的调用能直接进行,是因为交互两端是共用一片内存空间,其映射规则是完全一样的。但是在处理进程间的通信时,却出现问题。基于此问题Linux系统其实已经有很丰富的解决方法,如Message、Socket、Pipe、Share Memory等等。而在Android上,谷歌新开了一套自己的IPC机制---这就是大名鼎鼎的Binder。可以说,Binder无论作为系统开发或者应用开发,它都是Android的核心机制。Binder很重要的的优点之一就是,复杂数据类型传递可以复用内存,这点后面详细讲述。

我们知道,在Web端访问的一个流程通常如下:

没错,Binder的主题机制和此非常相似,它也是一套基于CS的架构。如web的访问过程,Binder也有四个重要的角色:Binder Server、Binder Client、Service Manager、Binder驱动。其和web端的对应关系如下

其原型图如下:进程1和进程2希望进行通信,所以必须借助Binder驱动来交互,而参与的进程需要一个类似ip地址的唯一标志,其中binder中的“ip地址”是动态的,所以为了防止频繁的获取“ip地址”,带保存、映射机制的Service Manager顺利成章地出现了,而Service Manager它在binder通信中的“ip地址”则永远是0。

2、Binder代码浅析

如框架所说,当我们要使用系统服务的时候,我们自己的进程不是直接连接系统服务,而是通过ServiceManager来充当中介角色,ServiceManager会根据“客户端”请求的“描述id”来返回“服务端”在Binder驱动中的句柄。

以一个简单的aidl为例,我们新建一个aidl文件ITestAidl,如下:

新建完成后,as会自动帮我们生成一个ITestAidl.java文件,其类结构如下:

其中有两个类:Stub抽象类和Proxy内部类。

2.1 注册

在Binder通信中我们可以把Stub当成“服务器”,Proxy当成“客户端”。由于Binder在发送和接收端的协议是保持移植,所以客户端和服务端的aidl代码必须保持一致。接下来,我们分析一下这个文件。

如上述所说的框架,一个进程能够被其他进程使用,其必须有一个唯一的id。如下DESCRIPTION就是作为Service唯一的id。

有了这个id,就可以进行注册了,我们看到在Stub中并没有注册的方法,在父类构造函数中,我们发现一个init的native方法,此方法就是向binder驱动注册的方法。

接着我们看看native方法的代码。可以看到在Stub初始化的时候程序会生成一个JavaBBinderHolder,并通过SetLongField把这个对象和Stub绑定

在使用的时候通过GetLongField找得这个JavaBBinderHolder对象

然后通过get方法获取真正持有Stub对象的JavaBBinder,先获取,如果为空则new一个返回

2.2 映射至Service manager

如上我们已经知道了Service创建的时候会在Binder驱动中申请一块内存,那Service是怎么映射到ServiceManager中的呢?首先我们看看ServiceManager在native层的一些行为,先看看main函数。这部分如下图,我们可以看到初始化的时候主要做了三个步骤:

1、为binder分配128k的内存

2、通知binder驱动,使自身成为binder驱动的“DNS”

3、维护一个监听Service的死循环,并且维护持有所有Service句柄的svclist

当添加服务的时候,handler会发送一个SVC_MSG_ADD_SERVICE的消息,handler接收消息时:

会执行do_add_service方法,其中有几步:

如果没有权限,不予add

如果已经注册过了,不予add

如果内存不够,不予add

否则才把service加到svclist中

而获取service的时候会发送一个SVC_MGR_CHECK_SERVICE消息,再执行do_find_service方法

2.3 Client和Server通信

Parcel是Binder通信基本单元。当一个进程想调用另一个进程的方法时候,调用者就必须获得被调用者的binder引用,其中传递采用的载体就是Parcel。

另一点在IPC中,Client做为Binder的,其在底层的原型是一个叫BpBinder的东西,而Server对应的是BBinder。两者都会实现transact方法,唯一的区别一个是收和一个是发。

回到刚才测试文件,首先aidl会为我们每个方法名绑定一个id名,以便服务端知道调用的是哪个方法。

当Client想调用Server端的testBinder(IBinder)方法时,Framework层会生成的请求和响应两个Parcel对象,继而把数据service的id---DESCRIPTION写入,再把通过writeStrongBinder方式写入binder对象

相应的,Server端接收到数据后返回一个NoException,完成binder传递操作。

但是,最开始说了Binder的优点是内存的重复利用,也就是说,即使是跨进程,Client和Server传递的数据指向的也是同一片内存。这块我们继续深入,先看看Client。

可以看到,Parcel先写入Service的”id“,再通过writeStrongBinder方法把binder写入,两个都是native方法,写入”id“比较简单,这里只讨论binder的写入。

而才c代码中,其主要跳转如下:

2.3.1 写数据

先通过注册部分讲述的iBinderForJavaObject找到binder对象,再调用c中的Parcel.writeStrongBinder方法,然后跳到flatten_binder

可以在finish_flatten_binder方法中看到,最终写入的是一个flat_binder_object对象,而这个对象只保存了binder的handle,再加上写入的”id“,其最终传递的Parcel携带的主要对象只有两个:id和binder的handle。而其type被设为BINDER_TYPE_BINDER或BINDER_TYPE_HANDLE。

2.3.2 读数据

Client发出来后,Server也会相应的处理,其调用方法就是readStrongBinder。

在读取的时候如果是BINDER_TYPE_BINDER,直接返沪binder的引用,如果是BINDER_TYPE_HANDLE,其则创建一个BpBinder返回给Client。

2.3.3 数据传递

说完了Client和Server的发送和接收,那数据中途是靠什么传输呢?这就得从ProcessState和IPCThreadState说起。

先说ProcessState,其主要有两个功能:

1、创建一个于binder驱动通信的PoolThread,最终展示形式是一个IPCThreadState

2、为指定handle创建IBinder,并维护一个IBinder Vector,先去寻找,无则创建

再说说IPCThreadState,刚才我们提到PoolThread在运行时会不断执行IPCThreadState的joinThreadPool方法。而在IPCThreadState里有两个成员变量mIn和mOut,这两个分别负责不停地查询是否有数据从Binder驱动出来,是否需要往Binder驱动写数据。其中talkWithDriver负责与驱动的通信,executeCommand解析mIn的数据。

再回到例子中的transact方法,其主要执行步骤:

接着执行transact的native方法,然后再执行writeTransactionData,完成整个写入过程。

在接收响应的时候调用WaitForResponse方法,在该方法中,执行一个死循环,不断去读取Driver的响应数据,直到有正确的cmd响应或者error出现,再返回回去,这一点和网络服务端的阻塞很像。

这样整个binder的主要内容就说完了。总的来说binder的通信架构如下:

谢谢阅读!

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

1 条评论
登录 后参与评论

相关文章

来自专栏Spark学习技巧

spark源码系列之内部通讯的三种机制

本文是以spark1.6.0的源码为例讲解。 Spark为协调各个组件完成任务及内部任务处理采用了多种方式进行了各个组件之间的通讯。总共三个部分牵涉的功能是: ...

2398
来自专栏swag code

自定义异常

683
来自专栏Java Edge

探究CAS原理(基于JAVA8源码分析)define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "define LOCK_IF_MP(mp) _

3066
来自专栏nnngu

04 Spring的@Autowired注解、@Resource注解、@Service注解

什么是注解 传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事务,这么做有两个缺点: 1、如果所有的内容都配置在.xml文件中,那...

2904
来自专栏潇涧技术专栏

Art of Android Development Reading Notes 2

(1)任何一个操作系统都需要有相应的IPC机制,Linux上可以通过命名通道、共享内存、信号量等来进行进程间通信。 (2)Android系统不仅可以使用Bin...

672
来自专栏iOS技术杂谈

iOS多线程——你要知道的GCD都在这里你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里

你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里 转载请注明出处 https://cloud.tencent.co...

40210
来自专栏Android 研究

Android跨进程通信IPC之4——AndroidIPC基础1

这里强烈建议把前面两篇文章看一遍,因为前面两篇文章对后面大家对android的IPC的理解帮助很大,本片文章主要内容如下

663
来自专栏技术小黑屋

Java细节:字符串的拼接

工作日忙于项目的逻辑实现,周六有点时间,从书柜里拿出厚厚的英文版Thinking In Java,读到了字符串对象的拼接。参考着这本书做个翻译,加上自己思考的东...

722
来自专栏iOS技术杂谈

iOS多线程——你要知道的NSThread都在这里你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里

你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里 转载请注明出处 https://cloud.tencent.co...

3019
来自专栏QQ音乐技术团队的专栏

支持跨进程单例的一种实现方案

零 烫烫烫烫烫烫 单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局...

30110

扫码关注云+社区