背景
如今的操作系统都支持多进程并发执行, 系统一般都存在多种不同的服务运行在多个进程当中。那么,进程与进程之间如何通信,即跨进程通信(IPC, Inter-Process Communication)是如何进行的呢? Linux已有好几种IPC机制:
- Signals: 最早的IPC方式,一个进程通过发送信号给另一个有相同UID/GID的进程或者在同一进程组的进程
- Pipes(包括 named pipes): Pipes是一个单向的用于连接一个进程的标准输出与另一个进程的标准输入的字节流通道
- Sockets: 双向的通道,两个进程通过打开同一个socket进行通信
- Message queues: 一个进程将消息写入消息队列,另一个进程从改队列中读取消息
- Semaphores: 信号量是一个可以被多个进程读写的共有变量
- Shared Memeory: 一个系统的内存区域,通过将其映射到两个不同进程的虚拟地址空间,因此每个进程都可以访问该地址空间
- D-Bus(Desktop Bus): 用于桌面组件与服务通讯的协议
对于Android而言,除了上述几种IPC方式之外,其最重要的IPC方式是binder。在了解Binder 的具体机制之前,先来看看Android系统组件。我们都知道,Android有Activity<直接与用户交互的UI>, Service<负责执行特定的任务,不与用户交互>, Content Provider<负责提供数据访问服务>, BroadCast Receiver<广播接收>四大组件。使用这四种组件都需要在应用的manifest文件AndroidManifest.xml
中进行配置声明。
Activity
是一个应用的UI界面,它主要负责前台与用户进行交互;Service
一般用于执行后台长时间的任务; Content Provider
用于保存用户需要长久保存的数据,它为本地或者远端的数据提供了一个统一的操作接口; Broadcaset Receiver
用于接收系统级别的消息,告知应用系统的状态与事件,如短息、来电以及低电量。
那么,如何让不同进程中的组件进行数据与消息的交换了?Android提供了Intents
用于组件的通信。一个 Intent由URI
和一个action组成,URI用于定位目标组件,而action则用于标识执行的操作。事实上,Intent本质是基于Binder这一基础设施来进行消息传递的。
Binder 术语
与很多RPC(Remote Procedure Call)l类似, Android Binder IPC也是基于C/S(Client/Server)的架构方式, 其主要有如下几个组成部分:
- 客户端(Binder Client): 需要使用某个服务的应用或者进程通过AIDL接口发起远程调用, 调用服务端的接口
- 服务端(Binder Service): 为所有客户端提供API的service
- 服务管理(Service Manager): 用于注册管理系统的Binder服务, 为客户端提供服务的查找
- 驱动(Binder Driver): 所有Binder通信的基础, 指令数据的接收与发送都是通过驱动来实现的
这里罗列了与Binder相关的一些术语:
- Binder Driver:用于跨进程通信的Kernel 层驱动,对用户空间提供一个
/dev/binder
的设备节点 - Binder protocol: 用于与Binder 驱动交互的数据交换协议(ioctl-based)
- IBinder interface: 每个Binder对象都需要实现的接口
- Binder Token : 用于标识Binder对象的32bit的整数
- AIDL : Android Interface Definition Language, 用于描述在IBinder接口上的操作
- Proxy: Android为每一个AIDL接口创建的代理对象,用于客户端调用服务对应的接口(An implementation of the AIDL interface that un/marshals and maps methods calls to transactions submitted via a wrapped IBinder reference to the Binder object)
- Stub: 每个AIDL接口对应的存根对象,用于服务端接受来自客户端的请求,将其转化为对应的函数调用(A partial implementation of the AIDL interface that maps transactions to Binder service method calls while un/marshals data
- Context Manager: 即ServiceManager, 特殊的Binder对象,注册时的句柄值为0,用于其他Binder服务对象的注册与查找
什么是Binder
Binder起源于由Dianne Hackborn 主导的OpenBinder ,OpenBinder“是一个系统层的组件架构,在OS系统服务之上提供一个高层次、丰富的抽象接口”,Android Binder在OpenBinder的基础上做了修改,但其基本的思想仍然保持一致。
相比上述如信号、socket等传统IPC方式,Binder有哪些不一样的特点?
- Binder是一个实现了IPC机制的内核驱动
- Binder提供了轻量级的 RPC(Remote Procedure Communication) 机制
- 每一个进程都有一个线程池来处理Binder请求
- 通过在服务端与设备文件
/dev/binder
进行内存共享的方式减少数据拷贝<只需要一次数据拷贝>
为什么需要Binder
考虑到安全(security)、稳定性(stability)以及内存管理等因素,Android 应用程序和系统服务运行在不同的进程中,因而需要通信与交换数据,即通常IPC方法需要解决的问题。
- 安全: 每个进程都有自己唯一的系统ID(UID,GID,PID),并且在沙箱(sandbox)中运行
- 稳定: 如果一个进程崩溃,不会影响到其他进程
- 内存管理:每个进程管理自己的内存资源,不需要时会释放出来给需要的进程使用
而在另一方面Android的 libc(bionic) 并不支持 SystemV IPC,
- 没有SystemV IPC 方法,如 semaphores, shared memory, message queues
- 当进程退出时忘记释放共享的IPC资源时,SystemV IPC 方法容易泄露kernel资源
Binder 通过内建的引用计数机制以及death-notification,确保IPC过程没有资源泄露:当一个binder service 不在被任何 clients引用时,binder的管理者会被通知销毁该binder service,因此 Binder 可以很好的适应低内存、低功耗等移动设备。
那么, 具体来说,Binder主要有如下一些特点:
支持 线程迁移(thread migration):
- 线程池自动化管理
- 支持远程调用
- 支持同步和异步(oneway) 调用
通过UID/PID 来识别调用者(clients)
可以跨进程发送文件描述符
支持常用数据类型的marshalling/unmarshalling
简单的AIDL接口
如果 client/service 在同一进程,同样可以使用
基本概念
通信模型
Binder 架构通信基于Client/Server模型:client(进程A)向Binder发起通信请求,并等待server的响应。在Client端,Binder为其提供了一个Proxy的接口;server(进程B)收到请求后,由线程池启动的线程对请求进行处理:
这里可以看到, Binder通信进程需要跨越三个层次(具体可以参考老罗的系列文章 https://blog.csdn.net/luoshengyang/article/details/6618363):
- Binder 内核驱动的通信( C )
- 中间件(middleware)的通信( C++ )
- 应用层的通信 ( Java )
应用层Binder通信
Java应用层主要通过AIDL接口来描述Client与Service之间的通信接口。 AIDL定义了远程service的调用接口,其在Client端产生一个Proxy类,在Server端产生一个Stub类。 AIDL的详细说明请参考:
有了接口之后,那么client要如何向server发送数据了?在Android中,一个进程向另一个进程发送数据,该过程称为Transaction(交易),交易所传递的数据,则称为transaction data, 在Java 应用层,交易数据就是一个Parcel对象。 Parcel可以用来传递flatted data,可以用于传递Parcelable对象,也可以用于传递IBinder对象的引用。
Parcel提供了诸如 writeByte/readByte
,writeDouble/readDouble
,writeInt/readInt
等一系列接口用于写入、读取Java中的基本数据类型。通常, 从一个对象创建一个parcel的过程被称为编组一个对象(marshalling),而将从Parcel重建一个对象的过程称为解组一个对象(unmarshalling)。
我们知道,Android的四个组件之间的通信都是依赖于Intent的,正是Intent将数据从一个组件传递到了另一个组件。从Intent的定义可以看出,其本质上是一个可以被编组与解组的Parcel对象,因而可以实现跨组件、跨进程的数据通信。
1 | public class Intent implements Parcelable, Cloneable{ ... } |
中间件层Binder通信
中间件层的编程语言主要是C++,其作用是:
- 管理用于处理Binder请求的线程池
- 对相关数据进行序列化与反序列化(marshalling/unmarshalling)
- 与Binder内核驱动进行交互
通过 /android/frameworks/native/include/binder/IServiceManager.h
中定义的接口:
1 | sp<IServiceManager> defaultServiceManager() |
可以获取到全局的变量 sp<IServiceManager>
,可以添加与获取相应的系统服务。
有关中间件更详细的介绍请参考: http://blog.csdn.net/luoshengyang/article/details/6627260
内核驱动层的Binder通信
Binder驱动定义了对外的操作接口: open, mmap, release, poll, 以及系统调用ioctl。 ioctl接口的定义:
1 | ioctl(int binderFD, BINDER_WRITE_READ, &bwd) |
这里, &bwd
为写入Binder驱动的一个数据结构,如下:
1 | struct binder_write_read { |
ioctl
主要有以下几个命令,其中 :
BINDER_WRITE_READ
用于写入或者读取内存中的交易数据的(最常用的一个命令)BINDER_SET_MAX_THREADS
用于设置线程池支持的最大线程数目(一般的service都设置为15)BINDER_SET_CONEXT_MGR
设置 Context(Service) manager
1 |
Binder Client与Server 通信流程
这里,再来回顾下,Android中基于Binder架构,Client与Server之间的通信流程。
(图片来自 《deep dive into Android Binder Framework》