走进Android Binder机制(驱动篇上)

版权声明:更多文章还请大家关注我的微信公众号,搜索【Android干货铺】,或者扫描左侧二维码。 https://blog.csdn.net/ming_147/article/details/65633571

由于篇幅限制,驱动篇文章分为上下两章,还请大家注意,此篇是上篇。

Binder的实现是比较复杂的,想要完全弄明白是怎么一回事,并不是一件容易的事情。

这里面牵涉到好几个层次,每一层都有一些模块和机制需要理解。这部分内容预计会分为三篇文章来讲解。本文是第一篇,首先会对整个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

Android系统中对于传统的IPC使用较少(但也有使用,例如:在请求Zygote fork进程的时候使用的是Socket IPC),大部分场景下使用的IPC都是Binder。

Binder相较于传统IPC来说更适合于Android系统,具体原因的包括如下三点:

  1. Binder本身是C/S架构的,这一点更符合Android系统的架构

  2. 性能上更有优势:管道,消息队列,Socket的通讯都需要两次数据拷贝,而Binder只需要一次。要知道,对于系统底层的IPC形式,少一次数据拷贝,对整体性能的影响是非常之大的。

  3. 安全性更好:传统IPC形式,无法得到对方的身份标识(UID/GID),而在使用Binder IPC时,这些身份标示是跟随调用过程而自动传递的。Server端很容易就可以知道Client端的身份,非常便于做安全检查。

整体架构

Binder整体架构如下所示:


从图中可以看出,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/include/uapi/linux/android/binder.h

或者

/kernel/drivers/staging/android/binder.c
/kernel/drivers/staging/android/uapi/binder.h

Binder机制的实现中,最核心的就是Binder驱动。 Binder是一个miscellaneous类型的驱动,本身不对应任何硬件,所有的操作都在软件层。 binder_init函数负责Binder驱动的初始化工作,该函数中大部分代码是在通过debugfs_create_dirdebugfs_create_file函数创建debugfs对应的文件。 如果内核在编译时打开了debugfs,则通过adb shell连上设备之后,可以在设备的这个路径找到debugfs对应的文件:/sys/kernel/debug。Binder驱动中创建的debug文件如下所示:

# ls -l /sys/kernel/debug/binder/                                     total 0
-r--r--r-- 1 root root 0 1970-01-01 00:00 failed_transaction_log
drwxr-xr-x 2 root root 0 1970-05-09 01:19 proc
-r--r--r-- 1 root root 0 1970-01-01 00:00 state
-r--r--r-- 1 root root 0 1970-01-01 00:00 stats
-r--r--r-- 1 root root 0 1970-01-01 00:00 transaction_log
-r--r--r-- 1 root root 0 1970-01-01 00:00 transactions

这些文件其实都在内存中的,实时的反应了当前Binder的使用情况,在实际的开发过程中,这些信息可以帮忙分析问题。例如,可以通过查看/sys/kernel/debug/binder/proc目录来确定哪些进程正在使用Binder,通过查看transaction_logtransactions文件来确定Binder通信的数据。

binder_init函数中最主要的工作其实下面这行:

ret = misc_register(&binder_miscdev);

该行代码真正向内核中注册了Binder设备。binder_miscdev的定义如下:

static struct miscdevice binder_miscdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "binder",
	.fops = &binder_fops};

这里指定了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 = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,};

这里除了owner之外,每一个字段都是一个函数指针,这些函数指针对应了用户空间在使用Binder设备时的操作。例如:binder_poll对应了poll系统调用的处理,binder_mmap对应了mmap系统调用的处理,其他类同。

这其中,有三个函数尤为重要,它们是:binder_openbinder_mmapbinder_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_readbinder_transaction_data这两个结构体最为重要,它们存储了IPC调用过程中的数据。关于这一点,我们在下文中会讲解。

Binder驱动中,还有一类结构体是仅仅Binder驱动内部实现过程中需要的,它们定义在binder.c中,包括:

结构体名称 说明
binder_node 描述Binder实体节点,即:对应了一个Server
binder_ref 描述对于Binder实体的引用
binder_buffer 描述Binder通信过程中存储数据的Buffer
binder_proc 描述使用Binder的进程
binder_thread 描述使用Binder的线程
binder_work 描述通信过程中的一项任务
binder_transaction 描述一次事务的相关信息
binder_deferred_state 描述延迟任务
binder_ref_death 描述Binder实体死亡的信息
binder_transaction_log debugfs日志
binder_transaction_log_entry debugfs日志条目

这里需要读者关注的结构体已经用加粗做了标注。

Binder协议

Binder协议可以分为控制协议和驱动协议两类。

控制协议是进程通过ioctl(“/dev/binder”) 与Binder设备进行通讯的协议,该协议包含以下几种命令:

命令 说明 参数类型
BINDER_WRITE_READ 读写操作,最常用的命令。IPC过程就是通过这个命令进行数据传递 binder_write_read
BINDER_SET_MAX_THREADS 设置进程支持的最大线程数量 size_t
BINDER_SET_CONTEXT_MGR 设置自身为ServiceManager
BINDER_THREAD_EXIT 通知驱动Binder线程退出
BINDER_VERSION 获取Binder驱动的版本号 binder_version
BINDER_SET_IDLE_PRIORITY 暂未用到 -
BINDER_SET_IDLE_TIMEOUT 暂未用到 -

Binder的驱动协议描述了对于Binder驱动的具体使用过程。驱动协议又可以分为两类:

  • 一类是binder_driver_command_protocol,描述了进程发送给Binder驱动的命令

  • 一类是binder_driver_return_protocol,描述了Binder驱动发送给进程的命令

binder_driver_command_protocol共包含17个命令,分别是:

