0
点赞
收藏
分享

微信扫一扫

Android跨进程通信IPC之13——Binder总结

Android跨进程通信IPC整体内容如下

本篇文章的主要内容如下:

一 、 Android为什么选用Binder作为最重要的IPC机制

我们知道在Linux系统中,进程间的通信方式有socket,named pipe,message queue, signal,sharememory等。这几种通信方式的优缺点如下:

Android中属性服务的实现和vold服务的实现采用了socket,getprop和setprop等命令都是通过socket和init进程通信来获的属性或者设置属性,vdc命令和mount service也是通过socket和vold服务通信来操作外接设备,比如SD卡

Message queue允许任意进程共享消息队列实现进程间通信,并由内核负责消息发送和接受之间的同步,从而使得用户在使用消息队列进行通信时不再需要考虑同步问题。这样使用方便,但是信息的复制需要额外消耗CPU时间,不适合信息量大或者操作频繁的场合。共享内存针对消息缓存的缺点改而利用内存缓冲区直接交换信息,无须复制,快递,信息量大是其优点。

共享内存块提供了在任意数量的进程之间进行高效的双向通信机制,每个使用者都可以读写数据,但是所有程序之间必须达成并遵守一定的协议,以防止诸如在读取信息之前覆盖内存空间等竞争状态的实现。不幸的是,Linux无法严格保证对内存块的独占访问,甚至是你通过使用IPC_PRIVATE创建新的共享内存块的时候,也不能保证访问的的独占性。同时,多个使用共享内存块的进程之间必须协调使用同一个键值。

Android应用程序开发者开发应用程序时,对系统框架的进程和线程运行机制不必了解,只需要利用四大组件开发,Android应用开发时可以轻易调用别的软件提供的功能,甚至可以调用系统App,在Android的世界里,所有应用都是平等的,但实质上应用进程被隔离在不同的沙盒里。

二、Binder中相关的类简述

为了让大家更好的理解Binder机制,我这里把每个类都简单说下,设计到C层就是结构体。每个类/结构体都有一个基本的作用,还是按照之前的分类,如下图

关于其中的关系,比如继承,实现如下图:

(一)、Java层相关类

1、IInterface

比如我们知道的ActivityManagerService继承自ActivityManagerNative,而ActivityManagerNative实现了IActivityManager,而IActivityManager继承自IInterface

2、IBinder

比如我们知道的Binder类就是实现了IBinder

3、Binder

比如我们知道的ActivityManagerService继承自ActivityManagerNative,而ActivityManagerNative继承自Binder

4、BinderProxy

存在客户端的进程的服务端Binder的代理

5、Parcel

我们经常的Parcelable其实就是将数据写入Parcel。具体可以看C++层的Parcel类

(二)、JNI层相关类

1、JavaBBinderHolder
2、JavaBBinder

JavaBBinder和JavaBBinderHolder相关的类类图如下所示(若看不清,请点击看大图),JavaBBinder继承自本地框架的BBinder,代表Binder Service服务端的实体,而JavaBBinderHolder保存了JavaBBinder指针,Java层的Binder的mObject保存的JavaBBinderHolder指针的值,故此这里用聚合关系表示。BinderProxy的mObject保存的是BpBinder对象的指针的值,故此这里用聚合关系表示

3、Java层Binder对象和NativeBinder对象相互转化的方法

这里涉及两个重要的函数

这样就实现了两层对象的转化

(三)、Native层相关类

1、BpRefBase
2、IInterface
3、BpInterface

BpInterface的原型:

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase

BpInterface是一个模板类,当server提供了INTERFACE接口(例如IXXXService),通常会继承BpInterface模板实现了一个BpXXXService

class BpXXXService: public BpInterface<IXXXService>
4、BnInterface
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder

BnXXXService 例如:

class BnXXXService: public BnInterface<IXXXService>

IXXXService为client端的代理接口BpXXXService和Server端的BnXXXServer的共同接口类,这个共同接口类的目的就是保证Service方法在C/S两端的一致性。

那么,BBinder与BpBinder的区别是什么?BpBinder是Client端创建的用于向Server发送消息的代理,而BBinder是Server端用于接受消息的通道。他们代码中虽然均有transact方法,但两者的作用不同,BpBinder的transact方法时向IPCThreadStata实例发送消息,通知其有消息要发送给Binder Driver;而BBinder则当IPCThreadState实例收到Binder Driver消息时,通过BBinder的transact方法将其传递给它的子类BnXXXService的onTransact函数执行Server端的操作

5、IBiner

这个类比较重要,说一下他的几个方法

方法名 说明
localBinder 获取本地Binder对象
remoteBinder 获取远程Binder对象
transact 进行一次Binder操作
queryLocalInterface 获取本地Binder,如果没有则返回NULL
getInterfaceDescriptor 获取Binder的服务接口描述,其实就是Binder服务的唯一标识
isBinderAlive 查询Binder服务是否还活着
pingBinder 发送PING_TRANSACTION给Binder服务
6、BpBinder

