Binder机制简介
参考:《Android框架揭秘》《Android系统源代码情景分析》
推荐:
Android Binder设计与实现 - 设计篇
Android进程间通信–Binder
Binder ServiceManager解析
Android Binder机制(一) Binder的设计和框架
1、Linux内存空间
Android进程与Linux进程一样,他们只运行在进程固有的虚拟地址空间中,32位系统中虚拟地址空间为4GB,其中3GB是用户空间,1GB是内核空间(可通过内核设定修改)。用户代码和相关库分别运行在用户空间的代码区域、数据区域、以及堆栈区域中,而内核空间中运行的代码则运行在内核空间的各个区域中。并且,进程具有各自独立的地址空间,单独运行。
如图所示:
那么,一个拥有独立空间的进程如何向另一个进程传递数据呢?显然要通过两个进程共享的内核空间。虽然各自进程拥有各自独立的用户空间,但是内核空间是共享的。从内核的角度来看进程不过是一个作业单位,虽然各进程的用户空间相互独立,但运行在内核空间的任务数据,代码都是彼此共享的。
例如,在搭载Android系统的手机中,使用相机拍照后,将其显示在桌面上。在这一过程中主要设计两个进程,一个进程用来驱动相机,另一个进程用来将照片显示在桌面上。相机驱动进程在拍完照之后,必然要通过负责画面输出的进程,将拍摄的照片显示到桌面上。那么驱动相机的进程是如何向负责画面输出的进程提出请求,把照片显示到桌面上的呢?
经过前面的学习,我们知道两个进程共享内核空间,所以相机驱动进程可以通过内核空间把显示请求传递给负责画面输出的进程,即两个进程通过内核空间,互相交换信息,实现IPC通信。
2、Android基本架构
由上图我们看到Binder Driver是运行在Linux内核中的,各个进程的用户空间是无法共享的,为了实现进程间的通信,Binder使用运行在内核空间(内核空间是所有进程共享的)的抽象驱动程序Binder Driver来实现进程进程间的通信(Linux也提供了其他的IPC机制)。
Bind机制简介
如果两个对象能直接相互访问,则这两个对象都存在于相同的内存地址空间,即同一个进程中,如果两个对象分别存在于两个不同的进程中,那么这两个对象是不能直接互相调用的。所以我们就要用到跨进程通信技术,使存在于两个不同进程的对象能够互访。Binder机制就是Android中应用最广泛的一种。
Android对Binder机制进行了抽象,定义了IBinder接口,该接口是对跨进程对象的抽象,在C/C++和Java层都有定义。IBinder定义了一套使用IBinder机制来实现客户端与服务端的通信协议。
一个普通对象只能在当前进程中被访问,如果希望它能被其他进程访问,就必须实现IBinder接口。IBinder接口可以指向本地对象,也可以指向远程对象,关键就在于IBinder接口中的transact函数。如果IBinder指向的是一个远程对象,那么transact只是负责把请求发送给服务器;如果IBinder指向的是一个本地对象,那么transact只负责提供服务即可。因此不管本地对象还是远程对象,都必须实现IBinder接口,才能够通过binder驱动建立两者之间的通信。
3、Binder机制分析
binder驱动中的数据结构
先来了解一下binder驱动中的几个数据结构:
binder_proc
一个进程在使用Binder进程间通信机制之前,首先要来调用函数open打开设备文件/dev/binder,此时Binder驱动程序会为这个进程创建一个binder_proc结构体,并且将这个结构体保存在一个全局的hash列表binder_procs中。
binder_proc结构体如下:
/*
* 描述一个正在使用Binder进程间通信机制的进程
*/
struct binder_proc {
struct hlist_node proc_node;
struct rb_root threads;
struct rb_root nodes;
struct rb_root refs_by_desc;
struct rb_root refs_by_node;
int pid;
...
};
这里面有很多内容,注意其中的那4个rb_root域,“rb”的意思是“red black”即红黑树,我们先来分析其中三个nodes,refs_by_desc,refs_by_desc。
在binder通信机制中,在用户空间的 Service 组件在内核空间使用 Binder实体对象,即 binder_node 来描述,在用户空间的 Client 组件在内核空间使用 Binder实体对象的引用 即 binder_ref 来描述
一个进程自己提供binder时,它就是服务端,当这个进程需要使用其他binder时,它就是客户端,所以使用binder_proc描述一个进程时,既要有binder实体对象用于描述这个进程自己的binder,于是就有nodes这颗红黑树,又要有使用别的进程的binder对象的引用,于是就有了refs_by_desc和refs_by_node这两颗红黑树。
其中,nodes树用于记录binder实体,节点类型为binder_node,refs_by_desc树和refs_by_node树则用于记录binder代理,节点类型相同,为binder_ref。之所以会有两个代理树,是为了便于快速查找,我们暂时只关心其中之一就可以了。
在一个进程中,有多少“被其他进程进行跨进程调用的”binder实体,就会在该进程对应的nodes树中生成多少个红黑树节点。另一方面,一个进程要访问多少其他进程的binder实体,则必须在其refs_by_desc树中拥有对应的引用节点。
binder_node
struct binder_node {
int debug_id;
struct binder_work work;
union {
struct rb_node rb_node;
struct hlist_node dead_node;
};
struct binder_proc *proc;
struct hlist_head refs;
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
binder_uintptr_t ptr;
binder_uintptr_t cookie;
unsigned has_strong_ref:1;
unsigned pending_strong_ref:1;
unsigned has_weak_ref:1;
unsigned pending_weak_ref:1;
unsigned has_async_transaction:1;
unsigned accept_fds:1;
unsigned min_priority:8;
struct list_head async_todo;
};
结构体binder_node用来描述一个Binder实体对象。每一个Server进程中的Binder在Binder驱动中都对应一个Binder对象,用来描述它在内核的状态。
成员变量proc指向一个Binder实体对象的宿主进程。在Binder驱动程序中,这些宿主进程通过一个binder_proc结构体来描述(上一节以分析)。宿主进程使用一个红黑树来维护它内部所有的Binder实体对象,而每一个Binder实体对象的成员变量rb_node就正好是这个红黑树中的一个节点。
成员变量cookie指向该Binder在Server中的地址
由于一个Binder实体对象可能会同时被多个Client组件引用,因此,Binder驱动程序就使用结构体binder_ref来描述这些引用关系,并且将引用了同一个Binder实体对象的所有引用都保存在一个hash列表中,这个hash列表通过Binder实体对象的成员变量refs来描述,Binder驱动程序通过这个成员变量就可以知道有哪些Client组件引用了同一个Binder实体对象。
binder_ref
struct binder_ref {
/* Lookups needed: */
/* node + proc => ref (transaction) */
/* desc + proc => ref (transaction, inc/dec ref) */
/* node => refs + procs (proc exit) */
int debug_id;
struct rb_node rb_node_desc;
struct rb_node rb_node_node;
struct hlist_node node_entry;
struct binder_proc *proc;
struct binder_node *node;
uint32_t desc;
int strong;
int weak;
struct binder_ref_death *death;
};
结构体binder_ref用来描述一个Binder引用对象。每一个Client组件在Binder驱动程序中都对应有一个Binder引用对象,用来描述它在内核中的状态。
成员变量node用来描述一个Binder引用对象所引用的Binder实体对象。这样两者就关联了起来。前面介绍结构体binder_node时提到,每一个Binder实体对象都有一个hash列表,用来保存哪些引用了它的Binder引用对象,而这些Binder引用对象的成员变量node_entry正好时这个hash列表的节点。
成员变量desc是一个句柄值,或者成为描述符,它是用来描述一个Binder引用对象的。在Client进程的用户空间中,一个Binder引用对象是使用一个句柄值来描述的,因此,当Client进程的用户空间通过Binder驱动程序来访问一个Service组件时,它只需要指定一个句柄值,Binder驱动程序就可以通过该句柄值找到对应的Binder引用对象,然后再根据该Binder引用对象的成员变量node找到对应的Binder实体对象,最后就可以通过该Binder实体对象找到要访问的Service组件。
成员变量proc指向一个Binder引用对象的宿主进程。一个宿主进程使用两个红黑树来保存它的内部所有的引用对象,它们分别以句柄值和对应的Binder实体对象的地址来作为关键字保存这些Binder引用对象,而这些Binder引用对象的成员变量rb_node_desc和rb_node_node就正好是这两个红黑树中的节点。
4、Binder过程简单描述
使用binder机制时,我们会感觉到,好像binder对象从Server进程被”传递”到了Client进程中。这其中涉及了四种类型的对象,它们粉表示位于驱动程序(内存空间)中的Binder实体对象(binder_node)和Binder引用对象(binder_ref),以及位于Binder库(用户空间)中的Binder本地对象(BBinder)和Binder代理对象(BpBinder)。下面先来简单分析一下这四个对象:
BBinder
Binder本地对象是一个类型为BBinder的对象,它是在用户空间中创建的,并且运行在Server进程中。Binder本地对象一方面会被运行在Server进程中的其他对象引用,另一方面也会被Binder驱动程序中的Binder实体对象引用,说白了,这个Binder里面就是服务的具体实现。
Binder实体对象
Binder实体对象是一个类型为binder_node的对象,它是在Binder驱动程序(内核空间)中创建的。每一个BBinder在Binder驱动中都对应有一个Binder实体对象,结构体binder_node就是用来描述一个Binder实体对象。就是在内核空间中对用户空间中的BBinder进行了描述,记录BBinder的地址,宿主进程,引用数等信息。
BpBinder
Binder代理对象是一个类型为BpBinder的对象,它在用户空间创建,并且运行在Client进程中。与Binder本地对象类似,Binder代理对象一方面会被运行在Client进程中的其他对象引用,另一方面也会引用Binder驱动程序中的Binder引用对象。
Binder引用对象
Binder引用对象是一个类型为binder_ref的对象,它是在Binder驱动程序中创建的,并且被用户空间中的Binder代理对象所引用。
它们之间的交互关系如下:
再重复一次:在Client进程的用户空间中,一个Binder引用对象是使用一个句柄值来描述的。因此,当Client进程的用户空间通过Binder驱动程序来访问一个Service组件时,它只需要指定一个句柄值,Binder驱动程序就可以通过该句柄值找到对应的Binder引用对象,然后再根据该Binder引用对象的成员变量node找到对应的Binder实体对象,最后就可以通过该Binder实体对象找到要访问的Service组件。
Client进程发起跨进程调用,向binder驱动传入自己记录的句柄值,binder驱动就会在Client进程对应的binder_proc结构体中的引用树refs_by_desc中查找和句柄相符的binder_def节点,一旦找到binder_ref节点,就可以通过该节点的node域找到对应的binder_node节点,这个binder_node当然是从属于Server进程,不过不要紧,因为binder_ref和binder_node都处于binder驱动的地址空间,所以指针是可以直接指向的,目标binder_node节点的cookie域,记录的是Server进程BBinder的地址,Binder驱动只需要把这个值反应给应用层,应用层就可以直接拿到BBinder。
那么问题来了,Client进程是如何获得一个句柄的???
5、Binder通信模型
Binder通信框架:
Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。Server,Client,ServiceManager分别是三个不同的进程。
和DNS类似,ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。注册了名字的Binder叫实名Binder,就象每个网站除了有IP地址外还有自己的网址。
Server注册到ServiceManager中
Server创建了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,通知ServiceManager注册一个名叫张三的Binder,它位于某个Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体节点以及ServiceManager对实体的引用,将名字及新建的引用打包传递给ServiceManager。也就是说,现在Server进程的内核空间新建一个Binder实体对象,然后在ServiceManager所在进程的内核空间新建一个Server进程中Binder实体对象的引用对象,然后驱动将Binder引用对象和名字发送给ServiceManager,ServiceManager收数据包后,从中取出名字和引用填入一张查找表中。
既然Server,Client,ServiceManager分别是三个不同的进程,那么Server向ServiceManager注册Binder必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡前提却是要找只鸡来孵蛋。Binder的实现比较巧妙:预先创造一只鸡来孵蛋:ServiceManager和其它进程同样采用Binder通信,ServiceManager是Server端,有自己的Binder对象(实体),其它进程都是Client,需要通过这个Binder的引用来实现Binder的注册,查询和获取。ServiceManager提供的Binder比较特殊,它没有名字也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成ServiceManager时Binder驱动会自动为它创建Binder实体(这就是那只预先造好的鸡)。其次这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。也就是说,一个Server若要向ServiceManager注册自己Binder就必需通过0这个引用号和ServiceManager的Binder通信。类比网络通信,0号引用就好比域名服务器的地址,你必须预先手工或动态配置好。要注意这里说的Client是相对ServiceManager而言的,一个应用程序可能是个提供服务的Server,但对ServiceManager来说它仍然是个Client。
Client 获得实名Binder的代理
Server向ServiceManager注册了Binder实体及其名字后,Client就可以通过名字获得Binder的引用了。Client也利用保留的0号引用向ServiceManager请求访问某个Binder:我申请获得名字叫张三的Binder引用,ServiceManager收到请求后,从请求数据包获取Binder的名字,根据名字找到对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。Client根据这个引用找到张三Binder实体,先检查是否已经为张三Binder实体创建过引用对象(遍历binder_ref红黑树),如果已经创建,就根据Binder引用对象的句柄(binder_ref中有句柄)找到Binder代理对象,这样Client就获得了Binder代理,如果没有创建,则在自己进程内核空间中创建新的张三Binder引用对象,并分配一个句柄,Client进程根据这个句柄在用户空间创建一个Binder的代理对象,这样Client就获得了Binder代理。这也就回答了之前的问题,如何获得句柄
从面向对象的角度,这个Binder实体对象,现在有两个引用,一个位于ServiceManager中,一个位于发起请求的Client中
匿名 Binder
Android中的实名Binder服务都是系统提供的,如ActivityManagerService,PowerManagerService,WindowManagerService等,实名服务可以通过ServiceManager查询到。
普通应用开发的Binder服务,只能是匿名服务。它不能通过ServiceManager查询到,但还是得通过Binder来查询。匿名服务经常使用的场景是服务进程回调客户进程中的函数。整个过程是:
客户端和服务端通过Binder连接上后,客户端把本地进程中创建的匿名服务的实体对象作为函数参数传递到服务端,驱动会在中间把实体对象“转换”成引用对象,这样服务进程就得到了客户进程创建的Binder服务的引用对象,然后就能回调客户进程中Binder服务的函数了。
在Java层Android还提供了通过组件Service的方式来包装和使用匿名服务。
匿名服务因为没有ServiceManager来提供名称解析服务,因此一般只能用于服务进程回调客户进程。但是,Android还通过Framework提供了一种启动Java匿名Binder服务的方法。这种方法的过程如下:
首先,某个应用通过调用bindService()方法发出一个Intent,Framework根据Intent找到对应的组件Service并启动它,包在组件Service中的Binder服务也将同时创建出来。
随后,Framework会把服务的IBinder对象通过ConnectivityManager的回调方法onServiceConnected()传回到应用,这样应用就得到匿名Binder服务的引用对象,也就能使用组件Service中的匿名Binder服务了。
在这里Framework用Intent代替了Binder服务的名称来查找对应的服务,同时也承担了ServiceManager的工作,解析Intent并传回服务的引用对象。
Binder通信模型