转 http://blog.csdn.net/universus/article/details/6211589java
关键词linux
Binder Android IPC Linux 内核 驱动数据库
摘要设计模式
Binder是Android系统进程间通讯(IPC)方式之一。Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通讯,说明Binder具备无可比拟的优点。深刻了解Binder并将之与传统IPC作对比有助于咱们深刻领会进程间通讯的实现和性能优化。本文将对Binder的设计细节作一个全面的阐述,首先经过介绍Binder通讯模型和Binder通讯协议了解Binder的设计需求;而后分别阐述Binder在系统不一样部分的表述方式和起的做用;最后还会解释Binder在数据接收端的设计考虑,包括线程池管理,内存映射和等待队列管理等。经过本文对Binder的详细介绍以及与其它IPC通讯方式的对比,读者将对Binder的优点和使用Binder做为Android主要IPC方式的缘由有深刻了解。数组
基于Client-Server的通讯方式普遍应用于从互联网和数据库访问到嵌入式手持设备内部通讯等各个领域。智能手机平台特别是Android系统中,为了向应用开发者提供丰富多样的功能,这种通讯方式更是无处不在,诸如媒体播放,视音频频捕获,到各类让手机更智能的传感器(加速度,方位,温度,光亮度等)都由不一样的Server负责管理,应用程序只需作为Client与这些Server创建链接即可以使用这些服务,花不多的时间和精力就能开发出使人眩目的功能。Client-Server方式的普遍采用对进程间通讯(IPC)机制是一个挑战。目前linux支持的IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通讯方式。固然也能够在这些底层机制上架设一套协议来实现Client-Server通讯,但这样增长了系统的复杂性,在手机这种条件复杂,资源稀缺的环境下可靠性也难以保证。缓存
另外一方面是传输性能。socket做为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通讯和本机上进程间的低速通讯。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,而后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。安全
表 1 各类IPC方式数据拷贝次数性能优化
IPC服务器 |
数据拷贝次数网络 |
共享内存 |
0 |
Binder |
1 |
Socket/管道/消息队列 |
2 |
还有一点是出于安全性考虑。Android做为一个开放式,拥有众多开发者的的平台,应用程序的来源普遍,确保智能终端的安全是很是重要的。终端用户不但愿从网上下载的程序在不知情的状况下偷窥隐私数据,链接无线网络,长期操做底层设备致使电池很快耗尽等等。传统IPC没有任何安全措施,彻底依赖上层协议来确保。首先传统IPC的接收方没法得到对方进程可靠的UID/PID(用户ID/进程ID),从而没法鉴别对方身份。Android为每一个安装好的应用程序分配了本身的UID,故进程的UID是鉴别进程身份的重要标志。使用传统IPC只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标记只有由IPC机制自己在内核中添加。其次传统IPC访问接入点是开放的,没法创建私有通道。好比命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序均可以和对端创建链接,无论怎样都没法阻止恶意程序经过猜想接收方地址得到链接。
基于以上缘由,Android须要创建一套新的IPC机制来知足系统对通讯方式,传输性能和安全性的要求,这就是Binder。Binder基于Client-Server通讯模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名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框架定义了四个角色:Server,Client,ServiceManager(之后简称SMgr)以及Binder驱动。其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网相似:Server是服务器,Client是客户终端,SMgr是域名服务器(DNS),驱动是路由器。
和路由器同样,Binder驱动虽然默默无闻,倒是通讯的核心。尽管名叫‘驱动’,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是同样的:它工做于内核态,提供open(),mmap(),poll(),ioctl()等标准文件操做,以字符驱动设备中的misc设备注册在设备目录/dev下,用户经过/dev/binder访问该它。驱动负责进程之间Binder通讯的创建,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。驱动和应用程序之间定义了一套接口协议,主要功能由ioctl()接口实现,不提供read(),write()接口,由于ioctl()灵活方便,且可以一次调用实现先写后读以知足同步交互,而没必要分别调用write()和read()。Binder驱动的代码位于linux目录的drivers/misc/binder.c中。
和DNS相似,SMgr的做用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client可以经过Binder名字得到对Server中Binder实体的引用。注册了名字的Binder叫实名Binder,就象每一个网站除了有IP地址外还有本身的网址。Server建立了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式经过Binder驱动发送给SMgr,通知SMgr注册一个名叫张三的Binder,它位于某个Server中。驱动为这个穿过进程边界的Binder建立位于内核中的实体节点以及SMgr对实体的引用,将名字及新建的引用打包传递给SMgr。SMgr收数据包后,从中取出名字和引用填入一张查找表中。
细心的读者可能会发现其中的蹊跷:SMgr是一个进程,Server是另外一个进程,Server向SMgr注册Binder必然会涉及进程间通讯。当前实现的是进程间通讯却又要用到进程间通讯,这就好象蛋能够孵出鸡前提倒是要找只鸡来孵蛋。Binder的实现比较巧妙:预先创造一只鸡来孵蛋:SMgr和其它进程一样采用Binder通讯,SMgr是Server端,有本身的Binder对象(实体),其它进程都是Client,须要经过这个Binder的引用来实现Binder的注册,查询和获取。SMgr提供的Binder比较特殊,它没有名字也不须要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将本身注册成SMgr时Binder驱动会自动为它建立Binder实体(这就是那只预先造好的鸡)。其次这个Binder的引用在全部Client中都固定为0而无须经过其它手段得到。也就是说,一个Server若要向SMgr注册本身Binder就必需经过0这个引用号和SMgr的Binder通讯。类比网络通讯,0号引用就比如域名服务器的地址,你必须预先手工或动态配置好。要注意这里说的Client是相对SMgr而言的,一个应用程序多是个提供服务的Server,但对SMgr来讲它仍然是个Client。
Server向SMgr注册了Binder实体及其名字后,Client就能够经过名字得到该Binder的引用了。Client也利用保留的0号引用向SMgr请求访问某个Binder:我申请得到名字叫张三的Binder的引用。SMgr收到这个链接请求,从请求数据包里得到Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用做为回复发送给发起请求的Client。从面向对象的角度,这个Binder对象如今有了两个引用:一个位于SMgr中,一个位于发起请求的Client中。若是接下来有更多的Client请求该Binder,系统中就会有更多的引用指向该Binder,就象java里一个对象存在多个引用同样。并且相似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。经过以上过程能够看出,SMgr象个火车票代售点,收集了全部火车的车票,能够经过它购买到乘坐各趟火车的票-获得某个Binder的引用。
并非全部Binder都须要注册给SMgr广而告之的。Server端能够经过已经创建的Binder链接将建立的Binder实体传给Client,固然这条已经创建的Binder链接必须是经过实名Binder实现。因为这个Binder没有向SMgr注册名字,因此是个匿名Binder。Client将会收到这个匿名Binder的引用,经过这个引用向位于Server中的实体发送请求。匿名Binder为通讯双方创建一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就没法经过穷举或猜想等任何方式得到该Binder的引用,向该Binder发送请求。
下图展现了参与Binder通讯的全部角色,将在之后章节中一一提到。
图 1 Binder通讯示例
考察一次Binder通讯的全过程会发现,Binder存在于系统如下几个部分中:
· 应用程序进程:分别位于Server进程和Client进程中
· Binder驱动:分别管理为Server端的Binder实体和Client端的引用
· 传输数据:因为Binder能够跨进程传递,须要在传输数据中予以表述
在系统不一样部分,Binder实现的功能不一样,表现形式也不同。接下来逐一探讨Binder在各部分所扮演的角色和使用的数据结构。
虽然Binder用到了面向对象的思想,但并不限制应用程序必定要使用面向对象的语言,不管是C语言仍是C++语言均可以很容易的使用Binder来通讯。例如尽管Android主要使用java/C++,象SMgr这么重要的进程就是用C语言实现的。不过面向对象的方式表述起来更方便,因此本文假设应用程序是用面向对象语言实现的。
Binder本质上只是一种底层通讯方式,和具体服务没有关系。为了提供具体服务,Server必须提供一套接口函数以便Client经过远程访问使用各类服务。这时一般采用Proxy设计模式:将接口函数定义在一个抽象类中,Server和Client都会以该抽象类为基类实现全部接口函数,所不一样的是Server端是真正的功能实现,而Client端是对这些函数远程调用请求的包装。如何将Binder和Proxy设计模式结合起来是应用程序实现面向对象Binder通讯的根本问题。
作为Proxy设计模式的基础,首先定义一个抽象接口类封装Server全部功能,其中包含一系列纯虚函数留待Server和Proxy各自实现。因为这些函数须要跨进程调用,须为其一一编号,从而Server能够根据收到的编号决定调用哪一个函数。其次就要引入Binder了。Server端定义另外一个Binder抽象类处理来自Client的Binder请求数据包,其中最重要的成员是虚函数onTransact()。该函数分析收到的数据包,调用相应的接口函数处理请求。
接下来采用继承方式以接口类和Binder抽象类为基类构建Binder在Server中的实体,实现基类里全部的虚函数,包括公共接口函数以及数据包处理函数:onTransact()。这个函数的输入是来自Client的binder_transaction_data结构的数据包。前面提到,该结构里有个成员code,包含此次请求的接口函数编号。onTransact()将case-by-case地解析code值,从数据包里取出函数参数,调用接口类中相应的,已经实现的公共接口函数。函数执行完毕,若是须要返回数据就再构建一个binder_transaction_data包将返回数据包填入其中。
那么各个Binder实体的onTransact()又是何时调用呢?这就须要驱动参与了。前面说过,Binder实体需要以Binde传输结构flat_binder_object形式发送给其它进程才能创建Binder通讯,而Binder实体指针就存放在该结构的handle域中。驱动根据Binder位置数组从传输数据中获取该Binder的传输结构,为它建立位于内核中的Binder节点,将Binder实体指针记录在该节点中。若是接下来有其它进程向该Binder发送数据,驱动会根据节点中记录的信息将Binder实体指针填入binder_transaction_data的target.ptr中返回给接收线程。接收线程从数据包中取出该指针,reinterpret_cast成Binder抽象类并调用onTransact()函数。因为这是个虚函数,不一样的Binder实体中有各自的实现,从而能够调用到不一样Binder实体提供的onTransact()。
作为Proxy设计模式的一部分,Client端的Binder一样要继承Server提供的公共接口类并实现公共函数。但这不是真正的实现,而是对远程函数调用的包装:将函数参数打包,经过Binder向Server发送申请并等待返回值。为此Client端的Binder还要知道Binder实体的相关信息,即对Binder实体的引用。该引用或是由SMgr转发过来的,对实名Binder的引用或是由另外一个进程直接发送过来的,对匿名Binder的引用。
因为继承了一样的公共接口类,Client Binder提供了与Server Binder同样的函数原型,使用户感受不出Server是运行在本地仍是远端。Client Binder中,公共接口函数的包装方式是:建立一个binder_transaction_data数据包,将其对应的编码填入code域,将调用该函数所需的参数填入data.buffer指向的缓存中,并指明数据包的目的地,那就是已经得到的对Binder实体的引用,填入数据包的target.handle中。注意这里和Server的区别:实际上target域是个联合体,包括ptr和handle两个成员,前者用于接收数据包的Server,指向 Binder实体对应的内存空间;后者用于做为请求方的Client,存放Binder实体的引用,告知驱动数据包将路由给哪一个实体。数据包准备好后,经过驱动接口发送出去。通过BC_TRANSACTION/BC_REPLY回合完成函数的远程调用并获得返回值。