BpBinder的实例代表了远程Binder,这个类的对象将被客户端调用。其中handle()函数将返回指向Binder服务实现者的句柄,这个类最重要的就是提供了transact()函数,这个函数将远程调用的参数封装好发送给Binder驱动。

7、BBinder

BBinder的实例代表了本地的Binder,它描述了服务的提供方,所有Binder服务的实现者都继承这个类(的子类),在继承类中,最重要的就是实现onTransact()函数,因为这个方法是所有请求的入口。因此,这个方法是和BpBinder中的transact()函数对应的。

由于BBinder与BpBinder都是IBinder的子类,具体区别如下:


8、ProcessState

ProcessState是以单例模式设计的,每个进程在使用Binder机制进行通信时,均需要维护一个ProcessState实例来描述当前进程在Binder通信时Binder状态。

在Binder IPC中,所有进程均会启动一个thread来负责与Binder Drive来通信,也就是不停的读写Binder Drive。Poolthread的启动方式为

ProcessState::self()->startThreadPool();

BpBinder的主要功能是负责Client向Binder Driver发送调用请求的数据,它是Client端Binder通信的核心对象,通过调用transact函数向Binder Driver发送调用请求数据。BpBinder的构造函数为

BpBinder(int32_t handle);

该构造函数可见,BpBinder会将通信中的Server的handle记录下来,当有数据发送时,会通知Binder Driver数据的发送目标。
ProcessState通过下下述方式来获取BpBinder对象。

ProcessState::self()->getContextObject(handle);

ProcessState创建的BpBinder实例,一般情况下会作为参数创建一个Client端的Service代理接口,例如BpXXX,在和ServiceManager通信时,Client会创建一个代理接口BpServieManager。

9、IPCThreadState

IPCThreadState是以单例模式设计的。因为每个进程只维护了一个ProcessState实例,同时Process state只启动了一个Poolthread,因此每个进程只需要一个IPCThreadState即可。

Poolthread实际内容为:

IPCThreadState::self()->joinThreadPool();

ProcessState中生成了BpBinder实例通过调用IPCThreadState的transact函数来向mOut中写入数据。

10、Parcel

Parcel是Binder IPC中最基本的通信单元,它存储C/S间函数调用的参数。Parccel只能存储基本的数据类型,如果是复杂的数据类型的话,在存储时,需要将其拆分为基本的数据类型来存储。

11、补充
12、类关系

下图描述了这些类之间的关系:

12、Native流程

那我们来看下在Native层中的Binder流程,以MediaPlayer为例


总结一下:

强调一下:

Binder的Native层设计逻辑简单介绍完毕。我们接下来看看Binder的底层设计。

(四)、Linux内核层的结构体

Binder驱动中有很多结构体,驱动中的结构体可以分为两类:

1、与用户控件共用的结构体
结构体名称 说明
flat_binder_object 描述在Binder在IPC中传递的对象
binder_write_read 存储一次读写操作的数据
binder_version 存储Binder的版本号
transaction_flags 描述事务的flag,例如是否是异步请求,是否支持fd
binder_transaction_data 存储一次事务的数据
binder_ptr_cookie 包含了一个指针和一个cookie
binder_handle_cookie 包含了一个句柄和一个cookie

这里面binder_write_readbinder_transaction_data这两个结构体最为重要,它们存储了IPC调用过程中的数据

2、仅在Binder驱动中使用
结构体名称 说明
binder_node 描述了Binder实体节点,即对应一个Server
binder_ref 描述对于Binder实体的引用
binder_buffer 描述Binder通信过程中存储数据的Buffer
binder_proc 描述使用Binder的进程
binder_thread 描述Binder的线程
binder_work 描述通信的一项任务
binder_transaction 描述一次事务的相关信息
binder_deferred_state 描述延迟任务
binder_ref_death 描述Binder实体死亡的信息
binder_transaction_log debugfs日志
binder_transaction_log_entry debugfs日志条目
3、总结

西面这张时序图描述了上述一个transaction完成的过程。不同颜色代表不同的线程。注意的是,虽然Kernel和User space线程颜色是不一样的,但所有的系统调用都发生在用户进程的上下文李(所谓上下文,就是Kernel能通过某种方式找到关联的进程,并完成进程的相关操作,比如唤醒某个睡眠线程,或跟用户控件交换数据,copy_from,copy_to,与之相对应的是中断上下文,其完全异步出发, 因此无法做任何与进程相关的操作,比如睡眠,锁等).

(五)、Binder整个通信个过程

三、Binder机制概述

前面几篇文章分别从驱动,Native,Framework层介绍了Binder,那我们就来总结一下:

四、Binder通信概述

Binder通信是一种C/S结构的通信结构。

整体流程如下:

五、Binder协议

Binder协议可以分为控制协议和驱动协议两类

(一) Binder控制协议

Binder控制协议是 进程通过 ioctl("/dev/binder") 与Binder设备进行通讯的协议,该协议包含以下几种命令:

