什么是Binder
在 linux 中,进程间的通讯机制有很多种,例如管道(pipe)、消息队列(message queue)、信号(signal)、共享内存(share memory)、套接字(socket)等方式,他们都是可以实现进程间通讯。但是,在 Android 终端上的应用软件的通信几乎看不到这些 IPC 通信方式,取而代之的是 Binder。Android 同时为 Java 环境和 C/C++ 环境提供了 Binder 机制。本篇文章主要介绍 C/C++ 环境下的 Binder 机制。。
Binder是一套轻量型的 IPC 机制,它比 linux 一般的通信方式更加简洁,消耗的内存资源更小。Binder 主要提供以下功能:
-
- 用驱动程序来推进进程间的通信。
- 通过共享内存来提高性能。
- 为进程请求分配每个进程的线程池。
- 针对系统中的对象引入了引用计数和跨进程的对象引用映射。
- 进程间同步调用。
Binder 的通信模型
Binder 通信是通过 linux 的Binder Driver 来实现的。Binder 的用户空间为每个进程维护着一个可用的线程池,线程池用于处理到来的 IPC 以及执行进程的本地消息,Binder 通信是同步的。同时,Binder 机制是基于 OpenBinder 来实现的,Android 系统的运行都将依赖 Binder 驱动。
Binder 通信是基于服务器(Service)与客户端(Client)的,所以需要 Binder 通信的进程都必须创建一个 Binder 接口。系统中有一个名为Service Manager的守护进程管理着系统中的各个服务,它负责监听是否有其他程序向其发送请求,如果有请求就响应,如果没有则继续监听等待。每个服务都要在Service Manager中注册,而请求服务的客户端则向Service Manager请求服务。在Android虚拟机启动之前,系统会先启动Service Manager进程,Service Manager就会打开Binder驱动,并通知Binder Kernel驱动程序,这个进程将作为System Service Manager,然后该进程将进入一个循环,等待处理来自其他进程的数据。因此,我们也可以将Binder的实现大致分为:Binder驱动、Service Manager、Service、Client 这几个部分。
Binder驱动 为上层应用程序和用户操作提供各种操作接口;
Service Manager主要负责管理Android系统中所有的服务,当客户端要与服务端进行通信时,首先就会通过Service Manager来查询和取得所需要交互的服务。当然,每个服务也都需要向Service Manager注册自己提供的服务,以便能够供客户端进行查询和获取;
Server 这里的服务即上面所说的服务端,通常也是Android的系统服务,通过Service Manager可以查询和获取某个Server;
client 这里的客户端一般是指Android系统上面的应用程序。它可以请求Server中的服务,比如Activity;
Binder 通信原理图
基于Client-Server的通信方式广泛应用于从互联网和数据库访问到嵌入式手持设备内部通信等各个领域。智能手机平台特别是Android 系统中,为了向应用开发者提供丰富多样的功能,这种通信方式更是无处不在,诸如媒体播放,视音频频捕获,到各种让手机更智能的传感器(加速度,方位,温度,光亮度等)都由不同的Server负责管理,应用程序只需做为Client与这些Server建立连接便可以使用这些服务,花很少的时间和精力就能开发出令人眩目的功能。Client-Server方式的广泛采用对进程间通信(IPC)机制是一个挑战。目前linux支持的IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。当然也可以在这些底层机制上架设一套协议来实现Client-Server通信,但这样增加了系统的复杂性,在手机这种条件复杂,资源稀缺的环境下可靠性也难以保证。
Binder 的面向对象思想
Binder使用Client-Server通信方式:一个进程作为Server提供诸如视频/音频解码,视频捕获,地址本查询,网络连接等服务;多个进程作为Client向Server发起服务请求,获得所需要的服务。要想实现Client-Server通信据必须实现以下两点:一是server 必须有确定的访问接入点或者说地址来接受Client的请求,并且Client可以通过某种途径获知Server的地址;二是制定Command- Reply协议来传输数据。例如在网络通信中Server的访问接入点就是Server主机的IP地址+端口号,传输协议为TCP协议。对Binder而言,Binder可以看成Server提供的实现某个特定服务的访问接入点, Client通过这个‘地址’向Server发送请求来使用该服务;对Client而言,Binder可以看成是通向Server的管道入口,要想和某个 Server通信首先必须建立这个管道并获得管道入口。
与其它IPC不同,Binder使用了面向对象的思想来描述作为访问接入点的Binder及其在Client中的入口:Binder是一个实体位于 Server中的对象,该对象提供了一套方法用以实现对服务的请求,就象类的成员函数。遍布于client中的入口可以看成指向这个binder对象的 ‘指针’,一旦获得了这个‘指针’就可以调用该对象的方法访问server。在Client看来,通过Binder‘指针’调用其提供的方法和通过指针调用其它任何本地对象的方法并无区别,尽管前者的实体位于远端Server中,而后者实体位于本地内存中。‘指针’是C++的术语,而更通常的说法是引用,即Client通过Binder的引用访问Server。而软件领域另一个术语‘句柄’也可以用来表述Binder在Client中的存在方式。从通信的角度看,Client中的Binder也可以看作是Server Binder的‘代理’,在本地代表远端Server为Client提供服务。本文中会使用‘引用’或‘句柄’这个两广泛使用的术语。
面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder 在英文里的原意。
当然面向对象只是针对应用程序而言,对于Binder驱动和内核其它模块一样使用C语言实现,没有类和对象的概念。Binder驱动为面向对象的进程间通信提供底层支持。
Binder 驱动
驱动程序部分驱动程序的部分在以下的文件夹中:
kernel/include/linux/binder.h
kernel/drivers/Android/binder.c
binder驱动程序是一个miscdevice,主设备号为10,此设备号使用动态获得(MISC_DYNAMIC_MINOR),其设备的节点为:/dev/binder
在其驱动的实现过程中,主要通过binder_ioctl函数与用户空间的进程交换数据。BINDER_WRITE_READ用来读写数据,数据包中有一个cmd域用于区分不同的请求。binder_thread_write函数用于发送请求或返回结果,而binder_thread_read函数则用于读取结果。在binder_thread_write函数中调用binder_transaction函数来转发请求并返回结果。当收到请求时,binder_transaction函数会通过对象的handle找到对象所在的进程,如果handle为空,就认为对象是context_mgr,把请求发给context_mgr所在的进程。请求中所有的Binder对象全部放到一个RB树中,最后把请求放到目标进程的队列中,等待目标进程读取。数据的解析工作放在binder_parse()中实现;关于如何生成context_mgr,内核中提供了BINDER_SET_CONTEXT_ MGR命令来完成此项功能。
Binder 相关结构体
1.struct binder_work;
2.Binder 对象———struct flat_binder_object
我们把进程之间传递的数据称之为Binder对象(Binder Object),它在对应源码中使用flat_binder_object结构体(位于binder.h文件中)来表示
3.struct binder_transaction_data
用于 Binder 驱动的实现
4.struct binder_write_read
5.binder_proc
binder_proc结构体用于保存调用Binder的各个进程或线程的信息,比如线程ID、进程ID、Binder状态信息等。
6.struct binder_node该结构体表示一个Binder节点。
7.struct binder_thread binder_thread结构体用于存储每一个单独的线程的信息
8.binder_transaction 主要用来中转请求和返回结果,保存接收和要发送的进程信息
其中,Binder 驱动在前面已经介绍了,它用于实现Binder的设备驱动,主要负责组织Binder的服务节点,调用Binder相关的处理线程,完成实际的Bainder传输等,它位于Binder结构的最底层(即Linux内核层)。Binder Adapter层是对Binder驱动的封装,主要用于操作Binder驱动,即应用程序不必直接接触Binder驱动程序,实现包括IPCThreadState.cpp和ProcessState.cpp,以及Parcel.cpp中的部分内容。Binder核心库是Binder框架的核心实现,主要包括IBinder、Binder(服务器端)和BpBinder(客户端);位最上面两层的Binder框架和具体的客户端/服务端都分别有Java和C++两种实现方案,主要供应用程序使用,比如摄像头和多媒体等,它们通过调用Binder的核心库来实现。由于这几个部分关系紧密,不便于单独分析每个模块,因此需要结合Binder的本地实现一起进行分析。
Binder的工作流程:
1)客户端首先获得服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的“引用”,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就像访问本地方法一样。
2)客户端通过调用服务器代理对象的方式向服务器端发送请求。
3)代理对象将用户请求通过Binder驱动发送到服务器进程。
4)服务器进程处理用户请求,并通过Binder驱动返回处理结果给客户端的服务器代理对象。
5)客户端收到服务器端的返回结果。
Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块,和一个守护进程Service Manager。
首先来看服务端。一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务代码。因此,要实现一个Binder服务,就必须重载onTransact()方法。
可以想象,重载onTransact()函数的主要内容是把onTransact()函数的参数转换为服务函数的参数,而onTransact()函数的参数来源是客户端调用transact()函数时输入的,因此,如果transact()有固定格式的输入,那么onTransact()就会有固定格式的输出。
下面再看Binder驱动。任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。
最后来看应用程序客户端。客户端要想访问远程服务,必须获取远程服务在Binder对象中对应的mRemote引用,至于如何获取,下面几节将要介绍。获得该mRemote对象后,就可以调用其transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容主要包括以下几项。
以线程间消息通信的模式,向服务端发送客户端传递过来的参数。
挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知(notify)。
接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。
从这里可以看出,对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的Binder,而事实上则是通过Binder驱动进行了中转。即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,所不同的是Binder驱动中的对象不会再额外产生一个线程。