Android跨进程通信二

1. Binder是什么

从机制上来说: Binder是Android中一种跨进程通信的方式

从Binder机制实现来说: Binder是一种虚拟的物理设备驱动

从Java层面来说: Binder是一个类,实现了IBinder接口。

2. Binder机制

2.1 背景知识

2.1.1 进程空间划分

一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来
二者区别:

进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间 进程间,
内核空间的数据可共享,所以内核空间 = 可共享空间
所有进程共用1个内核空间

进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:

  1. copy_from_user():将用户空间的数据拷贝到内核空间
  2. copy_to_user():将内核空间的数据拷贝到用户空间

2.1.2 传统跨进程通信原理

跨进程通信原理图
简易的流程如下

  1. 进程A将要发送的数据放到数据缓冲区中

  2. 通过系统调用copy_from_user,将数据从发送端的数据缓冲区拷贝到内核缓冲区

  3. 通过系统调用copy_to_user,将数据充内核缓冲区中copy到接收端的数据缓冲区中

这样的IPC发生两次数据拷贝。
接收端的数据缓冲区不确定开辟多大才合适。 要么开辟较大的空间,要么先请求一次API获取数据的大小。前者浪费空间,后者浪费时间

2.1.3 Linux中IPC的方式

目前linux支持的IPC包括管道,System V IPC,消息队列/共享内存/信号量,以及socket

Android是基于Linux的,为何不直接采用Linux已经存在的IPC方式,而要自己实现呢?

  1. Android中多用Client-Server方式来通信,传统Linux的IPC方式中,只有socket是基于Client-Server方式的

  2. 传输性能上面,Socket是通用接口,传输性能低,开销大,主要用于跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。

IPC数据拷贝次数
共享内存0
Binder1
Socket/管道/消息队列2
  1. 安全性能上面,统IPC没有任何安全措施,完全依赖上层协议来确保。其次传统IPC访问接入点是开放的,无法建立私有通道。

基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。

2.2 Binder机制原理与架构

2.2.1 Binder机制原理

Binder机制的原理

1.首先 Binder 驱动在内核空间创建一个数据接收缓存区;
2.接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
3.发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

2.2.2 Binder机制的架构

Binder机制的架构
Binder机制中四个角色分别是 Client、Server、ServiceManager、Binder驱动。其中Client和Server是开发者实现的,ServiceManager和Binder驱动是由系统提供。Client、Server、ServiceManager运行在用户空间,而Binder驱动运行在内核空间。

2.2.3 Binder机制的通信流程

访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 http://www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了 http://www.google.com 对应的 ip 地址 10.249.23.13,然后通过这个 ip 地址才能放到到 http://www.google.com 对应的服务器。访问网页的流程

在Binder架构中,Binder驱动类似于路由器,负责Client端、Server端、ServiceManager之间的交互。ServiceManager类似于DNS服务器,提供用名字获取服务引用的功能。

Binder机制的通信流程

一次完整的通信流程包含以下几个步骤

  1. Server进程注册服务
  2. Client进程查询服务
  3. Client进程得到服务
  4. Client进程使用服务
  5. Server进程返回结果

2.3 Binder机制的总结

Binder机制的总结

3. Binder机制的实现

先看一眼整体的架构图,后文会从下到上的顺序分析这个架构。
Binder机制实现整体架构

从图中可以看出,Binder的实现分为这么几层:

  • Framework层
    • Java部分
    • JNI部分
    • C++部分
  • 驱动层

驱动层位于Linux内核中,它提供了最底层的数据传递,对象标识,线程管理,调用过程控制等功能。驱动层是整个Binder机制的核心。
Framework层以驱动层为基础,提供了应用开发的基础设施。
Framework层既包含了C++部分的实现,也包含了Java部分的实现。为了能将C++的实现复用到Java端,中间通过JNI进行衔接。
既然是“进程间”通讯就至少牵涉到两个进程,Binder框架是典型的C/S架构。在下文中,我们把服务的请求方称之为Client,服务的实现方称之为Server。
Client对于Server的请求会经由Binder框架由上至下传递到内核的Binder驱动中,请求中包含了Client将要调用的命令和参数。请求到了Binder驱动之后,在确定了服务的提供方之后,会再从下至上将请求传递给具体的服务。整个调用过程如下图所示:

Binder调用过程

3.1 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_dir和debugfs_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_log和transactions文件来确定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_open,binder_mmap和binder_ioctl。 这是因为,需要使用Binder的进程,几乎总是先通过binder_open打开Binder设备,然后通过binder_mmap进行内存映射。
在这之后,通过binder_ioctl来进行实际的操作。Client对于Server端的请求,以及Server对于Client请求结果的返回,都是通过ioctl完成的。这里提到的流程如下图所示:
在这里插入图片描述

3.1.1 主要结构

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驱动内部实现过程中需要的,它们定义在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_logdebugfs日志
binder_transaction_log_entrydebugfs日志条目

3.1.2 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通知驱动释放Bufferbinder_uintptr_t
BC_ACQUIRE强引用计数+1__u32
BC_RELEASE强引用计数-1__u32
BC_INCREFS弱引用计数+1__u32
BC_DECREFS弱引用计数-1__u32
BC_ACQUIRE_DONEBR_ACQUIRE的回复binder_ptr_cookie
BC_INCREFS_DONEBR_INCREFS的回复binder_ptr_cookie
BC_ENTER_LOOPER通知驱动主线程readyvoid
BC_REGISTER_LOOPER通知驱动子线程readyvoid
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:上面介绍的这些结构体和协议,因为内容较多,初次看完记不住是很正常的,在下文详细讲解的时候,回过头来对照这些表格来理解是比较有帮助的。

3.1.3 打开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

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

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

3.1.4 内存映射(mmap)

在打开Binder设备之后,进程还会通过mmap进行内存映射。mmap的作用有如下两个:

  • 申请一块内存空间,用来接收Binder通信过程中的数据
  • 对这块内存进行地址映射,以便将来访问

binder_mmap函数对应了mmap系统调用的处理,这个函数也是Binder驱动的精华所在(这里说的binder_mmap函数也包括其内部调用的binder_update_page_range函数,见下文)。

前文我们说到,使用Binder机制,数据只需要经历一次拷贝就可以了,其原理就在这个函数中。

