Binder驱动的原理和实现
通过上一节的介绍,大家应该对Binder有了基本的认识了。任何上层应用程序接口和用户操作都需要底层硬件设备驱动的支持,并为其提供各种操作接口。本节首先从Binder的驱动实现人手,分析其原理和它提供给用户层使用的接口。
Binder驱动的原理
为了完成进程间通信,Binder采用了AIDL(Android Interface Definition Language)来描述进程间的接口。在实际的实现中,IBinder是作为一个特殊的字符型设备而存在的,设备节点为
/dev/binder,其实现遵循Linux设备驱动模型,实现代码主要涉及以下文伴:
(1)kemel/drivers/staging/binder.h
(2)kernel/drivers/staging/binder.c
在其驱动的实现过程中,主要通过bjnder 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_parser中实现;关于如何生成context__ mgr,内核中提供了BINDER。SET_ CONTEXTMGR命令来完成此项功能。下面我们就来看看Binder驱动究竟是如何实现的。
3.2.2 Blnder驱动的实现
上面我们已经对Binder驱动的原理进行了分析,在开始分析驱动的实现之前,我们还是通过一个例子来说明Binder在实际应用中应该如何运用,以及它能帮我们解决什么样的问题。这样会更容易帮助大家理解Binder驱动的实现。比如,A进程如果要使用B进程的服务,B进程首先要注题此服务,A进程通过Binder荻取该服务的hanlde,通过这个handle,A进程就可以使用该服务了。此外,你可以把handle理解成地址。A进程使用B进程的服务还意味着二者遵循相同的协议,这个协议反映在代码上就是二者要实现IBinder接口。
1.“对象”与“引用”
Binder不仅是Android系统中的一个完善的IPC机制,它也可以被当作Android系统的一种RPC(远程过程调用)机制,因为Binder的功能就是在本地“执行”其他进程的功能。因此,进程在通过Binder获取将要调用的进程服务时,可以是一个本地对象,也可以是一个远程服务的“引用”。这一点可能比较难以理解,稍候就会为大家分析,这里就先记住Binder不仅可以与本地进程通信,还可以与远程进程通信;这里的本地进程就是我们所说的本地对象,而远程进程则是我们所说的远程服务的一个“引用”。Binder的实质就是要把对象从一个进程映射到另一个进程中,而不管这个对象是本地的还
是远程的。如果是本地对象,更好理解;如果是远程对象,就按照我们上面所讲的来理解,即将远程对象的“引用”从一个进程映射到另一个进程中,于是当使用这个远程对象时,实际上
就是使用远裎对象在本地的一个“引用”,类似于把这个远程对象当作一个本地对象在使用。这也就是Binder与其他IPC机制不同的地方。
这个本地“对象”与远程对象的“引用”有什么不同呢?本地“对象”表示本地进程的地址空间的一个地址,而远程对象的“引用”则是一个抽象的32位句柄。它们之间是互斥的:
所有的进程本地对象都是本地进程的一个地址( address、ptr、binder),所有的远程进程的对象的“引用”都是一个句柄。对于发送者进程来说,不管是“对象”还是“引用”,它都会认为被发送的Binder对象是一个远程对象的句柄(即远程对象的“引用”)。但是,当Binder对象的数据被发送至日远端接收进程时,远端接收进程则会认为该Binder对象是一个本地对象地址(即本地对象)。正如我们之前说的,当Binder对象被接收进程接收后,不管该Binder对象是
“引用”这个词并不是官方所描述的,而是笔者为了方便大家理解,将其称为引用,或许你有更好的描述。
本地的还是远程的,它都会被当作一个本地进程来处理。因此,从第三方的角度来说,尽管名称不同,对于一次完整的Binder调用,都将指向同一个对象,Binder驱动则负责两种不同名称的对象的正确映射,这样殖能把数据发送给正确的进程进行通信。这个映射关系也是进程间引用对象的基础,对一个对象的引用,在远程是句柄,在本地则是地址(即本地对象的地址)。下面我们先介绍分析该机制中所使用的数据结构体,然后再对整个流程进行分析。
2.loincier Work
首先来看一个最简单也是最基础的结构体binder work,其定义如代码清单3-1所示。
代码清单3-1 binder vork定义
struct /oinder_work {
struct list head entry;
enum {
BINDER_WORK_TRANSACT工ON = l,
BINDER _WORK_TRANSACTION_COMPLETE ,
B工NDER_WORK_NODE ,
BINDER_WORK_DEAD_BINDER,
BINDER_WORK_DEAD_BINDER_AN D_CLEAR r
BINDER WORK CLEAR DEATH NOTIFICATION,
} type;
萁中entry被定义为list head,用来实现一个双向链表,存储所有binder work的队列;此外,还包含一个enum类型的type,表示binder work的类型,后文会对这些类型进行详细分析,
大家就会觉得它更像是一个用来表示状态的enum。
3.Binder的类型
Binder的类型是使用定义在binder.h头文件中的一个enum来表示的,定义如代码清单3-2
所示。
代码清单3-2 Binder类型
#define B_PACK_CHARS (cl, c2, c3, c4) \
#define B TYPE LARGE Ox85
enum {
BINDER TYPE BINDER =B PACK_CHARS(’s’, ’b’, ’★’,B_TYPE_LARGE),
BINDER TYPE WEAK BINDER =B PACK CHARS( 'W', 'b', ’★’,B_TYPE_lARGE),
BINDER TYPE HANDLE =B PACK_CHARS(’s’, 'h', ’★’, B_TYPE_LARGE),
BINDER TYPE WEAK HANDLE =B PACK CHARS( 'W', 'h!, ’★’, B_TYPE_LARGE),
BINDER TYPE FD =B PACK_CFiARS(7f’, ’d’, ’★’,B_TYPE_LARGE),
};
从上面的代码可以看出,Binder被分成了5个不同的类型,但是仔细一看却是3个不同的大类,它们分别是:本地对象(BINDER TYPE BINDER. BINDER TYPE l;VEAK BINDER).
远程对象的“引用”( BINDEIL TYPE HANIDLE、BINDER TYPE WEAK HANDLE)的文件
( BINDER—TYPE FD)。前面两种都是我们刚刚分析过的,下面主要分析最后一种——文件(BINDER_ TYPE FD)。如果传递的是BINDER TYPE FD类型,其实还是会将文件映射到句
柄上,根据此fd找到对应的文件,然后在目标进程中分配一个fd,最后把这个fd赋值给返回的句柄。
4。Binder对象
我们把进程之间传递的数据称之为Binder对象(Binder Object),它在对应源码中使用
flat binder_object结构体(位于binder.h文件中)来表示,其定义如代码清单3-3所示。
代码清单3-3 flat_binder_object定义
struct flat_ binder_object {
unsigned long type;
unsigned long flags;
union t
void *obinder ;
signed long handle;
};
void *cookie;
该结构体中的type字段描述的是Binder的粪型,传输的数据是一个复用数据联合体。对于Binder类型,数据就是一个Binder本地对象;HANDLE类型,就是一个远程的handle句柄。
本地Binder对象和远程handle句柄比较难以理解,这里我们再次举例说明:假如A有个对象O,对于A来说,O就是一个本地的Binder对象;如果B想访问A的O对象,对于B来说,O就是一个handle。因此,从根本上来说,handle和Binder都指向O。如果是本地对象,Binder还可以带有额外的数据,这些数据将被保存到cookie字段中。flags字段表示传输方式,比如同步和异步等,其值同样使用一个enum来表示,定义如代码清单3-4所示。其中TF ONEWAY表示单向传递,是异步的,不需要返回;TFROOT OBJECT表面;内容是一个组建的根对象,对应类型为本地对象Binder; TF STATUS CODE表示内容是一个32位的状态码,将对应类型为远程对象的“引用”(即句柄handle); TF ACCEPT FDS表面;可以接收一个文件描述符,对应的类型为文件(BIND嚣R TYPE FD),即handle中存储的为文件描述符。
5. lobinder transaction ciata
其实我们并没有从flat binder_object结构体中看到Binder对象所传递的实际内容,因为Binder对象所传递的实际内容是通过另外一个结构体binder transaction data来表示的,其定义如代码清单3-5所示。
代码清单3-5 binder_transaction—data定义
structbinder—transaction_data {
union{
size—t handle;
void xptr;
) target;
void *cookie;
unsigned int code;
unsigned int flags;
pid_t sender_pid;
uid—t sender—euid;
size—t data—size;
size—t offsets—size;
union f
struct f
const void *buffer;
const void *offsets;
} ptr;
uint8一t buf[8];
} data;
}j
该结构体是理解Binder驱动实现的关键,下面将详细地对一个重要的数据进行分析。其中target字段又足一个复合联合体对象,target字段中的handle是要处理此事件的目标对象的句柄,根据此handle,Binder驱动可以找到应该由哪个进程处理此事件,并且把此事件的任务分发给一个线程,而那个线程也正在执行ioctl的BINDER- WRITE_ READ操作,即正在等待一个请求(见3.1节和3.2.1节),处理方法将稍候分析。target的ptr字段与handle对应,对于请求方,使用handle来指出远程对象;对于响应方,使用ptr来寻址,以便找到需要处理此事件的对象。所以handle和ptr是一个事物的两种表达(正如前面所说的本地对象和远程对象的“引用”),handle和ptr之间的翻译(解析)关系正是Binder驱动需要维护的(在binder_ transaction函数中,稍候分析)。
另外,该结构体中的cookie字段表示target对象所附加的额外数据;code是一个命令,它描述了请求Binder对象执行的操作;flags字段描述了传输的方式与flat binder_object中的flags字段对应;sender_pid和sender_ euid表示该进程的pid和uid;data size裹示数据的大小字节数;offsets_ size表示数据的偏移量字节数;最后一个union数据data表示真正的数据,其中ptr表看;与target->ptr对应的对象的数据,buf表示与handle对象对应的数据,data中的ptr中的buffer