命令 说明 参数类型
BC_TRANSACTION Binder事务,即:Client对于Server的请求 binder_transaction_data
BC_REPLY 事务的应答,即:Server对于Client的回复 binder_transaction_data
BC_FREE_BUFFER 通知驱动释放Buffer binder_uintptr_t
BC_ACQUIRE 强引用计数+1 __u32
BC_RELEASE 强引用计数-1 __u32
BC_INCREFS 弱引用计数+1 __u32
BC_DECREFS 弱引用计数-1 __u32
BC_ACQUIRE_DONE BR_ACQUIRE的回复 binder_ptr_cookie
BC_INCREFS_DONE BR_INCREFS的回复 binder_ptr_cookie
BC_ENTER_LOOPER 通知驱动主线程ready void
BC_REGISTER_LOOPER 通知驱动子线程ready void
BC_EXIT_LOOPER 通知驱动线程已经退出 void
BC_REQUEST_DEATH_NOTIFICATION 请求接收死亡通知 binder_handle_cookie
BC_CLEAR_DEATH_NOTIFICATION 去除接收死亡通知 binder_handle_cookie
BC_DEAD_BINDER_DONE 已经处理完死亡通知 binder_uintptr_t
BC_ATTEMPT_ACQUIRE 暂未实现 -
BC_ACQUIRE_RESULT 暂未实现 -

binder_driver_return_protocol共包含18个命令,分别是:

返回类型 说明 参数类型
BR_OK 操作完成 void
BR_NOOP 操作完成 void
BR_ERROR 发生错误 __s32
BR_TRANSACTION 通知进程收到一次Binder请求(Server端) binder_transaction_data
BR_REPLY 通知进程收到Binder请求的回复(Client) binder_transaction_data
BR_TRANSACTION_COMPLETE 驱动对于接受请求的确认回复 void
BR_FAILED_REPLY 告知发送方通信目标不存在 void
BR_SPAWN_LOOPER 通知Binder进程创建一个新的线程 void
BR_ACQUIRE 强引用计数+1请求 binder_ptr_cookie
BR_RELEASE 强引用计数-1请求 binder_ptr_cookie
BR_INCREFS 弱引用计数+1请求 binder_ptr_cookie
BR_DECREFS 若引用计数-1请求 binder_ptr_cookie
BR_DEAD_BINDER 发送死亡通知 binder_uintptr_t
BR_CLEAR_DEATH_NOTIFICATION_DONE 清理死亡通知完成 binder_uintptr_t
BR_DEAD_REPLY 告知发送方对方已经死亡 void
BR_ACQUIRE_RESULT 暂未实现 -
BR_ATTEMPT_ACQUIRE 暂未实现 -
BR_FINISHED 暂未实现 -

单独看上面的协议可能很难理解,这里我们以一次Binder请求过程来详细看一下Binder协议是如何通信的,就比较好理解了。

这幅图的说明如下:

  • Binder是C/S架构的,通信过程牵涉到:Client,Server以及Binder驱动三个角色

  • Client对于Server的请求以及Server对于Client回复都需要通过Binder驱动来中转数据

  • BC_XXX命令是进程发送给驱动的命令

  • BR_XXX命令是驱动发送给进程的命令

  • 整个通信过程由Binder驱动控制


这里再补充说明一下,通过上面的Binder协议的说明中我们看到,Binder协议的通信过程中,不仅仅是发送请求和接受数据这些命令。同时包括了对于引用计数的管理和对于死亡通知的管理(告知一方,通讯的另外一方已经死亡)等功能。

这些功能的通信过程和上面这幅图是类似的:一方发送BC_XXX,然后由驱动控制通信过程,接着发送对应的BR_XXX命令给通信的另外一方。因为这种相似性,对于这些内容就不再赘述了。

在有了上面这些背景知识介绍之后,我们就可以进入到Binder驱动的内部实现中来一探究竟了。

PS:上面介绍的这些结构体和协议,因为内容较多,初次看完记不住是很正常的,在下文详细讲解的时候,回过头来对照这些表格来理解是比较有帮助的。

打开Binder设备

任何进程在使用Binder之前,都需要先通过open("/dev/binder")打开Binder设备。上文已经提到,用户空间的open系统调用对应了驱动中的binder_open函数。在这个函数,Binder驱动会为调用的进程做一些初始化工作。binder_open函数代码如下所示:

static int binder_open(struct inode *nodp, struct file *filp){
	struct binder_proc *proc;

   // 创建进程对应的binder_proc对象	proc = kzalloc(sizeof(*proc), GFP_KERNEL); 
	if (proc == NULL)
		return -ENOMEM;
	get_task_struct(current);
	proc->tsk = current;
	// 初始化binder_proc	INIT_LIST_HEAD(&proc->todo);
	init_waitqueue_head(&proc->wait);
	proc->default_priority = task_nice(current);

  // 锁保护	binder_lock(__func__);

	binder_stats_created(BINDER_STAT_PROC);
	// 添加到全局列表binder_procs中	hlist_add_head(&proc->proc_node, &binder_procs);
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	filp->private_data = proc;

	binder_unlock(__func__);

	return 0;}

在Binder驱动中,通过binder_procs记录了所有使用Binder的进程。每个初次打开Binder设备的进程都会被添加到这个列表中的。

另外,请读者回顾一下上文介绍的Binder驱动中的几个关键结构体:

  • binder_proc

  • binder_node

  • binder_thread

  • binder_ref

  • binder_buffer

在实现过程中,为了便于查找,这些结构体互相之间都留有字段存储关联的结构。

下面这幅图描述了这里说到的这些内容:



展开阅读全文

没有更多推荐了,返回首页