binder_mmap这个函数中,会申请一块物理内存,然后在用户空间和内核空间同时对应到这块内存上。在这之后,当有Client要发送数据给Server的时候,只需一次,将Client发送过来的数据拷贝到Server端的内核空间指定的内存地址即可,由于这个内存地址在服务端已经同时映射到用户空间,因此无需再做一次复制,Server即可直接访问,整个过程如下图所示:
在这里插入图片描述

这幅图的说明如下:

  • Server在启动之后,调用对/dev/binder设备调用mmap
  • 内核中的binder_mmap函数进行对应的处理:申请一块物理内存,然后在用户空间和内核空间同时进行映射
  • Client通过BINDER_WRITE_READ命令发送请求,这个请求将先到驱动中,同时需要将数据从Client进程的用户空间拷贝到内核空间
  • 驱动通过BR_TRANSACTION通知Server有人发出请求,Server进行处理。由于这块内存也在用户空间进行了映射,因此Server进程的代码可以直接访问

了解原理之后,我们再来看一下Binder驱动的相关源码。这段代码有两个函数:

  • binder_mmap函数对应了mmap的系统调用的处理
  • binder_update_page_range函数真正实现了内存分配和地址映射
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;

	struct vm_struct *area;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;

	...
   // 在内核空间获取一块地址范围
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
	if (area == NULL) {
		ret = -ENOMEM;
		failure_string = "get_vm_area";
		goto err_get_vm_area_failed;
	}
	proc->buffer = area->addr;
	// 记录内核空间与用户空间的地址偏移
	proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
	mutex_unlock(&binder_mmap_lock);

  ...
	proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
	if (proc->pages == NULL) {
		ret = -ENOMEM;
		failure_string = "alloc page array";
		goto err_alloc_pages_failed;
	}
	proc->buffer_size = vma->vm_end - vma->vm_start;

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;

	/* binder_update_page_range assumes preemption is disabled */
	preempt_disable();
	// 通过下面这个函数真正完成内存的申请和地址的映射
	// 初次使用,先申请一个PAGE_SIZE大小的内存
	ret = binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma);
	...
}

static int binder_update_page_range(struct binder_proc *proc, int allocate,
				    void *start, void *end,
				    struct vm_area_struct *vma)
{
	void *page_addr;
	unsigned long user_page_addr;
	struct vm_struct tmp_area;
	struct page **page;
	struct mm_struct *mm;

	...

	for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
		int ret;
		struct page **page_array_ptr;
		page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];

		BUG_ON(*page);
		// 真正进行内存的分配
		*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
		if (*page == NULL) {
			pr_err("%d: binder_alloc_buf failed for page at %p\n",
				proc->pid, page_addr);
			goto err_alloc_page_failed;
		}
		tmp_area.addr = page_addr;
		tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
		page_array_ptr = page;
		// 在内核空间进行内存映射
		ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
		if (ret) {
			pr_err("%d: binder_alloc_buf failed to map page at %p in kernel\n",
			       proc->pid, page_addr);
			goto err_map_kernel_failed;
		}
		user_page_addr =
			(uintptr_t)page_addr + proc->user_buffer_offset;
		// 在用户空间进行内存映射
		ret = vm_insert_page(vma, user_page_addr, page[0]);
		if (ret) {
			pr_err("%d: binder_alloc_buf failed to map page at %lx in userspace\n",
			       proc->pid, user_page_addr);
			goto err_vm_insert_page_failed;
		}
		/* vm_insert_page does not seem to increment the refcount */
	}
	if (mm) {
		up_write(&mm->mmap_sem);
		mmput(mm);
	}

	preempt_disable();

	return 0;
...

在开发过程中,我们可以通过procfs看到进程映射的这块内存空间:

  • 将Android设备连接到电脑上之后,通过adb shell进入到终端
  • 然后选择一个使用了Binder的进程,例如system_server(这是系统中一个非常重要的进程,下一章我们会专门讲解),通过 ps | grep system_server来确定进程号,例如是1889
  • 通过 cat /proc/[pid]/maps | grep “/dev/binder” 过滤出这块内存的地址
    在我的Nexus 6P上,控制台输出如下:
angler:/ # ps  | grep system_server
system    1889  526   2353404 140016 SyS_epoll_ 72972eeaf4 S system_server
angler:/ # cat /proc/1889/maps | grep "/dev/binder"
7294761000-729485f000 r--p 00000000 00:0c 12593                          /dev/binder

PS:grep是通过通配符进行匹配过滤的命令,“|”是Unix上的管道命令。即将前一个命令的输出给下一个命令作为输入。如果这里我们不加“ | grep xxx”,那么将看到前一个命令的完整输出。

3.1.5 内存的管理

上文中,我们看到binder_mmap的时候,会申请一个PAGE_SIZE通常是4K的内存。而实际使用过程中,一个PAGE_SIZE的大小通常是不够的。

在驱动中,会根据实际的使用情况进行内存的分配。有内存的分配,当然也需要内存的释放。这里我们就来看看Binder驱动中是如何进行内存的管理的。

首先,我们还是从一次IPC请求说起。

当一个Client想要对Server发出请求时,它首先将请求发送到Binder设备上,由Binder驱动根据请求的信息找到对应的目标节点,然后将请求数据传递过去。

进程通过ioctl系统调用来发出请求:ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)

PS:这行代码来自于Framework层的IPCThreadState类。在后文中,我们将看到,IPCThreadState类专门负责与驱动进行通信。

这里的mProcess->mDriverFD对应了打开Binder设备时的fd。BINDER_WRITE_READ对应了具体要做的操作码,这个操作码将由Binder驱动解析。bwr存储了请求数据,其类型是binder_write_read。

binder_write_read其实是一个相对外层的数据结构,其内部会包含一个binder_transaction_data结构的数据。binder_transaction_data包含了发出请求者的标识,请求的目标对象以及请求所需要的参数。它们的关系如下图所示:

在这里插入图片描述

