专栏首页汪毅雄的专栏Binder 总体架构及相关代码浅析
原创

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 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 机器学习之回归(二):广义线性模型(GLM)

    本文在上篇线性回归的基础上,延伸到广义线性模型,并把广义线性模型目的、假设条件来源,指数族分布、连接函数等各个函数的关系都进行详细地解释。最后用两个常见的 GL...

    汪毅雄
  • 机器学习之回归原理详述(一)

    本文用了从数学层面和代码层面,再结合一些通俗易懂的例子,详细地描述了回归主要涉及的原理和知识,希望对于机器学习的初学者或者有兴趣研究模型具体实现的同学带来一点帮...

    汪毅雄
  • 深度学习中的Batch Normalization

    BN也叫归一化,它和比如说CNN中的卷积层一样,也是网络中的一层,但它是一个数据的处理层。数据经过BN后,再交给其他层处理,可以极大地提升训练效率。

    汪毅雄
  • 基于Excel2013的合并计算

    工作组、求和、利用函数randbetween、合并计算 比如在excel表格中填入=randbetween(1,10),输入时如果需要补全要用鼠标点击,不能按...

    潇洒坤
  • 《Kotlin极简教程》第3章 Kotlin语言基础 配图

    一个会写诗的程序员
  • iOS-运行程序屏幕上下有黑边

    用户1890628
  • Web开发---单页面应用(签到日报)

    疫情前期,员工分布在各个地区,需要上报个人的健康状态和位置信息,于是做了一个单页面应用(当时钉钉和微信上的健康上报模板还没出现)

    MiaoGIS
  • SAP Spartacus中使用到的Angular ModuleWithProviders类型

    FunctionConstructor的定义,和JavaScript里的Function构造器类似:

    Jerry Wang
  • 如何将你写的脚本程序打包成一个exe可执行程序

    编写的程序打包成一个exe文件,随时可以双击执行,想想是不是很酷。接下来我们一起看一下如何将自己编写的程序打包为一个exe的可执行程序。

    菜鸟小白的学习分享
  • 头条 _signature、 __ac_nonce、 __ac_signature参数

    直接全局搜索这个参数,会在一个 index-*.js 中搜索到, 虽然 captcha.js 中也有,不过没用

    andrew_a

扫码关注云+社区

领取腾讯云代金券