Android Binder机制の设计与实现5(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
无论是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的操作
5.2.1 文件形式的 Binder除了通常意义上用来通信的Binder,还有一种特殊的Binder:文件Binder。这种Binder的基本思想是:将文件看成Binder实 体,进程打开的文件号看成Binder的引用。一个进程可以将它打开文件的文件号传递给另一个进程,从而另一个进程也打开了同一个文件,就象Binder 的引用在进程之间传递一样。 一个进程打开一个文件,就获得与该文件绑定的打开文件号。从Binder的角度,linux在内核创建的打开文件描述结构struct file是Binder的实体,打开文件号是该进程对该实体的引用。既然是Binder那么就可以在进程之间传递,故也可以用 flat_binder_object结构将文件Binder通过数据包发送至其它进程,只是结构中type域的值为BINDER_TYPE_FD,表明 该Binder是文件Binder。而结构中的handle域则存放文件在发送方进程中的打开文件号。我们知道打开文件号是个局限于某个进程的值,一旦跨 进程就没有意义了。这一点和Binder实体用户指针或Binder引用号是一样的,若要跨进程同样需要驱动做转换。驱动在接收Binder的进程空间创 建一个新的打开文件号,将它与已有的打开文件描述结构struct file勾连上,从此该Binder实体又多了一个引用。新建的打开文件号覆盖flat_binder_object中原来的文件号交给接收进程。接收进 程利用它可以执行read(),write()等文件操作。 传个文件为啥要这么麻烦,直接将文件名用Binder传过去,接收方用open()打开不就行了吗?其实这还是有区别的。首先对同一个打开文件共享 的层次不同:使用文件Binder打开的文件共享linux VFS中的struct file,struct dentry,struct inode结构,这意味着一个进程使用read()/write()/seek()改变了文件指针另一个进程的文件指针也会改变;而如果两个进程分别使用 文件名打开同一文件则有各自的struct file结构,从而各自独立维护文件指针,互不干扰。其次是一些特殊设备文件要求在struct file一级共享才能使用,例如Android的另一个驱动ashmem,它和Binder一样也是misc设备,用以实现进程间的共享内存。一个进程打 开的ashmem文件只有通过文件Binder发送到另一个进程才能实现内存共享,这大大提高了内存共享的安全性,道理和Binder增强了IPC的安全 性是一样的。 5.3 Binder 在驱动中的表述驱动是Binder通信的核心,系统中所有的Binder实体以及每个实体在各个进程中的引用都登记在驱动中;驱动需要记录Binder引用 ->实体之间多对一的关系;为引用找到对应的实体;在某个进程中为实体创建或查找到对应的引用;记录Binder的归属地(位于哪个进程中);通过 管理Binder的强/弱引用创建/销毁Binder实体等等。 驱动里的Binder是什么时候创建的呢?前面提到过,为了实现实名Binder的注册,系统必须创建第一只鸡 – 为SMgr创建的,注册实名Binder专用的Binder实体,负责实名Binder注册过程中的进程间通信。既然创建了实体也要有对应的引用:驱动将 所有进程中的0号引用都预留给该Binder实体,即一开始所有进程的0号引用都指注册实名Binder专用的Binder,无须特殊操作任何进程通过0 号引用都可以注册实名Binder。接下来随着应用程序通过不断地注册实名Binder,不断向SMgr索要Binder的引用,不断将Binder从一 个进程传递给另一个进程,越来越多的Binder以传输结构 – flat_binder_object的形式穿越驱动做跨进程的迁徙。由于binder_transaction_data中data.offset数组 的存在,所有流经驱动的Binder都逃不过驱动的眼睛。Binder将对每个穿越进程边界的Binder做如下操作:检查传输结构的type域,如果是 BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER则创建Binder的实体;如果是 BINDER_TYPE_HANDLE或BINDER_TYPE_WEAK_HANDLE则创建Binder的引用;如果是 BINDER_TYPE_HANDLE则为进程打开文件,无须创建任何数据结构。详细过程可参考表7。随着越来越多的Binder实体或引用穿过驱动在进 程间传递,驱动会在内核里创建越来越多的节点或引用,当然这个过程对用户来说是透明的。 5.3.1 Binder 实体在驱动中的表述驱动中的Binder实体也叫‘节点’,隶属于提供实体的进程,由struct binder_node结构来表示: 表 8 Binder节点描述结构:binder_node
每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的 flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个新节点添加到树中。由于对于同一个进程来说 内存地址是唯一的,所以不会重复建设造成混乱。 5.3.2 Binder 引用在驱动中的表述和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref结构体表示: 表 9 Binder引用描述结构:binder_ref
就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存 放所有该进程正在使用的引用。但Binder的引用可以通过两个键值索引: · 对应实体在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。实体在内核中 的地址是唯一的,用做索引不会产生二义性;但实体可能来自不同用户进程,而实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个 进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。 · 引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号将返回给 应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都保留给SMgr,其它值由驱动在创建引用时动态分配。向Binder 发送数据包时,应用程序通过将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的 Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信 息,实现数据包的路由。 |
|
来自: android之情殇 > 《android》