android Binder设计与实现四
5 Binder 的表述
考察一次Binder通信的全过程会发现,Binder存在于系统以下几个部分中:
· 应用程序进程:又分为Server进程和Client进程
· Binder驱动:Server和Client有不同表述形式
· 传输数据:由于Binder可以跨进程传递,需要在传输数据中予以表述
在系统不同部分,Binder实现的功能不同,表现形式也不一样的。接下来逐一探讨Binder在各部分所扮演的角色和使用的数据结构。
5.1 Binder 在应用程序中的表述
虽然Binder用到了面向对象的思想,但并不限制应用程序一定要使用面向对象的语言,无论是C语言还是C++语言都可以很容易的使用Binder 来通信。例如尽管Android主要使用java或C++,象SMgr这么重要的进程就是用C语言实现的。不过面向对象的方式表述起来更方便,所以本文假设应用程序是用面向对象语言实现的。
Binder本质上只是一种底层通信方式,和具体服务没有关系。为了提供具体服务,Server必须提供一套接口函数以便Client通过远程访问 使用各种服务。这时通常采用Proxy设计模式:将接口函数定义在一个抽象类中,Server和Client都会以该抽象类为基类实现所有接口函数,所不 同的是Server端是真正的功能实现,而Client端是对这些函数远程调用请求的包装。如何将Binder和Proxy设计模式结合起来是应用程序实 现面向对象Binder通信的根本问题。
5.1.1 Binder 在Server端的表述 – 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()。
5.1.2 Binder 在Client端的表述 – Binder引用
做为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回合完成函数的远程调用并得到返回值。
5.2 Binder 在传输数据中的表述
Binder可以塞在数据包的有效数据中越进程边界从一个进程传递给另一个进程,这些传输中的Binder用结构 flat_binder_object表示,如下表所示:
表 6 Binder传输结构:flat_binder_object
成员 | 含义 |
unsigned long type | 表明该Binder的类型,包括以下几种: BINDER_TYPE_BINDER:表示传递的是Binder实体,并且指向该实体的引用都是强类型; BINDER_TYPE_WEAK_BINDER:表示传递的是Binder实体,并且指向该实体的引用都是弱类型; BINDER_TYPE_HANDLE:表示传递的是Binder强类型的引用 BINDER_TYPE_WEAK_HANDLE:表示传递的是Binder弱类型的引用 BINDER_TYPE_FD:表示传递的是文件形式的Binder,详见下节 |
unsigned long flags | 该域只对第一次传递Binder实体时有效,因为此刻驱动需要在内核中创建相应的实体节点,有些参数需要从该域取出: 第0-7位:代码中用FLAT_BINDER_FLAG_PRIORITY_MASK取得,表示处理本实体请求数据包的线程的最低优先级。当一个应用程序提供多个实体时,可以通过该参数调整分配给各个实体的处理能力。 第8位:代码中用FLAT_BINDER_FLAG_ACCEPTS_FDS取得,置1表示该实体可以接收其它进程发过来的文件形式的Binder。由于接收文件形式的Binder会在本进程中自动打开文件,有些Server可以用该标志禁止该功能,以防打开过多文件。 |
union { void *binder; signed long handle; }; |
当传递的是Binder实体时使用binder域,指向Binder实体在应用程序中的地址。 当传递的是Binder引用时使用handle域,存放Binder在进程中的引用号。 |
void *cookie; | 该域只对Binder实体有效,存放与该Binder有关的附加信息。 |
无论是Binder实体还是对实体的引用都从属与某个进程,所以该结构不能透明地在进程之间传输,必须有驱动的参与。例如当Server把 Binder实体传递给Client时,在发送数据中,flat_binder_object中的type是 BINDER_TYPE_BINDER,binder指向Server进程用户空间地址。如果透传给接收端将毫无用处,驱动必须对数据流中的这个 Binder做修改:将type该成BINDER_TYPE_HANDLE;为这个Binder在接收进程中创建位于内核中的引用并将引用号填入 handle中。对于发生数据流中引用类型的Binder也要做同样转换。经过处理后接收进程从数据流中取得的Binder引用才是有效的,才可以将其填入数据包binder_transaction_data的target.handle域,向Binder实体发送请求。
这样做也是出于安全性考虑:应用程序不能随便猜测一个引用号填入target.handle中就可以向Server请求服务了,因为驱动并没有为你在内核中创建该引用,必定会驱动被拒绝。唯有经过身份认证确认合法后,由‘权威机构’通过数据流授予你的Binder才能使用,因为这时驱动已经在内核中 为你建立了引用,交给你的引用号是合法的。
下表总结了当flat_binder_object结构穿过驱动时驱动所做的操作:
表 7 驱动对flat_binder_object的操作
Binder 类型(type 域) | 在发送方的操作 | 在接收方的操作 |
BINDER_TYPE_BINDER BINDER_TYPE_WEAK_BINDER |
只有实体所在的进程能发送该类型的Binder。如果是第一次发送驱动将创建实体在内核中的节点,并保存binder,cookie,flag域。 | 如果是第一次接收该Binder则创建实体在内核中的引用;将handle域替换为新建的引用号;将type域替换为BINDER_TYPE_(WEAK_)HANDLE |
BINDER_TYPE_HANDLE BINDER_TYPE_WEAK_HANDLE |
获得Binder引用的进程都能发送该类型Binder。驱动根据handle域提供的引用号查找建立在内核的引用。如果找到说明引用号合法,否则拒绝该发送请求。 | 如果收到的Binder实易做图于接收进程中:将ptr域替换为保存在节点中的binder值;cookie替换为保存在节点中的cookie 值;type替换为BINDER_TYPE_(WEAK_)BINDER。 如果收到的Binder实体不在接收进程中:如果是第一次接收则创建实体在内核中的引用;将handle域替换为新建的引用号 |
BINDER_TYPE_FD | 验证handle域中提供的打开文件号是否有效,无效则拒绝该发送请求。 | 在接收方创建新的打开文件号并将其与提供的打开文件描述结构绑定。 |
5.2.1 文件形式的 Binder
除了通常意义上用来通信的Binder,还有一种特殊的Binder:文件Binder。这种Binder的基本思想是:将文件看成Binder实 体,进程打开的文件号看成Binder的引用。一
补充:移动开发 , Android ,