binder_ioctl函数对应了ioctl系统调用的处理。这个函数的逻辑比较简单,就是根据ioctl的命令来确定进一步处理的逻辑,具体如下:

  • 如果命令是BINDER_WRITE_READ,并且
  • 如果 bwr.write_size > 0,则调用binder_thread_write
  • 如果 bwr.read_size > 0,则调用binder_thread_read
  • 如果命令是BINDER_SET_MAX_THREADS,则设置进程的max_threads,即进程支持的最大线程数
  • 如果命令是BINDER_SET_CONTEXT_MGR,则设置当前进程为ServiceManager,见下文
  • 如果命令是BINDER_THREAD_EXIT,则调用binder_free_thread,释放binder_thread
  • 如果命令是BINDER_VERSION,则返回当前的Binder版本号

这其中,最关键的就是binder_thread_write方法。当Client请求Server的时候,便会发送一个BINDER_WRITE_READ命令,同时框架会将将实际的数据包装好。此时,binder_transaction_data中的code将是BC_TRANSACTION,由此便会调用到binder_transaction方法,这个方法是对一次Binder事务的处理,这其中会调用binder_alloc_buf函数为此次事务申请一个缓存。这里提到到调用关系如下:
在这里插入图片描述

binder_update_page_range这个函数在上文中,我们已经看到过了。其作用就是:进行内存分配并且完成内存的映射。而binder_alloc_buf函数,正如其名称那样的:完成缓存的分配。

在驱动中,通过binder_buffer结构体描述缓存。一次Binder事务就会对应一个binder_buffer,其结构如下所示:

struct binder_buffer {
	struct list_head entry;
	struct rb_node rb_node;

	unsigned free:1;
	unsigned allow_user_free:1;
	unsigned async_transaction:1;
	unsigned debug_id:29;

	struct binder_transaction *transaction;

	struct binder_node *target_node;
	size_t data_size;
	size_t offsets_size;
	uint8_t data[0];
};

而在binder_proc(描述了使用Binder的进程)中,包含了几个字段用来管理进程在Binder IPC过程中缓存,如下:

struct binder_proc {
	...
	struct list_head buffers; // 进程拥有的buffer列表
	struct rb_root free_buffers; // 空闲buffer列表
	struct rb_root allocated_buffers; // 已使用的buffer列表
	size_t free_async_space; // 剩余的异步调用的空间

	size_t buffer_size; // 缓存的上限
  ...
};

进程在mmap时,会设定支持的总缓存大小的上限(下文会讲到)。而进程每当收到BC_TRANSACTION,就会判断已使用缓存加本次申请的和有没有超过上限。如果没有,就考虑进行内存的分配。

进程的空闲缓存记录在binder_proc的free_buffers中,这是一个以红黑树形式存储的结构。每次尝试分配缓存的时候,会从这里面按大小顺序进行查找,找到最接近需要的一块缓存。查找的逻辑如下:

while (n) {
	buffer = rb_entry(n, struct binder_buffer, rb_node);
	BUG_ON(!buffer->free);
	buffer_size = binder_buffer_size(proc, buffer);

	if (size < buffer_size) {
		best_fit = n;
		n = n->rb_left;
	} else if (size > buffer_size)
		n = n->rb_right;
	else {
		best_fit = n;
		break;
	}
}

找到之后,还需要对binder_proc中的字段进行相应的更新:

rb_erase(best_fit, &proc->free_buffers);
buffer->free = 0;
binder_insert_allocated_buffer(proc, buffer);
if (buffer_size != size) {
	struct binder_buffer *new_buffer = (void *)buffer->data + size;
	list_add(&new_buffer->entry, &buffer->entry);
	new_buffer->free = 1;
	binder_insert_free_buffer(proc, new_buffer);
}
binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
	     "%d: binder_alloc_buf size %zd got %p\n",
	      proc->pid, size, buffer);
buffer->data_size = data_size;
buffer->offsets_size = offsets_size;
buffer->async_transaction = is_async;
if (is_async) {
	proc->free_async_space -= size + sizeof(struct binder_buffer);
	binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
		     "%d: binder_alloc_buf size %zd async free %zd\n",
		      proc->pid, size, proc->free_async_space);
}

下面我们再来看看内存的释放。

BC_FREE_BUFFER命令是通知驱动进行内存的释放,binder_free_buf函数是真正实现的逻辑,这个函数与binder_alloc_buf是刚好对应的。在这个函数中,所做的事情包括:

  • 重新计算进程的空闲缓存大小
  • 通过binder_update_page_range释放内存
  • 更新binder_proc的buffers,free_buffers,allocated_buffers字段

3.1.6 Binder中的“面向对象”

Binder机制淡化了进程的边界,使得跨越进程也能够调用到指定服务的方法,其原因是因为Binder机制在底层处理了在进程间的“对象”传递。

在Binder驱动中,并不是真的将对象在进程间来回序列化,而是通过特定的标识来进行对象的传递。Binder驱动中,通过flat_binder_object来描述需要跨越进程传递的对象。其定义如下:

struct flat_binder_object {
	__u32		type;
	__u32		flags;

	union {
		binder_uintptr_t	binder; /* local object */
		__u32			handle;	/* remote object */
	};
	binder_uintptr_t	cookie;
};

这其中,type有如下5种类型。

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_CHARS('f', 'd', '*', B_TYPE_LARGE),
};

当对象传递到Binder驱动中的时候,由驱动来进行翻译和解释,然后传递到接收的进程。
例如当Server把Binder实体传递给Client时,在发送数据流中,flat_binder_object中的type是BINDER_TYPE_BINDER,同时binder字段指向Server进程用户空间地址。但这个地址对于Client进程是没有意义的(Linux中,每个进程的地址空间是互相隔离的),驱动必须对数据流中的flat_binder_object做相应的翻译:将type该成BINDER_TYPE_HANDLE;为这个Binder在接收进程中创建位于内核中的引用并将引用号填入handle中。对于发生数据流中引用类型的Binder也要做同样转换。经过处理后接收进程从数据流中取得的Binder引用才是有效的,才可以将其填入数据包binder_transaction_data的target.handle域,向Binder实体发送请求。

由于每个请求和请求的返回都会经历内核的翻译,因此这个过程从进程的角度来看是完全透明的。进程完全不用感知这个过程,就好像对象真的在进程间来回传递一样。

3.1.7 驱动层的线程管理

