作者:apigfly
简单的开始
我们先简单了解下binder架构
涉及的几个概念:
-
客户进程
:发起远程方法调用的进程 -
服务进程
:真正服务所运行的进程 -
binder驱动
:binder通信
的核心,远程调用都是通过它来处理和传递的 -
servicemanager
:一个管理binder服务
的进程
一个简单版本的跨进程调用的流程:
-
客户进程
把调用信息发给驱动
,等待驱动
返回消息 -
驱动
处理一下调用信息,并通知服务进程
-
服务进程
接到驱动
的通知,根据调用信息开始执行 -
服务进程
执行完毕,通知驱动
-
驱动
接收到服务进程
的通知,对数据进行简单处理,通知客户进程
读取数据 -
客户进程
接收到通知,读取驱动
返回的数据
需要简单区分的两个名词:
- 实现一个
Binder服务
:利用Binder
来实现一个自己的IPC
的服务(怎么使用Binder
) -
Binder
的实现:Binder
的实现原理,涉及到学习binder驱动
部分了
Binder的一些疑问
第一个问题,
客户进程
要调用服务进程
的函数,怎么确认要调用哪一个服务
呢?又怎么知道要调用哪个函数呢?
我们先看几个知识点:
- 在
Binder服务
的使用上,Android 定义了一些模板类- 在
服务进程
中,如果你写的服务想要支持Binder远程调用
,就必须按我这样写- 对于
C++
层,要手动实现Bn...
、Bp...
等类(不知道咋写?Android中那么多支持IPC的服务,借鉴呗。好消息是现在有个HIDL
可以简化开发) - 对于
Java
层,因为AIDL
的存在,会有脚本自动生成Bn...
、Bp...
等类,只需要在回调中实现业务逻辑就可以
- 对于
- 而对于
客户进程
,如果想完成一个远程调用,那就在编程阶段必须- 对于
C++
层,要得到想要调用服务的Bp.....
类型信息才可以,在编程阶段就要写进客户进程
- 对于
Java
层,抄一份想要调用的服务进程
中AIDL
在客户进程
,重新编译一下就可以获得对应服务的接口类了
- 对于
- 对于
客户进程
,还有一种完成远程调用的方式,就是我们使用的各种...Manager
- 和
c++
层和AIDL
一样,我们在客户进程也是通过对于...Manager
类的接口来完成的调用
- 和
- 在
- 想象一个
自由的通信状态
应该是:给我个binder驱动
我可以忍受,干嘛要集成你的服务接口类
啊,这么多条条框框的。我想调用哪家的IPC
服务,直接通过驱动
发给他就可以了哇。-
too young,too naive
,本人考虑并不周全,只是举例哈:- 如何取得要调用的服务对象?后台服务千千万,就算不取得,也得要通知这个服务吧
- 如何传递要调用哪个方法呢?
- 如何获得远程服务有多少方法呢?
- 服务进程在编写时除了完成业务逻辑还需要单独和驱动交流工作。
- 这些问题,
Android
通过自己设计出来的的Binder相关的模板类
都解决了- 不知道服务对象在哪里?
- 那就在
客户进程
加上我设计好的Bp...
类,里面有提前定义好的接口asInterface
,直接转换就好了
- 那就在
- 不知道有哪些可以远程调用的方法?
- 都给你
Bp...
类了,还要啥自行车
?
- 都给你
- 用和
binder驱动
通信么?- 通啥信啊,封装好了,直接调用方法就行了
- 不知道服务对象在哪里?
-
- 在这里回答
第一个问题
:- 在编程时,我们根据需求,把需要调用的
IPC
服务的Bp...
类方到我们的客户进程-
Bp...
类有个asInterface
方法,可以通过这个方法转化为我们需要的代理对象
-
- 而关于调用方法的指定,binder 会在
Bp...
和Bn...
模板类中定义出调用的函数号来- 客户进程有了
Bp...
类,也就有了调用的函数号
- 客户进程有了
- 在编程时,我们根据需求,把需要调用的
第二个问题,我们自己写的服务什么时候启动的呢?
从常理来讲,我们的服务
要在进程启动
的时候就要开始监听外界的消息了,不然万一错过消息怎么办。
Binder
也是这样做的:
- 对于
服务进程
来说- 会在
进程启动
时执行joinThreadPool
- 这个函数会进入
while循环
读取binder驱动
的数据
- 这个函数会进入
- 会在
- 对于
客户进程
来说- 在发送远程调用时,会执行
transact
函数时- 这个函数在发送完调用数据给
binder驱动
后,会继续读取,查看返回数据
- 这个函数在发送完调用数据给
- 在发送远程调用时,会执行
servicemanager 怎么没占多少篇幅呢?
首先这不是一个问题,哈哈哈哈!
-
servicemanager
是Android
用来管理注册的Binder服务
的,我们的getService
大概率是找的它。(还没没看源码,不要轻信) -
servicemanager
并没有按照Android定义的模板来,自己实现了一个简易版本的 -
servicemanager
在Android系统启动时就启动了,这部分在init.rc
中配置 - 没有
servicemanager
不影响我们认识学习binder
整体流程和结构 - 有了
servicemanager
,可以加深我们对binder
的理解
导读结束
binder导读
部分对一些知识点的描述比较简单。细节还是看正文
- 书本中源码是
Android 5.0
,但是binder
这部分从9.0
上看也没有太大变化,只是增加了一些log
打印 - 这代码强悍的生命力不正是我们追求的么,哈哈哈
- 本来还想在导读中说一下
一次拷贝
的事情,算了,留在正文吧
binder
的学习比时间晚了两周。。。。。
好在真滴可以学到不少东西,下面是正文,谨慎阅读
Binder 简介
Binder
是Android特有的一种进程间通信方式。Android Binder
的前身是OpenBinder
,最早由Dianne Hackborn
开发并用于PalmOS
上,后来Dianne Hackborn
加入了OpenBinder
的基础上开发了Android Binder
Binder
和传统的IPC机制
相比,融合了远程过程调用(RPC
)的概念,而且这种远程调用不是传统的面向过程
的远程调用,而是一种面向对象
的远程调用。
从Unix发展而来的IPC机制
,只能提供比较原始的进程间通信手段,通信的双方必须处理线程同步、内存管理等复杂问题,不但工作量大,而且容易出错。
除了Socket
、匿名管道以外,传统的IPC
,例如命名管道、信号量、消息队列等已经从Android中去掉了。
和其他IPC
相比,Socket
是一种比较成熟的通信手段,同步控制也很容易实现。Socket
用于网络通信非常合适,但是用于进程间通信,效率就不太高了。
Android在架构上一直希望模糊进程的概念,取而代之以
组件
的概念。应用不需要关系组件
的存放位置、运行在哪个进程、生命周期等问题。随时随地,只要拥有Binder
对象,就能使用组件
功能。Binder
就像一张网,将整个系统的组件,跨越进程和线程,组织在了一起。
Binder
很强大也很复杂。但我们在使用时却是一件很轻松的事情,不需要考虑线程同步、内存分配等问题。这些复杂的事情都在Binder
框架中完成了。
正因为如此,Android系统的服务都是利用Binder
构建的。Android系统服务有几十种之多,这是任何一个其它嵌入式平台所不具备的。
Android在进程间传递数据使用的是共享内存的方式,这样数据只需要复制一次就能从一个进程到达另一个进程(一般的IPC都需要两步,从用户进程复制到内核,再从内核复制到服务进程),大大提高了数据传输效率。
在安全性方面Android也做了考虑,Binder
调用时会传递进程的euid
到服务端,因此,服务端可以通过检查调用进程的权限来决定是否允许其使用所调用的服务。
大家可以阅读下知乎大神的 为什么 Android 要采用 Binder 作为 IPC 机制? 来加深下了解,文章有惊喜
Binder
对象定义
为了理解方便,把Binder
相关的对象进行如下定义:
-
Binder实体对象
:Binder实体对象
就是Binder
服务的提供者。一个提供Binder
服务的类必须继承BBinder
类。因此,有时为了强调对象的类型,也用BBinder对象
来代替Binder 实体对象
。 -
Binder引用对象
:Binder引用对象
是Binder实体对象
在客户进程的代表,每个引用对象的类型都是BpBinder
类。同样可以用名称BpBinder对象
来代替Binder引用对象
。 -
Binder代理对象
:Binder代理对象
也称为Binder接口对象
,它主要为客户端的上层应用提供接口服务,从IInterface
类派生,它实现了Binder
服务的函数接口,当然只是一个转调的空壳。通过Binder代理对象
应用能像使用本地对象一样使用远端的Binder实体对象
提供的服务。 -
IBinder对象
:BBinder
和BpBinder
类都是从IBinder
对象继承而来的。在很多场合,不需要刻意地去区分Binder实体对象
和Binder引用对象
,这是刻意使用IBinder对象
来统一称呼它们。
Binder接口对象
主要和应用程序打交道,将Binder接口对象
和Binder引用对象
分开的好处就是Binder接口对象
可以有很多实例,但是它们包含的是同一个Binder引用对象
这样方便了用户层的使用。
关系图如下:
应用完全可以抛开Binder接口对象
直接使用Binder引用对象
,但是这样开发的程序兼容性不好。也正是因为在客户端将Binder引用对象
和Binder接口对象
分离,Android才能用一套架构来同时为Java层
和Native层
提供Binder
服务。隔离后Binder
底层不需要关心上层的实现细节,只需要和Binder实体对象
和Binder引用对象
进行交互。
Binder
的架构
Binder
通信的参与者由4部分组成:
-
Binder驱动
:Binder
的核心,实现各种Binder
的底层操作。 -
ServiceManager
:提供Binder 实体对象
的名称到Binder 引用对象
的转换服务 -
服务端
:Binder
服务的提供者 -
客户端
:Binder
服务的使用者
关系图如下:
Binder 驱动
的简单解释:
-
Binder 驱动
属于Binder
架构的核心,通过文件系统内的标准接口,如open
、ioctl
、mmap
等,向用户层提供服务。 - 应用层和
Binder 驱动
之间的数据通过ioctl
接口来完成,并没有使用read
和write
系统调用。使用ioctl
的好处就是一次系统调用就可以完成用户系统和驱动之间的双向数据交换,不但简化了控制逻辑,也提高了传输效率。 -
Binder 驱动
的主要功能就是提供Binder
通信的通道,维护Binder
对象的引用计数,转换传输中的Binder实体对象
和Binder引用对象
以及管理数据缓存区。
ServiceManager
的简单解释:
-
ServiceManager
是一个守护进程,它的作用是提供Binder
服务的查询功能,返回被查询服务的引用。 - 对于一个
Binder
服务,通常会有一个唯一的字符串标识,只要它向ServiceManager
注册了这个标识,应用就可以通过标识来获得Binder
服务的引用对象。-
ServiceManager
是一个独立的进程,对于其他想要获得Binder
服务的进程来说,首要任务是获得ServiceManager
。 -
ServiceManager
也是通过Binder框架来提供服务的,感觉遇到了鸡生蛋蛋生鸡的情况 - 为了保证其他进程能获得
ServiceManager
,Android默认注册绑定了ServiceManager
:Binder引用对象
的核心数据是一个Binder实体对象
的引用号,它是在驱动内部分配的一个值。Binder框架
硬性规定了0
代表ServiceManager
。这样用户进程使用参数0
可以直接构造出ServiceManager
的Binder引用对象
。
-
- 为什么查找
Binder
服务功能不直接放到Binder驱动
中完成呢?- 是Android的安全管理要求,不容许任意进程都能注册
Binder
服务,虽然任意进程都能创建Binder
服务,但只有root进程
或system进程
才可以不受限制地向ServiceManager
注册服务。 -
ServiceManager
中有一张表,里面规定了能够注册服务的进程的用户ID,以及能够注册的服务名称。
- 是Android的安全管理要求,不容许任意进程都能注册
Binder
服务可以分成两种:实名服务
和匿名服务
。除实名服务
能够通过ServiceManager
查询到外,它们从开发到使用没有任何其他区别。
对于普通应用开发的Binder
服务,都是匿名服务
。对于匿名服务
无法通过ServiceManager
获取,那么客户端通过什么方式才能获得Binder引用对象
呢?
- 首先,
匿名Binder服务
的传输必须基于已经建立好的Binder通信连接,也就是基于实名注册的Binder
-
Server
将匿名Binder对象
写入Parcel
,接着通过ioctl
和binder驱动
通信,bidner驱动
会对传入的对象进行检查。 - 如果发现传入的
type
是binder
,则会在当前进程(Server
对应的proc
)查找是否有这个节点- 如果没有,就用传入的
匿名Binder对象
在用户空间的指针
和cookie
(通常是BBinder
本身)构造一个新的节点,接着得到匿名Binder对象
的引用,存放在transaction_data
中等待Client
去获取。
- 如果没有,就用传入的
-
Client
得到这个匿名Binder
的引用,通过这个引用向位于Server
中的实体发送请求。
匿名Binder这部分暂时收集到的资料就是这些,等学习完后面的再来补充吧
组件Service
和匿名Binder服务
Android组件
Service
包含了一种启动Java匿名Binder服务的方法,我们来简单看下
- 首先,需要应用调用
bindService
方法发出一个Intent
-
Framework
根据Intent
找到对应的组件Service
并启动它,此时组件Service
中匿名Binder服务
也将同时创建出来。 - 随后,
Framework
会把服务的IBinder
对象通过ConnectivityManager
的回调方法onServiceConnected()
传回到应用 - 这样应用就得到了
匿名Binder服务
的引用对象
在这里Android的Framework
用Intent
代替了Binder服务
的名称来查找对应的服务,同时也承担了ServiceManager
的工作,解析Intent
并传回服务的引用对象。如图:
Android的Framework
并没有使用新的技术来传递Binder对象
,因为Framework
中担任这个角色的ActivityManagerService
本身就是一个Binder服务
,最终还是通过Binder框架
在服务端和客户端之间传递了IBinder对象
。
Binder的层次
从代码实现上划分,Binde设计的类可以分为4个层次,如图:
- 最上层是位于
Framework
中的各种Binder服务类和他们的接口类。这一层的类非常多,例如:ActivityManagerService
、WindowManagerService
、PackageManagerService
等,它们为应用程序提供了各种各样的服务。 - 第二层是用于给最上层的
服务类和接口类
的开发提供基础,如IBinder、BBinder、BpBinder等。 - 第三层是和
Binder驱动
交互的IPCThreadState
和ProcessState
类 - 最底层是
Binder驱动
。
从图上看,libbinder
被拆分成了两个层次,主要原因是:
- 第一层和第二层联系很紧密,第二层中的各种Binder类用来支撑服务类和代理类的开发
- 但是第三层的
IPCThread
和第四层的驱动耦合的很厉害
这些正是Binder架构
被人诟病的地方,驱动和应用层之间过于耦合了,违反了Linux驱动设计
的原则,因此,主流的Linux并不愿意接纳Binder
。
网友评论