Binder的实现是比较复杂的,想要完全弄明白是怎么一回事,并不是一件容易的事情。
这里面牵涉到好几个层次,每一层都有一些模块和机制需要理解。
这部分内容会分以下几篇文章来讲解。
链接: 理解 Android Binder 机制(一):驱动篇.
链接: 理解 Android Binder 机制(二):C++篇.
链接: 理解 Android Binder 机制(三):JAVA篇.
链接: 理解 Android Binder 机制(四):补充篇-AIDL以及Proxy-Stub设计模式.
本文是第一篇,首先会对整个Binder机制做一个架构性的讲解,然后会将大部分精力用来讲解Binder机制中最核心的部分:Binder驱动的实现。
Binder机制简介
Binder源自Be Inc公司开发的OpenBinder框架,后来该框架转移的Palm Inc,由Dianne Hackborn主导开发。OpenBinder的内核部分已经合入Linux Kernel 3.19。
Android Binder是在OpneBinder上的定制实现。原先的OpenBinder框架现在已经不再继续开发,可以说Android上的Binder让原先的OpneBinder得到了重生。
Binder是Android系统中大量使用的IPC(Inter-process communication,进程间通讯)机制。无论是应用程序对系统服务的请求,还是应用程序自身提供对外服务,都需要使用到Binder。
因此,Binder机制在Android系统中的地位非常重要,可以说,理解Binder是理解Android系统的绝对必要前提。
在Unix/Linux环境下,传统的IPC机制包括:
- 管道
- 消息队列
- 共享内存
- 信号量
- Socket
等。
由于篇幅所限,本文不会对这些IPC机制做讲解,有兴趣的读者可以参阅《UNIX网络编程 卷2:进程间通信》。
Android系统中对于传统的IPC使用较少(但也有使用,例如:在请求Zygote fork进程的时候使用的是Socket IPC),大部分场景下使用的IPC都是Binder。
Binder相较于传统IPC来说更适合于Android系统,具体原因的包括如下三点:
-
Binder本身是C/S架构的,这一点更符合Android系统的架构
-
性能上更有优势:管道,消息队列,Socket的通讯都需要两次数据拷贝,而Binder只需要一次。要知道,对于系统底层的IPC形式,少一次数据拷贝,对整体性能的影响是非常之大的
-
安全性更好:传统IPC形式,无法得到对方的身份标识(UID/GID),而在使用Binder IPC时,这些身份标示是跟随调用过程而自动传递的。Server端很容易就可以知道Client端的身份,非常便于做安全检查
整体架构
从图中可以看出,Binder的实现分为这么几层:
-
Framework层
-
Java部分
-
JNI部分
-
C++部分
-
驱动层
驱动层位于Linux内核中,它提供了最底层的数据传递,对象标识,线程管理,调用过程控制等功能。驱动层是整个Binder机制的核心。
Framework层以驱动层为基础,提供了应用开发的基础设施。
Framework层既包含了C++部分的实现,也包含了Java部分的实现。为了能将C++的实现复用到Java端,中间通过JNI进行衔接。
开发者可以在Framework之上利用Binder提供的机制来进行具体的业务逻辑开发。其实不仅仅是第三方开发者,Android系统中本身也包含了很多系统服务都是基于Binder框架开发的。
既然是“进程间”通讯就至少牵涉到两个进程,Binder框架是典型的C/S架构。在下文中,我们把服务的请求方称之为Client,服务的实现方称之为Server。
Client对于Server的请求会经由Binder框架由上至下传递到内核的Binder驱动中,请求中包含了Client将要调用的命令和参数。请求到了Binder驱动之后,在确定了服务的提供方之后,会再从下至上将请求传递给具体的服务。整个调用过程如下图所示:
对网络协议有所了解的读者会发现,这个数据的传递过程和网络协议是如此的相似。
初识ServiceManager
前面已经提到,使用Binder框架的既包括系统服务,也包括第三方应用。因此,在同一时刻,系统中会有大量的Server同时存在。那么,Client在请求Server的时候,是如果确定请求发送给哪一个Server的呢?
这个问题,就和我们现实生活中如何找到一个公司/商场,如何确定一个人/一辆车一样,解决的方法就是:每个目标对象都需要一个唯一的标识。并且,需要有一个组织来管理这个唯一的标识。
而Binder框架中负责管理这个标识的就是ServiceManager。ServiceManager对于Binder Server的管理就好比车管所对于车牌号码的的管理,派出所对于身份证号码的管理:每个公开对外提供服务的Server都需要注册到ServiceManager中(通过addService),注册的时候需要指定一个唯一的id(这个id其实就是一个字符串)。
Client要对Server发出请求,就必须知道服务端的id。Client需要先根据Server的id通过ServerManager拿到Server的标示(通过getService),然后通过这个标示与Server进行通信。
整个过程如下图所示:
如果上面这些介绍已经让你一头雾水,请不要过分担心,下面会详细讲解这其中的细节。
下文会以自下而上的方式来讲解Binder框架。自下而上未必是最好的方法,每个人的思考方式不一样,如果你更喜欢自上而下的理解,你也按这样的顺序来阅读。
对于大部分人来说,我们可能需要反复的查阅才能完全理解。
驱动层
源码路径(这部分代码不在AOSP中,而是位于Linux内核代码中):
/kernel/drivers/android/binder.c
/kernel/drivers/android/binder_internal.h
/kernel/include/uapi/linux/android/binder.h
Binder机制的实现中,最核心的就是Binder驱动。 Binder是一个miscellaneous类型的驱动,本身不对应任何硬件,所有的操作都在软件层。 <font color='red'>binder_init </font>函数负责Binder驱动的初始化工作,该函数中大部分代码是在通过<font color='red'> debugfs_create_dir </font>和<font color='red'> debugfs_create_file</font>函数创建debugfs对应的文件。 如果内核在编译时打开了debugfs,则通过adb shell连上设备之后,可以在设备的这个路径找到debugfs对应的文件:<font color='red'> /sys/kernel/debug </font>。Binder驱动中创建的debug文件如下所示:
```c
jobs:/ # ls -l /sys/kernel/debug/binder/
total 0
-r--r--r-- 1 root root 0 1970-01-01 08:00 failed_transaction_log
drwxr-xr-x 2 root root 0 2021-02-02 16:44 proc
-r--r--r-- 1 root root 0 1970-01-01 08:00 state
-r--r--r-- 1 root root 0 1970-01-01 08:00 stats
-r--r--r-- 1 root root 0 1970-01-01 08:00 transaction_log
-r--r--r-- 1 root root 0 1970-01-01 08:00 transactions
这些文件其实都在内存中的,实时的反应了当前Binder的使用情况,在实际的开发过程中,这些信息可以帮忙分析问题。例如,可以通过查看 /sys/kernel/debug/binder/proc 目录来确定哪些进程正在使用Binder,通过查看transaction_log和transactions文件来确定Binder通信的数据。
init_binder_device函数中最主要的工作其实下面这行:
ret = misc_register(&binder_device->miscdev);
该行代码真正向内核中注册了Binder设备。binder_miscdev的定义如下:
binder_device->miscdev.fops = &binder_fops;
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
binder_device->miscdev.name = name;
这里指定了Binder设备的名称是“binder”。这样,在用户空间便可以通过对/dev/binder文件进行操作来使用Binder。
binder_miscdev同时也指定了该设备的fops。fops是另外一个结构体,这个结构中包含了一系列的函数指针,其定义如下:
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
这里除了owner之外,每一个字段都是一个函数指针,这些函数指针对应了用户空间在使用Binder设备时的操作。例如:binder_poll对应了poll系统调用的处理,binder_mmap对应了mmap系统调用的处理,其他类同。
这其中,有三个函数尤为重要,它们是:binder_open,binder_mmap和binder_ioctl。 这是因为,需要使用Binder的进程,几乎总是先通过binder_open打开Binder设备,然后通过binder_mmap进行内存映射。
在这之后,通过binder_ioctl来进行实际的操作。Client对于Server端的请求,以及Server对于Client请求结果的返回,都是通过ioctl完成的。
这里提到的流程如下图所示:
主要结构
Binder驱动中包含了很多的结构体。为了便于下文讲解,这里我们先对这些结构体做一些介绍。
驱动中的结构体可以分为两类:
一类是与用户空间共用的,这些结构体在Binder通信协议过程中会用到。因此,这些结构体定义在binder.h中,包括:
结构体名称 | 说明 |
---|---|
flat_binder_object | 描述在Binder IPC中传递的对象,见下文 |
binder_write_read | 存储一次读写操作的数据 |
binder_version | 存储Binder的版本号 |
transaction_flags | 描述事务的flag,例如是否是异步请求,是否支持fd |
binder_transaction_data | 存储一次事务的数据 |
binder_ptr_cookie | 包含了一个指针和一个cookie |
binder_handle_cookie | 包含了一个句柄和一个cookie |
binder_pri_desc | 暂未用到 |
binder_pri_ptr_cookie | 暂未用到 |
这其中,binder_write_read和binder_transaction_data这两个结构体最为重要,它们存储了IPC调用过程中的数据。关于这一点,我们在下文中会讲解。
Binder驱动中,有一类结构体是定义在binder_internal.h中,包括:
结构体名称 | 说明 |
---|---|
binder_node | 描述Binder实体节点,即:对应了一个Server |
binder_ref | 描述对于Binder实体的引用 |
binder_proc | 描述使用Binder的进程 |
binder_thread | 描述使用Binder的线程 |
binder_work | 描述通信过程中的一项任务 |
binder_ref_death | 描述Binder实体死亡的信息 |
binder_transaction_log | debugfs日志 |
binder_transaction_log_entry | debugfs日志条目 |
有一类结构体是定义在binder_alloc.h中
结构体名称 | 说明 |
---|---|
binder_buffer | 描述Binder通信过程中存储数据的Buffer |
有一类结构体是定义在binder.c中
结构体名称 | 说明 |
---|---|
binder_deferred_state | 描述延迟任务 |
Binder 实体在驱动中的表述
驱动中的Binder实体也叫‘节点’,隶属于提供实体的进程,由struct binder_node结构来表示:
成员```````````````````````````````````````` | 含义 |
---|---|
struct binder_work work; | 当本节点引用计数发生改变,需要通知所属进程时,通过该成员挂入所属进程的to-do队列里,唤醒所属进程执行Binder实体引用计数的修改。 |
union {
struct rb_node rb_node; struct hlist_node dead_node; }; |
每个进程都维护一棵红黑树,以Binder实体在用户空间的指针,即本结构的ptr成员为索引存放该进程所有的Binder实体。这样驱动可以根据Binder实体在用户空间的指针很快找到其位于内核的节点。rb_node用于将本节点链入该红黑树中。 销毁节点时须将rb_node从红黑树中摘除,但如果本节点还有引用没有切断,就用dead_node将节点隔离到另一个链表中,直到通知所有进程切断与该节点的引用后,该节点才可能被销毁。 |
struct binder_proc *proc; | 本成员指向节点所属的进程,即提供该节点的进程。 |
struct hlist_head refs; | 本成员是队列头,所有指向本节点的引用都链接在该队列里。这些引用可能隶属于不同的进程。通过该队列可以遍历指向该节点的所有引用。 |
int internal_strong_refs; | 用以实现强指针的计数器:产生一个指向本节点的强引用该计数就会加1。 |
int local_weak_refs; | 驱动为传输中的Binder设置的弱引用计数。如果一个Binder打包在数据包中从一个进程发送到另一个进程,驱动会为该Binder增加引用计数,直到接收进程通过BC_FREE_BUFFER通知驱动释放该数据包的数据区为止。 |
int local_strong_refs; | 驱动为传输中的Binder设置的强引用计数。 |
void __user *ptr; | 指向用户空间Binder实体的指针,来自于flat_binder_object的binder成员 |
void __user *cookie; | 指向用户空间的附加指针,来自于flat_binder_object的cookie成员 |
unsigned has_strong_ref; unsigned pending_strong_ref; unsigned has_weak_ref; unsigned pending_weak_ref |
这一组标志用于控制驱动与Binder实体所在进程交互式修改引用计数 |
unsigned has_async_transaction; | 该成员表明该节点在to-do队列中有异步交互尚未完成。驱动将所有发送往接收端的数据包暂存在接收进程或线程开辟的to-do队列里。对于异步交互,驱动做了适当流控:如果to-do队列里有异步交互尚待处理则该成员置1,这将导致新到的异步交互存放在本结构成员 – asynch_todo队列中,而不直接送到to-do队列里。目的是为同步交互让路,避免长时间阻塞发送端。 |
unsigned accept_fds | 表明节点是否同意接受文件方式的Binder,来自flat_binder_object中flags成员的FLAT_BINDER_FLAG_ACCEPTS_FDS位。由于接收文件Binder会为进程自动打开一个文件,占用有限的文件描述符,节点可以设置该位拒绝这种行为。 |
int min_priority | 设置处理Binder请求的线程的最低优先级。发送线程将数据提交给接收线程处理时,驱动会将发送线程的优先级也赋予接收线程,使得数据即使跨了进程也能以同样优先级得到处理。不过如果发送线程优先级过低,接收线程将以预设的最小值运行。 该域的值来自于flat_binder_object中flags成员。 |
struct list_head async_todo | 异步交互等待队列;用于分流发往本节点的异步交互包 |
每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个新节点添加到树中。由于对于同一个进程来说内存地址是唯一的,所以不会重复建设造成混乱。
Binder 引用在驱动中的表述
和实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,用struct binder_ref结构体表示:
成员```````````````````````````````````````` | 含义 |
---|---|
struct rb_node rb_node_desc; | 每个进程有一棵红黑树,进程所有引用以引用号(即本结构的desc域)为索引添入该树中。本成员用做链接到该树的一个节点。 |
struct rb_node rb_node_node; | 每个进程又有一棵红黑树,进程所有引用以节点实体在驱动中的内存地址(即本结构的node域)为所引添入该树中。本成员用做链接到该树的一个节点。 |
struct hlist_node node_entry; | 该域将本引用做为节点链入所指向的Binder实体结构binder_node中的refs队列 |
struct binder_proc *proc; | 本引用所属的进程 |
struct binder_node *node; | 本引用所指向的节点(Binder实体) |
uint32_t desc; | 本结构的引用号 |
int strong; | 强引用计数 |
int weak; | 弱引用计数 |
struct binder_ref_death *death; | 应用程序向驱动发送BC_REQUEST_DEATH_NOTIFICATION或BC_CLEAR_DEATH_NOTIFICATION命令从而当Binder实体销毁时能够收到来自驱动的提醒。该域不为空表明用户订阅了对应实体销毁的‘噩耗’。 |
就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存放所有正在使用的引用。不同的是Binder的引用可以通过两个键值索引:
-
对应实体在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。实体在内核中的地址是唯一的,用做索引不会产生二义性;但实体可能来自不同用户进程,而实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。
-
引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号将返回给应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都固定保留给SMgr,其它值由驱动动态分配。向Binder发送数据包时,应用程序将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信息,实现数据包的路由。
通过desc获取binder ref引用,先去找,找到就返回,找不到就创建
static struct binder_ref *binder_get_ref_for_node_olocked(
struct binder_proc *proc,
struct binder_node *node,
struct binder_ref *new_ref)
{
struct binder_context *context = proc->context;
struct rb_node **p = &proc->refs_by_node.rb_node;
struct rb_node *parent = NULL;
struct binder_ref *ref;
struct rb_node *n;
//先去找,找到就返回
while (*p) {
parent = *p;
ref = rb_entry(parent, struct binder_ref, rb_node_node);
if (node < ref->node)
p = &(*p)->rb_left;
else if (node > ref->node)
p = &(*p)->rb_right;
else
return ref;
}
if (!new_ref)
return NULL;
binder_stats_created(BINDER_STAT_REF);
new_ref->data.debug_id = atomic_inc_return(&binder_last_id);
new_ref->proc = proc;
new_ref->node = node;
rb_link_node(&new_ref->rb_node_node, parent, p);
rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node);
new_ref->data.desc = (node == context->binder_context_mgr_node) ? 0 : 1;
for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) {
ref = rb_entry(n, struct binder_ref, rb_node_desc);
if (ref->data.desc > new_ref->data.desc)
break;
new_ref->data.desc = ref->data.desc + 1;
}
p = &proc->refs_by_desc.rb_node;
while (*p) {
parent = *p;
ref = rb_entry(parent, struct binder_ref, rb_node_desc);
if (new_ref->data