上文多次提到,Binder本身是C/S架构。由Server提供服务,被Client使用。既然是C/S架构,就可能存在多个Client会同时访问Server的情况。 在这种情况下,如果Server只有一个线程处理响应,就会导致客户端的请求可能需要排队而导致响应过慢的现象发生。解决这个问题的方法就是引入多线程。

Binder机制的设计从最底层–驱动层,就考虑到了对于多线程的支持。具体内容如下:

  • 使用Binder的进程在启动之后,通过BINDER_SET_MAX_THREADS告知驱动其支持的最大线程数量
  • 驱动会对线程进行管理。在binder_proc结构中,这些字段记录了进程中线程的信息:max_threads,requested_threads,requested_threads_started,ready_threads
  • binder_thread结构对应了Binder进程中的线程
  • 驱动通过BR_SPAWN_LOOPER命令告知进程需要创建一个新的线程
  • 进程通过BC_ENTER_LOOPER命令告知驱动其主线程已经ready
  • 进程通过BC_REGISTER_LOOPER命令告知驱动其子线程(非主线程)已经ready
  • 进程通过BC_EXIT_LOOPER命令告知驱动其线程将要退出
  • 在线程退出之后,通过BINDER_THREAD_EXIT告知Binder驱动。驱动将对应的binder_thread对象销毁

3.1.8 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 Server在驱动中会有一个binder_node进行对应。同时,Binder驱动会负责在进程间传递服务对象,并负责底层的转换。另外,我们也提到,每一个Binder服务都需要有一个唯一的名称。由ServiceManager来管理这些服务的注册和查找。

而实际上,为了便于使用,ServiceManager本身也实现为一个Server对象。任何进程在使用ServiceManager的时候,都需要先拿到指向它的标识。然后通过这个标识来使用ServiceManager。

这似乎形成了一个互相矛盾的现象:

  • 通过ServiceManager我们才能拿到Server的标识
  • ServiceManager本身也是一个Server
    解决这个矛盾的办法其实也很简单:Binder机制为ServiceManager预留了一个特殊的位置。这个位置是预先定好的,任何想要使用ServiceManager的进程只要通过这个特定的位置就可以访问到ServiceManager了(而不用再通过ServiceManager的接口)。

在Binder驱动中,有一个全局的变量:

static struct binder_node *binder_context_mgr_node;

这个变量指向的就是ServiceManager。

当有进程通过ioctl并指定命令为BINDER_SET_CONTEXT_MGR的时候,驱动被认定这个进程是ServiceManager,binder_ioctl函数中对应的处理如下:

case BINDER_SET_CONTEXT_MGR:
	if (binder_context_mgr_node != NULL) {
		pr_err("BINDER_SET_CONTEXT_MGR already set\n");
		ret = -EBUSY;
		goto err;
	}
	ret = security_binder_set_context_mgr(proc->tsk);
	if (ret < 0)
		goto err;
	if (uid_valid(binder_context_mgr_uid)) {
		if (!uid_eq(binder_context_mgr_uid, current->cred->euid)) {
			pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
			       from_kuid(&init_user_ns, current->cred->euid),
			       from_kuid(&init_user_ns, binder_context_mgr_uid));
			ret = -EPERM;
			goto err;
		}
	} else
		binder_context_mgr_uid = current->cred->euid;
	binder_context_mgr_node = binder_new_node(proc, 0, 0);
	if (binder_context_mgr_node == NULL) {
		ret = -ENOMEM;
		goto err;
	}
	binder_context_mgr_node->local_weak_refs++;
	binder_context_mgr_node->local_strong_refs++;
	binder_context_mgr_node->has_strong_ref = 1;
	binder_context_mgr_node->has_weak_ref = 1;
	break;

ServiceManager应当要先于所有Binder Server之前启动。在它启动完成并告知Binder驱动之后,驱动便设定好了这个特定的节点。

在这之后,当有其他模块想要使用ServerManager的时候,只要将请求指向ServiceManager所在的位置即可。

在Binder驱动中,通过handle = 0这个位置来访问ServiceManager。例如,binder_transaction中,判断如果target.handler为0,则认为这个请求是发送给ServiceManager的,相关代码如下:

if (tr->target.handle) {
	struct binder_ref *ref;
	ref = binder_get_ref(proc, tr->target.handle, true);
	if (ref == NULL) {
		binder_user_error("%d:%d got transaction to invalid handle\n",
			proc->pid, thread->pid);
		return_error = BR_FAILED_REPLY;
		goto err_invalid_target_handle;
	}
	target_node = ref->node;
} else {
	target_node = binder_context_mgr_node;
	if (target_node == NULL) {
		return_error = BR_DEAD_REPLY;
		goto err_no_context_mgr_node;
	}
}

3.2 Binder机制的lib C++层实现

3.2.1 关键类

Binder中,将实现分为Proxy和Native两端。Proxy对应了上文提到的Client端,是服务对外提供的接口。而Native是服务实现的一端,对应了上文提到的Server端。类名中带有小写字母p的(例如BpInterface),就是指Proxy端。类名带有小写字母n的(例如BnInterface),就是指Native端。

Proxy代表了调用方,通常与服务的实现不在同一个进程,因此下文中,我们也称Proxy端为“远程”端。Native端是服务实现的自身,因此下文中,我们也称Native端为”本地“端。

这里,我们先对Binder中的主要类做一个简要说明,了解一下它们的关系,然后再详细的讲解。

类名说明
BpRefBaseRefBase的子类,提供remote方法获取远程Binder
IInterfaceBinder服务接口的基类,Binder服务通常需要同时提供本地接口和远程接口
BpInterface远程接口的基类,远程接口是供客户端调用的接口集
BnInterface本地接口的基类,本地接口是需要服务中真正实现的接口集
IBinerBinder对象的基类,BBinder和BpBinder都是这个类的子类
BpBinder远程Binder,这个类提供transact方法来发送请求,BpXXX实现中会用到
BBinder本地Binder,服务实现方的基类,提供了onTransact接口来接收请求
ProcessState代表了使用Binder的进程
IPCThreadState代表了使用Binder的线程,这个类中封装了与Binder驱动通信的逻辑
Parcel在Binder上传递的数据的包装器
下图描述了这些类之间的关系:

另外说明一下,Binder服务的实现类(图中紫色部分)通常都会遵守下面的命名规则:

  • 服务的接口使用I字母作为前缀

  • 远程接口使用Bp作为前缀

  • 本地接口使用Bn作为前缀

  • Service: BnInterface, 即 Binder native interface.

  • Client: BpInterface, 即 Binder proxy interface.

  1. BnInterface
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder 
{
public:
    virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
    virtual cosnt String16& getInterfaceDescriptor() const;
    
protected:
    virtual IBinder* onAsBinder();
}

模板参数 INTERFACE 是一个由进程自定义的 Service 组件接口, 模板类 BnInterface 需要实现该接口
该类由又继承了 BBinder

  1. BBinder
class BBinder : public IBinder
{
public:
    ......
    virtual status_t transact(
        unit32_t code,
        const Parcel& data,
        Parcel* reply,
        unit32_t flag = 0
    );
proctected:
    ......
    virtual status_t onTransact(
        unit32_t code,
        const Parcel& data,
        Parcel* reply,
        unit32_t flag = 0
    )
}

BBinder 类有两个重要的成员函数 transact 和 onTransact

  • transact: 当 Binder 代理对象通过 Binder 驱动程序向一个 Binder 本地对象发出一个进程间的请求时, Binder 驱动程序就会调用该 Binder 本地对象的成员函数 transact 来处理该请求。
  • onTransact: 该方法由 Binder 本地对象来实现, 它负责分发与业务相关的进程间的请求。
class IBinder : public RefBase {
    ......
}

可见 IBinder 类又继承了 RefBase, 也就是说 Binder 本地对象是通过引用计数技术来维护生命周期的。

Binder机制的native层实现

IBinder这个类。这个类描述了所有在Binder上传递的对象,它既是Binder本地对象BBinder的父类,也是Binder远程对象BpBinder的父类。这个类中的主要方法说明如下:

方法名说明
localBinder获取本地Binder对象
remoteBinder获取远程Binder对象
transact进行一次Binder操作
queryLocalInterface尝试获取本地Binder,如何失败返回NULL
getInterfaceDescriptor获取Binder的服务接口描述,其实就是Binder服务的唯一标识
isBinderAlive查询Binder服务是否还活着
pingBinder发送PING_TRANSACTION给Binder服务

BpBinder的实例代表了远程Binder,这个类的对象将被客户端调用。其中handle方法会返回指向Binder服务实现者的句柄,这个类最重要就是提供了transact方法,这个方法会将远程调用的参数封装好发送的Binder驱动。

由于每个Binder服务通常都会提供多个服务接口,而这个方法中的uint32_t code参数就是用来对服务接口进行编号区分的。Binder服务的每个接口都需要指定一个唯一的code,这个code要在Proxy和Native端配对好。当客户端将请求发送到服务端的时候,服务端根据这个code(onTransact方法中)来区分调用哪个接口方法。

BBinder的实例代表了本地Binder,它描述了服务的提供方,所有Binder服务的实现者都要继承这个类(的子类),在继承类中,最重要的就是实现onTransact方法,因为这个方法是所有请求的入口。因此,这个方法是和BpBinder中的transact方法对应的,这个方法同样也有一个uint32_t code参数,在这个方法的实现中,由服务提供者通过code对请求的接口进行区分,然后调用具体实现服务的方法。

IBinder中定义了uint32_t code允许的范围:

FIRST_CALL_TRANSACTION = 0x00000001
LAST_CALL_TRANSACTION = 0x00ffffff,

Binder服务要保证自己提供的每个服务接口有一个唯一的code,例如某个Binder服务可以将:add接口code设为1,minus接口code设为2,multiple接口code设为3,divide接口code设为4,等等。

我们再来看看BpReBase,IInterface,BpInterface和BnInterface。

每个Binder服务都是为了某个功能而实现的,因此其本身会定义一套接口集(通常是C++的一个类)来描述自己提供的所有功能。而Binder服务既有自身实现服务的类,也要有给客户端进程调用的类。为了便于开发,这两中类里面的服务接口应当是一致的,例如:假设服务实现方提供了一个接口为add(int a, int b)的服务方法,那么其远程接口中也应当有一个add(int a, int b)方法。因此为了实现方便,本地实现类和远程接口类需要有一个公共的描述服务接口的基类(即上图中的IXXXService)来继承。而这个基类通常是IInterface的子类,IInterface的定义如下:

class IInterface : public virtual RefBase
{
public:
            IInterface();
            static sp<IBinder>  asBinder(const IInterface*);
            static sp<IBinder>  asBinder(const sp<IInterface>&);

protected:
    virtual                     ~IInterface();
    virtual IBinder*            onAsBinder() = 0;
};

之所以要继承自IInterface类是因为这个类中定义了onAsBinder让子类实现。onAsBinder在本地对象的实现类中返回的是本地对象,在远程对象的实现类中返回的是远程对象。onAsBinder方法被两个静态方法asBinder方法调用。有了这些接口之后,在代码中便可以直接通过IXXX::asBinder方法获取到不用区分本地还是远程的IBinder对象。这个在跨进程传递Binder对象的时候有很大的作用(因为不用区分具体细节,只要直接调用和传递就好)。

下面,我们来看一下BpInterface和BnInterface的定义:

template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;

protected:
    virtual IBinder*            onAsBinder();
};

// ----------------------------------------------------------------------

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);

protected:
    virtual IBinder*            onAsBinder();
};

这两个类都是模板类,它们在继承自INTERFACE的基础上各自继承了另外一个类。这里的INTERFACE便是我们Binder服务接口的基类。另外,BnInterface继承了BBinder类,由此可以通过复写onTransact方法来提供实现。BpInterface继承了BpRefBase,通过这个类的remote方法可以获取到指向服务实现方的句柄。在客户端接口的实现类中,每个接口在组装好参数之后,都会调用remote()->transact来发送请求,而这里其实就是调用的BpBinder的transact方法,这样请求便通过Binder到达了服务实现方的onTransact中。这个过程如下图所示:
交互流程

基于Binder框架开发的服务,除了满足上文提到的类名规则之外,还需要遵守其他一些共同的规约:

为了进行服务的区分,每个Binder服务需要指定一个唯一的标识,这个标识通过getInterfaceDescriptor返回,类型是一个字符串。通常,Binder服务会在类中定义static const android::String16 descriptor;这样一个常量来描述这个标识符,然后在getInterfaceDescriptor方法中返回这个常量。
为了便于调用者获取到调用接口,服务接口的公共基类需要提供一个android::sp asInterface方法来返回基类对象指针。
由于上面提到的这两点对于所有Binder服务的实现逻辑都是类似的。为了简化开发者的重复工作,在libbinder中,定义了两个宏来简化这些重复工作,它们是:

#define DECLARE_META_INTERFACE(INTERFACE)                            \
    static const android::String16 descriptor;                       \
    static android::sp<I##INTERFACE> asInterface(                    \
            const android::sp<android::IBinder>& obj);               \
    virtual const android::String16& getInterfaceDescriptor() const; \
    I##INTERFACE();                                                  \
    virtual ~I##INTERFACE();                                         \


#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                    \
    const android::String16 I##INTERFACE::descriptor(NAME);          \
    const android::String16&                                         \
            I##INTERFACE::getInterfaceDescriptor() const {           \
        return I##INTERFACE::descriptor;                             \
    }                                                                \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(             \
            const android::sp<android::IBinder>& obj)                \
    {                                                                \
        android::sp<I##INTERFACE> intr;                              \
        if (obj != NULL) {                                           \
            intr = static_cast<I##INTERFACE*>(                       \
                obj->queryLocalInterface(                            \
                        I##INTERFACE::descriptor).get());            \
            if (intr == NULL) {                                      \
                intr = new Bp##INTERFACE(obj);                       \
            }                                                        \
        }                                                            \
        return intr;                                                 \
    }                                                                \
    I##INTERFACE::I##INTERFACE() { }                                 \
    I##INTERFACE::~I##INTERFACE() { }                

有了这两个宏之后,开发者只要在接口基类(IXXX)头文件中,使用DECLARE_META_INTERFACE宏便完成了需要的组件的声明。然后在cpp文件中使用IMPLEMENT_META_INTERFACE便完成了这些组件的实现。

3.2.2 binder初始化

任何使用Binder机制的进程都必须要对/dev/binder设备进行open以及mmap之后才能使用,这部分逻辑是所有使用Binder机制进程共同的。对于这种共同逻辑的封装便是Framework层的职责之一。Binder中,ProcessState类封装了这个逻辑,相关代码见下文。

这里是ProcessState构造函数,在这个函数中,初始化mDriverFD的时候调用了open_driver方法打开binder设备,然后又在函数体中,通过mmap进行内存映射。

ProcessState::ProcessState()
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
    , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
    , mExecutingThreadsCount(0)
    , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
    , mStarvationStartTimeMs(0)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
    }

    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
}

open_driver的函数实现如下所示。在这个函数中完成了三个工作:

  • 首先通过open系统调用打开了dev/binder设备
  • 然后通过ioctl获取Binder实现的版本号,并检查是否匹配
  • 最后通过ioctl设置进程支持的最大线程数量
static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
    if (fd >= 0) {
        int vers = 0;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        if (result == -1) {
            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
            close(fd);
            fd = -1;
        }
        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
            ALOGE("Binder driver protocol does not match user space protocol!");
            close(fd);
            fd = -1;
        }
        size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
        if (result == -1) {
            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
        }
    } else {
        ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
    }
    return fd;
}

ProcessState是一个Singleton(单例)类型的类,在一个进程中,只会存在一个实例。通过ProcessState::self()接口获取这个实例。一旦获取这个实例,便会执行其构造函数,由此完成了对于Binder设备的初始化工作。

3.2.3 关于Binder传递数据的大小限制

由于Binder的数据需要跨进程传递,并且还需要在内核上开辟空间,因此允许在Binder上传递的数据并不是无无限大的。mmap中指定的大小便是对数据传递的大小限制:

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) // 1M - 8k

mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);

这里我们看到,在进行mmap的时候,指定了最大size为BINDER_VM_SIZE,即 1M - 8k的大小。 因此我们在开发过程中,一次Binder调用的数据总和不能超过这个大小。

对于这个区域的大小,我们也可以在设备上进行确认。这里我们还之前提到的system_server为例。上面我们讲解了通过procfs来获取映射的内存地址,除此之外,我们也可以通过showmap命令,来确定这块区域的大小,相关命令如下:

angler:/ # ps  | grep system_server
system    1889  526   2353404 135968 SyS_epoll_ 72972eeaf4 S system_server
angler:/ # showmap 1889 | grep "/dev/binder"
    1016        4        4        0        0        4        0        0    1 /dev/binder

这里可以看到,这块区域的大小正是 1M - 8K = 1016k。

Tips: 通过showmap命令可以看到进程的详细内存占用情况。在实际的开发过程中,当我们要对某个进程做内存占用分析的时候,这个命令是相当有用的。建议读者尝试通过showmap命令查看system_server或其他感兴趣进程的完整map,看看这些进程都依赖了哪些库或者模块,以及内存占用情况是怎样的。

3.2.4 与驱动的通信

负责与Binder驱动通信的IPCThreadState也是一个单例类。但这个类不是一个进程只有一个实例,而是一个线程有一个实例。

IPCThreadState负责了与驱动通信的细节处理。这个类中的关键几个方法说明如下:

方法说明
transact公开接口。供Proxy发送数据到驱动,并读取返回结果
sendReply供Server端写回请求的返回结果
waitForResponse发送请求后等待响应结果
talkWithDriver通过ioctl BINDER_WRITE_READ来与驱动通信
writeTransactionData写入一次事务的数据
executeCommand处理binder_driver_return_protocol协议命令
freeBuffer通过BC_FREE_BUFFER命令释放Buffer

BpBinder::transact方法在发送请求的时候,其实就是直接调用了IPCThreadState对应的方法来发送请求到Binder驱动的,相关代码如下:

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }

    return DEAD_OBJECT;
}

而IPCThreadState::transact方法主要逻辑如下:

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();

    flags |= TF_ACCEPT_FDS;

    if (err == NO_ERROR) {
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }

    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }

    if ((flags & TF_ONE_WAY) == 0) {
        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }

    return err;
}