命令 说明 参数类型
BINDER_WRITE_READ 读写操作,最常用的命令。IPC过程就是通过这个命令进行数据传递 binder_write_read
BINDER_SET_MAX_THREADS 设置进程支持的最大线程数量 size_t
BINDER_SET_CONTEXT_MGR 设置自身为ServiceManager
BINDER_THREAD_EXIT 通知驱动Binder线程退出
BINDER_VERSION 获取Binder驱动的版本号 binder_version

(二) Binder驱动协议

Binder驱动协议描述了对Binder驱动的具体使用过程。驱动协议又可以分为两类:

1、binder_driver_command_protocol 共包含17条命令,分别如下:
命令 说明 参数类型
BC_TRANSACTION Binder事务,即:Client对于Server的请求 binder_transaction_data
BC_REPLAY 事务的应答,即Server对于Client的回复 binder_transaction_data
BC_FREE_BUFFER 通知驱动释放Buffer binder_uintptr_t
BC_ACQUIRE 强引用技术+1 _u32
BC_RELEASE 强引用技术-1 _u32
BC_INCREFS 弱引用+1 _u32
BC_DECREFS 弱引用 -1 _u32
BC_ACQUIRE_DONE BR_ACQUIRE的回复 binder_ptr_cookie
BC_INCREFS_DONE BR_INCREFS的回复 binder_ptr_cookie
BC_ENTER_LOOPER 通知驱动主线程ready void
BC_REGISTER_LOOPER 通知驱动子线程ready void
BC_EXIT_LOOPER 通知驱动线程已经退出 void
BC_REQUEST_DEATH_NOTIFICATION 请求接受死亡通知 binder_ptr_cookie
BC_CLEAR_DEATH_NOTIFICATION 去除接收死亡通知 binder_ptr_cookie
BC_DEAD_BINDER_DONE 已经处理完死亡通知 binder_uintptr_t

BC_ATTEMPT_ACQUIREBC_ACQUIRE_RESULT 暂未实现。

2、binder_driver_return_protocol 共包含18条命令,分别如下:
返回类型 说明 参数类型
BR_OK 操作完成 void
BR_NOOP 操作完成 void
BR_ERROR 发生错误 _s32
BR_TRANSACTION 通知进程收到一次Binder请求(Server端) binder_transaction_data
BR_REPLAY 通知进程收到Binder请求的回复(Client端) binder_transaction_data
BR_TRANSACTION_COMPLETE 驱动对于接受请求的确认税负 void
BR_FAILED_REPLAY 告知发送发通信目标不存在 void
BR_SPWAN_LOOPER 通知Binder进程创建一个新的线程 void
BR_ACQUIRE 强引用计数+1 binder_ptr_cookie
BR_RELEASE 强引用计数-1 binder_ptr_cookie
BR_INCREFS 弱引用计数+1 binder_ptr_cookie
BR_DECREFS 弱引用计数-1 binder_ptr_cookie
BR_DEAD_BINDER 发送死亡通知 binder_uintptr_t
BR_CELAR_DEATH_NOTIFACATION_DONE 清理死亡通知完成 binder_uintptr_t
BR_DEAD_REPLY 告知发送方对方已经死亡 void

BR_ACQUIRE_RESULTBR_FINISHEDBR_ATTEMPT_ACQUIRE 暂未实现。

单独看上面的协议可能很难理解,这里我们可以将一次Binder请求过程来看一下Binder协议是如何通信的,就比较好理解了,如下图:

图说明:

补充说明,通过上面的Binder协议,我们知道,Binder协议的通信过程中,不仅仅是发送请求和接收数据这些命令。同时包括了对于引用计数的管理和对于死亡通知管理的功能(告知一方,通信的另外一方已经死亡)。这些功能的通信过程和上面这幅图类似:

上面提到了Binder的架构,那我们下面就来研究下Binder的结构

六、Binder架构

(一)、Binder架构的思考

在说到Binder架构之前,先简单说说大家熟悉的TCP/IP五层通信系统结构

这是经典的五层TCP/IP协议体系,这样分层设计的思想,让每一个子问题都设计成一个独立的协议,这协议的设计/分析/实现/测试都变得更加简单:

Binder架构也是采用分层架构设计,每一层都有其不同的功能,以大家平时用的startService为例子,AMP为ActivityManagerProxy,AMS为ActivityManagerSerivce 如下图:

(二) 、Binder结构

(三) 、startService的流程

如下图:

(四)、SeviceManager自身的注册和其他service的注册

这里放一张图说明整个过程

详细经过这么多篇文章的讲解,大家对Binder有一点的了解,为了让大家加深对Binder的理解,推荐下面几篇文章
听说你Binder机制学的不错,来面试下这几个问题(一)
听说你Binder机制学的不错,来面试下这几个问题(二)
听说你Binder机制学的不错,来面试下这几个问题(三)

举报

相关推荐

0 条评论