这段代码应该还是比较好理解的:首先通过writeTransactionData写入数据,然后通过waitForResponse等待返回结果。TF_ONE_WAY表示此次请求是单向的,即:不用真正等待结果即可返回。

而writeTransactionData方法其实就是在组装binder_transaction_data数据:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;

    tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;

    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } else if (statusBuffer) {
        tr.flags |= TF_STATUS_CODE;
        *statusBuffer = err;
        tr.data_size = sizeof(status_t);
        tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
        tr.offsets_size = 0;
        tr.data.ptr.offsets = 0;
    } else {
        return (mLastError = err);
    }

    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));

    return NO_ERROR;
}

3.2.5 数据包装器:Parcel

Binder上提供的是跨进程的服务,每个服务包含了不同的接口,每个接口的参数数量和类型都不一样。那么当客户端想要调用服务端的接口,参数是如何跨进程传递给服务端的呢?除此之外,服务端想要给客户端返回结果,结果又是如何传递回来的呢?

这些问题的答案就是:Parcel。Parcel就像一个包装器,调用者可以以任意顺序往里面放入需要的数据,所有写入的数据就像是被打成一个整体的包,然后可以直接在Binde上传输。

Parcel提供了所有基本类型的写入和读出接口,下面是其中的一部分:

...
status_t            writeInt32(int32_t val);
status_t            writeUint32(uint32_t val);
status_t            writeInt64(int64_t val);
status_t            writeUint64(uint64_t val);
status_t            writeFloat(float val);
status_t            writeDouble(double val);
status_t            writeCString(const char* str);
status_t            writeString8(const String8& str);

status_t            readInt32(int32_t *pArg) const;
uint32_t            readUint32() const;
status_t            readUint32(uint32_t *pArg) const;
int64_t             readInt64() const;
status_t            readInt64(int64_t *pArg) const;
uint64_t            readUint64() const;
status_t            readUint64(uint64_t *pArg) const;
float               readFloat() const;
status_t            readFloat(float *pArg) const;
double              readDouble() const;
status_t            readDouble(double *pArg) const;
intptr_t            readIntPtr() const;
status_t            readIntPtr(intptr_t *pArg) const;
bool                readBool() const;
status_t            readBool(bool *pArg) const;
char16_t            readChar() const;
status_t            readChar(char16_t *pArg) const;
int8_t              readByte() const;
status_t            readByte(int8_t *pArg) const;

// Read a UTF16 encoded string, convert to UTF8
status_t            readUtf8FromUtf16(std::string* str) const;
status_t            readUtf8FromUtf16(std::unique_ptr<std::string>* str) const;

const char*         readCString() const;
...

因此对于基本类型,开发者可以直接调用接口写入和读出。而对于非基本类型,需要由开发者将其拆分成基本类型然后写入到Parcel中(读出的时候也是一样)。 Parcel会将所有写入的数据进行打包,Parcel本身可以作为一个整体在进程间传递。接收方在收到Parcel之后,只要按写入同样的顺序读出即可。

这个过程,和我们现实生活中寄送包裹做法是一样的:我们将需要寄送的包裹放到硬纸盒中交给快递公司。快递公司将所有的包裹进行打包,然后集中放到运输车中送到目的地,到了目的地之后然后再进行拆分。

Parcel既包含C++部分的实现,也同时提供了Java的接口,中间通过JNI衔接。Java层的接口其实仅仅是一层包装,真正的实现都是位于C++部分中,它们的关系如下图所示:

在这里插入图片描述
特别需要说明一下的是,Parcel类除了可以传递基本数据类型,还可以传递Binder对象:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}

这个方法写入的是sp类型的对象,而IBinder既可能是本地Binder,也可能是远程Binder,这样我们就不可以不用关心具体细节直接进行Binder对象的传递。

这也是为什么IInterface中定义了两个asBinder的static方法,如果你不记得了,请回忆一下这两个方法:

static sp<IBinder>  asBinder(const IInterface*);
static sp<IBinder>  asBinder(const sp<IInterface>&);

而对于Binder驱动,我们前面已经讲解过:Binder驱动并不是真的将对象在进程间序列化传递,而是由Binder驱动完成了对于Binder对象指针的解释和翻译,使调用者看起来就像在进程间传递对象一样。

3.2.6 Framework层的线程管理

在讲解Binder驱动的时候,我们就讲解过驱动中对应线程的管理。这里我们再来看看,Framework层是如何与驱动层对接进行线程管理的。ProcessState::setThreadPoolMaxThreadCount 方法中,会通过BINDER_SET_MAX_THREADS命令设置进程支持的最大线程数量:

#define DEFAULT_MAX_BINDER_THREADS 15

status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {
    status_t result = NO_ERROR;
    if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &maxThreads) != -1) {
        mMaxThreads = maxThreads;
    } else {
        result = -errno;
        ALOGE("Binder ioctl to set max threads failed: %s", strerror(-result));
    }
    return result;
}

由此驱动便知道了该Binder服务支持的最大线程数。驱动在运行过程中,会根据需要,并在没有超过上限的情况下,通过BR_SPAWN_LOOPER命令通知进程创建线程:

IPCThreadState在收到BR_SPAWN_LOOPER请求之后,便会调用ProcessState::spawnPooledThread来创建线程:

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    ...
    case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;
    ...
}

ProcessState::spawnPooledThread方法负责为线程设定名称并创建线程:

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp<Thread> t = new PoolThread(isMain);
        t->run(name.string());
    }
}

线程在run之后,会调用threadLoop将自身添加的线程池中:

virtual bool threadLoop()
{
   IPCThreadState::self()->joinThreadPool(mIsMain);
   return false;
}

而IPCThreadState::joinThreadPool方法中,会根据当前线程是否是主线程发送BC_ENTER_LOOPER或者BC_REGISTER_LOOPER命令告知驱动线程已经创建完毕。整个调用流程如下图所示:
在这里插入图片描述

源码位置(9.0)

3.3. Binder机制的Framework实现

Android应用程序使用Java语言开发,Binder框架自然也少不了在Java层提供接口。

前文中我们看到,Binder机制在C++层已经有了完整的实现。因此Java层完全不用重复实现,而是通过JNI衔接了C++层以复用其实现。

下图描述了Binder Framework Java层到C++层的衔接关系。
framework层UML图

在这里插入图片描述

源码位置(9.0)

这里对图中Java层和JNI层的几个类做一下说明:

名称类型说明
IInterfaceinterface供Java层Binder服务接口继承的接口
IBinderinterfaceJava层的IBinder类,提供了transact方法来调用远程服务
Binderclass实现了IBinder接口,封装了JNI的实现。Java层Binder服务的基类
BinderProxyclass实现了IBinder接口,封装了JNI的实现。提供transact方法调用远程服务
JavaBBinderHolderclass内部存储了JavaBBinder
JavaBBinderclass将C++端的onTransact调用传递到Java端
ParcelclassJava层的数据包装器,见C++层的Parcel类分析
这里的IInterface,IBinder和C++层的两个类是同名的。这个同名并不是巧合:它们不仅仅同名,它们所起的作用,以及其中包含的接口都是几乎一样的,区别仅仅在于一个是C++层,一个是Java层而已。

除了IInterface,IBinder之外,这里Binder与BinderProxy类也是与C++的类对应的,下面列出了Java层和C++层类的对应关系:

C++Java层
IInterfaceIInterface
IBinderIBinder
BBinderBinder
BpProxyBinderProxy
ParcelParcel

3.3.1 JNI的衔接

JNI全称是Java Native Interface,这个是由Java虚拟机提供的机制。这个机制使得native代码可以和Java代码互相通讯。简单来说就是:我们可以在C/C++端调用Java代码,也可以在Java端调用C/C++代码。

关于JNI的详细说明,可以参见Oracle的官方文档:Java Native Interface ,这里不多说明。

实际上,在Android中很多的服务或者机制都是在C/C++层实现的,想要将这些实现复用到Java层,就必须通过JNI进行衔接。AOSP源码中,/frameworks/base/core/jni/ 目录下的源码就是专门用来对接Framework层的JNI实现的。

看一下Binder.java的实现就会发现,这里面有不少的方法都是用native关键字修饰的,并且没有方法实现体,这些方法其实都是在C++中实现的:

public static final native int getCallingPid();

public static final native int getCallingUid();

public static final native long clearCallingIdentity();

public static final native void restoreCallingIdentity(long token);

public static final native void setThreadStrictModePolicy(int policyMask);

public static final native int getThreadStrictModePolicy();

public static final native void flushPendingCommands();

public static final native void joinThreadPool();

在android_util_Binder.cpp文件中的下面这段代码,设定了Java方法与C++方法的对应关系:

static const JNINativeMethod gBinderMethods[] = {
    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
    { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
    { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
    { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity },
    { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy },
    { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
    { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
    { "init", "()V", (void*)android_os_Binder_init },
    { "destroy", "()V", (void*)android_os_Binder_destroy },
    { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
};

这种对应关系意味着:当Binder.java中的getCallingPid方法被调用的时候,真正的实现其实是android_os_Binder_getCallingPid,当getCallingUid方法被调用的时候,真正的实现其实是android_os_Binder_getCallingUid,其他类同。

然后我们再看一下android_os_Binder_getCallingPid方法的实现就会发现,这里其实就是对接到了libbinder中了:

static jint android_os_Binder_getCallingPid(JNIEnv* env, jobject clazz)
{
    return IPCThreadState::self()->getCallingPid();
}

这里看到了Java端的代码是如何调用的libbinder中的C++方法的。那么,相反的方向是如何调用的呢?最关键的,libbinder中的BBinder::onTransact是如何能够调用到Java中的Binder::onTransact的呢?

这段逻辑就是android_util_Binder.cpp中JavaBBinder::onTransact中处理的了。JavaBBinder是BBinder子类,其类结构如下:
在这里插入图片描述
JavaBBinder::onTransact关键代码如下:

virtual status_t onTransact(
   uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
{
   JNIEnv* env = javavm_to_jnienv(mVM);

   IPCThreadState* thread_state = IPCThreadState::self();
   const int32_t strict_policy_before = thread_state->getStrictModePolicy();

   jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
       code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
   ...
}

请注意这段代码中的这一行:

jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
  code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);

这一行代码其实是在调用mObject上offset为mExecTransact的方法。这里的几个参数说明如下:

  • mObject 指向了Java端的Binder对象
  • gBinderOffsets.mExecTransact 指向了Binder类的execTransact方法
  • data 调用execTransact方法的参数
  • code, data, reply, flags都是传递给调用方法execTransact的参数

而JNIEnv.CallBooleanMethod这个方法是由虚拟机实现的。即:虚拟机会提供native方法来调用一个Java Object上的方法(关于Android上的Java虚拟机,今后我们会专门讲解)。

这样,就在C++层的JavaBBinder::onTransact中调用了Java层Binder::execTransact方法。而在Binder::execTransact方法中,又调用了自身的onTransact方法,由此保证整个过程串联了起来:

private boolean execTransact(int code, long dataObj, long replyObj,
       int flags) {
   Parcel data = Parcel.obtain(dataObj);
   Parcel reply = Parcel.obtain(replyObj);
   boolean res;
   try {
       res = onTransact(code, data, reply, flags);
   } catch (RemoteException|RuntimeException e) {
       if (LOG_RUNTIME_EXCEPTION) {
           Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
       }
       if ((flags & FLAG_ONEWAY) != 0) {
           if (e instanceof RemoteException) {
               Log.w(TAG, "Binder call failed.", e);
           } else {
               Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
           }
       } else {
           reply.setDataPosition(0);
           reply.writeException(e);
       }
       res = true;
   } catch (OutOfMemoryError e) {
       RuntimeException re = new RuntimeException("Out of memory", e);
       reply.setDataPosition(0);
       reply.writeException(re);
       res = true;
   }
   checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
   reply.recycle();
   data.recycle();

   StrictMode.clearGatheredViolations();

   return res;
}

参考

Android Binder
Android Interface Definition Language
Android Bander设计与实现 - 设计篇
Binder系列—开篇
彻底理解Android Binder通信架构
binder驱动——-之内存映射篇
Android Binder机制(一) Binder的设计和框架
Android Binder 分析——内存管理
理解Android Binder机制1/3:驱动篇
理解Android Binder机制2/3:C++层
理解Android Binder机制3/3:Java层

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值