Binder系列5 Binder传输原理之驱动路由

一 Binder驱动路由

在上一篇文章的最后,我们知道 BpBinder 将数据发到了 Binder 驱动。然而在驱动层,这部分数据又是如何传递到 BBinder 一侧的呢?这里面到底藏着什么猫腻?另外,上一篇文章虽然阐述了4棵红黑树,但是并未说明红黑树的节点到底是怎么产生的。现在,我们试着来回答这些问题。

1.1 驱动处理函数binder_ioctl

我们知道在 Binder 驱动层,和用户空间的 ioctl() 相对应的函数是 binder_ioctl() 函数。在这个函数里,会先调用类似 copy_from_user() 这样的函数,来读取用户空间的数据。然后,再调用 binder_thread_write() 和 binder_thread_read() 做进一步的处理。我们先画一张调用关系图:

在这里插入图片描述
binder_ioctl 函数针对 BINDER_WRITE_READ 的处理如下:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	//之前介绍过,通过filp的private_data域找到对应的发起请求的进程
	struct binder_proc *proc = filp->private_data;	
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd);//命令
	void __user *ubuf = (void __user *)arg;//参数 
	........ 
    //从proc的线程树threads中查找当前线程,如果没有找到则创建一个新的线程
    //并添加到threads树中(线程树threads的生成)
	thread = binder_get_thread(proc);
	........ 
	switch (cmd) {//解析命令,根据不同的命令,执行不同的操作
	case BINDER_WRITE_READ:
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
	........
	default:
		ret = -EINVAL;
		goto err;
	}
	ret = 0;
    ........
	return ret;
}

从代码中可以看到,驱动通过 binder_ioctl 函数来和用户空间通信,这个函数会解析从用户空间传输过来的命令,并根据不同命令做不同操作,我们看到针对 BINDER_WRITE_READ 命令,对应的处理函数是 binder_ioctl_write_read,注意这个函数中的最后一个参数 thread,这个 thread 表示当前执行 Binder 通信的线程,是通过调用 binder_get_thread 获得的,这个函数非常重要,事关之前所说的四颗红黑树之一的 threads 树的生成.我们看代码如下:

1.2 threads树的生成 binder_get_thread

static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
	struct binder_thread *thread;
	struct binder_thread *new_thread;

	binder_inner_proc_lock(proc);
	//先从threads树中查找与当前线程匹配的线程
	thread = binder_get_thread_ilocked(proc, NULL);
	binder_inner_proc_unlock(proc);
	if (!thread) {//如果在threads树中找不到匹配当前的线程,则新创建一个
		new_thread = kzalloc(sizeof(*thread), GFP_KERNEL);
		if (new_thread == NULL)
			return NULL;
		binder_inner_proc_lock(proc);
		//对新创建的线程初始化,并把这个创建的线程链接到线程树threads中
		thread = binder_get_thread_ilocked(proc, new_thread);
		binder_inner_proc_unlock(proc);
		if (thread != new_thread)
			kfree(new_thread);
	}
	return thread;
}

先调用 binder_get_thread_ilocked 函数,从发起端进程的 threads 树中遍历查找与当前线程匹配的线程,第一次调用的时候,第二个参数为 NULL,代码如下:

static struct binder_thread *binder_get_thread_ilocked(
		struct binder_proc *proc, struct binder_thread *new_thread)
{
	struct binder_thread *thread = NULL;
	struct rb_node *parent = NULL;
	struct rb_node **p = &proc->threads.rb_node;//根据proc获取当前进程的线程树

	while (*p) {//遍历线程树
		parent = *p;
		thread = rb_entry(parent, struct binder_thread, rb_node);

		if (current->pid < thread->pid)
			p = &(*p)->rb_left;
		else if (current->pid > thread->pid)
			p = &(*p)->rb_right;
		else
			return thread;//找到与当前线程匹配的线程,返回该线程节点
	}
	if (!new_thread)//第一次调用的时候new_thread为null
		return NULL;//没有找到,返回null
	thread = new_thread;//把新创建的new_thread赋值给thread
	binder_stats_created(BINDER_STAT_THREAD);
	//以下为对binder_thread的初始化操作
	thread->proc = proc;
	thread->pid = current->pid;
	get_task_struct(current);
	thread->task = current;
	atomic_set(&thread->tmp_ref, 0);
	init_waitqueue_head(&thread->wait);
	INIT_LIST_HEAD(&thread->todo);//初始化线程的等待队列todo
	rb_link_node(&thread->rb_node, parent, p);
	//把新创建的biner_thread插入到进程的线程树threads中
	rb_insert_color(&thread->rb_node, &proc->threads);
	thread->looper_need_return = true;
	thread->return_error.work.type = BINDER_WORK_RETURN_ERROR;
	thread->return_error.cmd = BR_OK;
	thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR;
	thread->reply_error.cmd = BR_OK;
	INIT_LIST_HEAD(&new_thread->waiting_thread_node);
	return thread;
}

代码很清晰,基本过程是遍历本进程的线程树 threads,查找是否存在与当前线程匹配的 binder_thread 节点,如果找到则返回该 binder_thread 节点,如果没有找到,则返回 null.然后回到 binder_get_thread 函数,可以看到如果是 null,则创建一个新的 biner_thread 并重新调用 binder_get_thread_ilocked,观察该函数的第二个参数,就是新创建的 binder_thread,我们看到在第二次执行 binder_get_thread_ilocked 函数的过程中会把这个新创建的 binder_thread 初始化并插入到 threads 中,通过这种方式,线程树中会链接越来越多的 binder_thread,这个就是进程中的线程树 threads 产生的过程.另外这种"找不到就创建"的做法在其它红黑树中也会用到,大家需要了解.

1.3 Binder读写命令的操作 binder_ioctl_write_read

接下来看对于命令 BINDER_WRITE_READ 的处理函数 binder_ioctl_write_read,代码如下:

static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread)
{
	int ret = 0;
	struct binder_proc *proc = filp->private_data;//还是根据filp找到当前进程
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;//参数
	struct binder_write_read bwr;//定义内核空间的binder_write_read
	........
	//把用户空间数据ubuf拷贝到内核空间bwr
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		........
	}
	........			
	if (bwr.write_size > 0) {//如果write_size大于0,则调用binder_thread_write函数执行写操作
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		........
	}
	if (bwr.read_size > 0) {//如果read_size大于0,则调用binder_thread_read函数执行读操作
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
		........
		//执行完读操作后,检查本进程的等待队列,如果非空,也即todo中有待处理的事务,则唤醒本进程来处理在todo队列中等待的事务
		if (!binder_worklist_empty_ilocked(&proc->todo))
			binder_wakeup_proc_ilocked(proc);//唤醒本进程来处理todo队列中的事务
		........
	}
	........
	if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {//将内核空间数据bwr拷贝到用户空间ubuf
		........
	}
out:
	return ret;
}

从代码中可以看到,先执行写操作,后执行读操作.当然也可以只执行写操作,或只执行读操作,这要根据 write_size 和 read_size 的值来决定,这个为我们处理同步和异步任务带来了便利,同步任务需要等待回复,则两个值都大于0即可;如果是异步操作,那么只写不读,发送完毕即结束,则设置 write_size 大于0,read_size 等于0即可,非常灵活和方便.我们之前的 SMgr 的 binder_loop 循环就是只读操作,大家可以回看之前的代码来验证.我们接下来先来分析写操作.

1.4 Binder写操作 binder_thread_write

static int binder_thread_write(struct binder_proc *proc,
			struct binder_thread *thread,
			binder_uintptr_t binder_buffer, size_t size,
			binder_size_t *consumed)
{
	uint32_t cmd;
	struct binder_context *context = proc->context;//Binder驱动全局的context
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;
	while (ptr < end && thread->return_error.cmd == BR_OK) {
		int ret;
		//拷贝用户空间的cmd命令到内核空间,此时为BC_TRANSACTION
		if (get_user(cmd, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
		........
		switch (cmd) {		
		........
		//走到BC_TRANSACTION
		case BC_TRANSACTION:
		case BC_REPLY: {
		//定义内核空间的binder_transaction_data
			struct binder_transaction_data tr;
			//拷贝用户空间的binder_transaction_data到内核空间的tr
			if (copy_from_user(&tr, ptr, sizeof(tr)))
				return -EFAULT;
			ptr += sizeof(tr);
			//执行核心函数binder_transaction
			binder_transaction(proc, thread, &tr,
					   cmd == BC_REPLY, 0);
			break;
		}	
		........
		}
		*consumed = ptr - buffer;
	}
	return 0;
}

我们重点关注 BC_TRANSACTION 命令,这个是之前 IPCThreadState 的 transact 函数中通过 writeTransactionData 函数写入的命令,主要用来实现数据的传输,从代码中可以看到先把 binder_transaction_data 从用户空间拷贝到内核空间,然后执行 binder_transaction 函数.在分析 binder_transaction 函数之前,我们需要先了解下 Binder 事务的概念和 binder_transaction_data 的数据结构.

二 Binder事务

2.1 Binder事务的概念

Binder 机制对传输工作设计的大体思路是这样的,它将每次一个"传输并执行特定语义的"工作,理解为一个事务,既然所传输的数据是 binder_transaction_data 类型的,那么这种事务可以相应地定义为 binder_transaction.很显然系统中会同时存在很多事务,分别用来执行不同的传输任务,那么发向同一个进程或线程的若干事务就必须串行化地组织起来.因此 Binder 驱动为进程节点(binder_proc)和线程节点(binder_thread)都设计了一个 todo 等待队列,这个 todo 队列的职责就是"串行化地组织待处理的事务"。

struct binder_transaction {
	int debug_id;//调试调用
	//描述事务的工作事项,这里会将type设置为BINDER_WORK_TRANSACTION
	//用于把事务添加到todo队列
	struct binder_work work;
	struct binder_thread *from;//源线程,即发起事务的线程(同步操作才有意义)
	//指向源线程的传输栈transaction_stack
	struct binder_transaction *from_parent;
	struct binder_proc *to_proc;//接收事务的目标进程
	struct binder_thread *to_thread;//接收事务的目标线程
	//指向该事务目标线程的传输栈transaction_stack
	struct binder_transaction *to_parent;
	unsigned need_reply:1;
	/* unsigned is_dead:1; */	/* not used at the moment */
	struct binder_buffer *buffer;//其中的data域用于存放传输的数据
	unsigned int	code;//binder_transaction_date的code,也就是函数编号
	unsigned int	flags;//binder_transaction_date的flag
	struct binder_priority	priority;
	struct binder_priority	saved_priority;
	bool    set_priority_called;
	kuid_t	sender_euid;	
	spinlock_t lock;
};
struct binder_work {
	struct list_head entry;//work的项
	enum {
		BINDER_WORK_TRANSACTION = 1,
		BINDER_WORK_TRANSACTION_COMPLETE,
		BINDER_WORK_RETURN_ERROR,
		BINDER_WORK_NODE,
		BINDER_WORK_DEAD_BINDER,
		BINDER_WORK_DEAD_BINDER_AND_CLEAR,
		BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
	} type;//work的类型
};
struct binder_buffer {
	struct list_head entry; /* free and allocated entries by address */
	struct rb_node rb_node; /* free entry by size or allocated entry */
				/* by address */
	unsigned free:1;
	unsigned allow_user_free:1;
	unsigned async_transaction:1;
	unsigned free_in_progress:1;
	unsigned debug_id:28;
	struct binder_transaction *transaction;//buffer对应的transaction
	struct binder_node *target_node;
	size_t data_size;
	size_t offsets_size;
	size_t extra_buffers_size;
	void *data;//存放传输的数据
};

下图绘制了一个进程节点,以及一个从属于该进程的线程节点,它们各带了两个待处理的事务(binder_transaction):

在这里插入图片描述
一个 Binder 事务 binder_transaction 中包含了:要传输的数据,该事务的类型,发送事务的源线程,接收事务的目标进程或目标线程,还有该事务分别在源线程和目标线程的事务栈 transaction_stack 中的位置.也就是一个 Binder 事务中所包含的信息,满足了目标接收端进程或线程所需要知道的一切信息.这样看来,传输任务的基本目标就很明确了,就是在发起端中生成一个 binder_transaction 节点,并且对这个事务进行赋值和初始化操作,然后把这个事务插入到目标进程或目标线程的 todo 队列中去,然后唤醒目标进程或目标线程对传输过来的事务进行处理,这个就是传输的基本流程.

2.2 事务栈 transaction_stack

事务栈 transaction_stack 示意图如下:

在这里插入图片描述

事务栈 transaction_stack 概念的引入是为了提高线程的使用率,它存在于 binder_thread 线程中,用来标记本线程中需要处理的事务,这些事务以堆栈的形式存放,它本质上就是一个事务,只是这个事务栈标记了本线程内部事务的先后依赖关系,这个有助于 Binder 驱动根据发送端线程当前的事务,来寻找目标进程端是否存在空闲的线程来处理,这个能够寻找最优线程,节省线程资源,提高线程的利用率,是 Binder 驱动对线程选择的重大优化.

在事务的概念中我们已经知道,发起传输主要就是生成一个 binder_transaction 并赋值初始化,然后把这个事务插入到目标进程或目标线程的 todo 队列中即可,可是该怎么找目标进程或目标线程呢?基本做法是先从发起端的 BpBinder 开始,找到与其对应的 binder_node 节点,这个在前文阐述 binder_proc 的4棵红黑树时已经说过了,这里不再赘述。总之拿到目标 binder_node 之后,我们就可以通过其 proc 域,拿到目标进程对应的 binder_proc了。如果偷懒的话,我们可以直接把 binder_transaction 节点插到这个 binder_proc 的 todo 队列中去,就算完成传输任务了。当然,我们知道 Binder 驱动做了一些更精细的调整,这就用到了事务栈了。

Binder 驱动希望能把 binder_transaction 尽量放到目标进程里的某个线程中去,这样可以充分利用这个进程中的 Binder 工作线程。比如目标进程中的一个 Binder 线程目前正睡着,它在等待其他某个线程做完某个事务后才会醒来,而那个事务又偏偏在等待当前的这个 binder_transaction 事务,需要当前这个 binder_transaction 事务处理结束后才能完成,那么我们就可以让这个睡着的线程,先处理当前的 binder_transaction 事务,这就达到了充分利用线程的目的,这些事务的相互等待关系是用事务栈 transaction_stack 来描述的.也就是说如果 Binder 驱动可以找到一个合适的线程的话,它就会把 binder_transaction 节点插到这个线程的 todo 队列中,而如果找不到合适的线程的话,那么就把 binder_transaction 节点插入到目标进程的 todo 队列中。

三 传输函数binder_transaction

接下来看 binder_transaction 函数,这是个非常重要的函数,代码量很大,我们截取重要信息分析,但是基本思路不变,就是创建一个事务,然后把这个事务赋值并初始化,然后把这个事务插入到一个合适的目标线程或目标进程的 todo 队列中,知道基本思路就好分析了,当然细节中包括怎么选取合适的目标线程,以及怎么创建 binder_ref 引用树和怎么创建 binder_node 节点树等,这些重要的内容都包含在这个函数里面.代码如下:

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size)
{
	int ret;
	struct binder_transaction *t;//定义要发送的事务
	struct binder_work *tcomplete;//向发送线程反馈命令发送完成
	........
	struct binder_proc *target_proc = NULL;//目标进程
	struct binder_thread *target_thread = NULL;//目标线程
	struct binder_node *target_node = NULL;//目标binder_node节点
	........
	if (reply) {
	  ........
	  //reply的操作大家自己研究,他是Server端向Client端的回复
	} else {//此处传输指令为BC_TRANSACTION, reply为false
    	//tr中的target描述传输的目标端,系列1中介绍过,若目标Binder对应的句柄非0,
    	//即目标进程为普通服务进程
		if (tr->target.handle) {
			struct binder_ref *ref;
			........
			//根据句柄值在本进程中的引用树refs_by_desc中查找对应的binder_ref
			ref = binder_get_ref_olocked(proc, tr->target.handle,
						     true);
			if (ref) {
     			//根据查找到的binder_ref引用,找到其对应的目标节点target_node以及目标节点所在的目标进程target_proc.
				target_node = binder_get_node_refs_for_txn(
						ref->node, &target_proc,
						&return_error);
			} else {
				//异常情况,一般不会发生
			}
			........
		} else {//若目标进程对应的句柄为0, 那就说明目标进程为SMgr进程
			........
			//获取全局的context中的SMgr的Binder节点binder_context_mgr_node赋值给target_node
			target_node = context->binder_context_mgr_node;
			if (target_node)
    			//根据target_node,找到其对应的目标进程target_proc,并对target_node的引用做计数管理
				target_node = binder_get_node_refs_for_txn(
						target_node, &target_proc,
						&return_error);
			........
		}
		//通过以上操作我们获取到了目标节点target_node和目标节点所在的目标进程target_proc
		........
		//如果是同步操作,也就是非oneway,并且线程的事务栈不为空, 则需要在目标进程中查找是否存在可以复用的线程作为目标线程
		//异步操作,也就是oneway情况,因为不需要等待回复,发出去就完事了,所以不存在线程等待情况,也就无法查找能够复用的线程.
		if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
			struct binder_transaction *tmp;
			tmp = thread->transaction_stack;
			........
			while (tmp) {//遍历当前线程的事务栈,查找是否存在可以复用的线程
				struct binder_thread *from;
				........
				from = tmp->from;
				//注意算法,就是查找事务栈中的事务,是否有来自目标进程的线程在等待,如果有即可作为复用线程(想想为什么)
				if (from && from->proc == target_proc) {
					atomic_inc(&from->tmp_ref);
					target_thread = from;
					spin_unlock(&tmp->lock);
					break;
				}
				........
				tmp = tmp->from_parent;
			}
		}
		........
	}
	........
	//为事务t分配内存空间
	t = kzalloc(sizeof(*t), GFP_KERNEL);
	........
    //为tcomplete分配内存空间
	tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
	........
	if (!reply && !(tr->flags & TF_ONE_WAY))
		t->from = thread;//事务的发起线程from只有在非oneway(同步)的情况下才有意义
	else
		t->from = NULL;//oneway(异步)情况下不需要回复,所以也不需要知道事务的发起线程是哪个
	//对事务的初始化操作
	t->sender_euid = task_euid(proc->tsk);
	t->to_proc = target_proc;//目标进程保存到事务的to_proc
	t->to_thread = target_thread;//目标线程(如果存在的话)保存到事务的to_thread
	t->code = tr->code;//函数编号
	t->flags = tr->flags;//同步异步flag
	........
    //从目标进程分配一个内核缓冲区给事务t的buffer
	t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
		tr->offsets_size, extra_buffers_size,
		!reply && (t->flags & TF_ONE_WAY));
	........
	//继续对事务的buffer初始化
	t->buffer->allow_user_free = 0;
	t->buffer->debug_id = t->debug_id;
	t->buffer->transaction = t;
	t->buffer->target_node = target_node;
	........
	//以下操作为从传输的数据中解析出每一个Binder也就是flat_binder_object,并生成相应的红黑树(refs树和nodes树)
	//设置事务t中偏移数组的开始位置off_start, 即当前位置+binder_transaction_data数据大小
	off_start = (binder_size_t *)(t->buffer->data +
				      ALIGN(tr->data_size, sizeof(void *)));
	offp = off_start;
   //将用户空间中binder_transaction_data类型数据tr的数据缓冲区, 拷贝到内核空间中事务t的内核缓冲区
	if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
			   tr->data.ptr.buffer, tr->data_size)) {
		........
	}
	// 将用户空间中binder_transaction_data类型数据tr的偏移数组, 拷贝到内核空间中事务t的偏移数组中
    // offp为事务t中偏移数组的起始位置
	if (copy_from_user(offp, (const void __user *)(uintptr_t)
			   tr->data.ptr.offsets, tr->offsets_size)) {
		........
	}
	........
	//设置事务t中偏移数组的结束位置off_end
	off_end = (void *)off_start + tr->offsets_size;
	........
	//遍历偏移数组区间,找出其中的每一个flat_binder_object对象(Binder在传输数据中的表现形式)并生成相应的binder_ref
	//相应的binder_ref或binder_node,然后分别添加到引用树(refs)或者节点树(nodes)中.
	for (; offp < off_end; offp++) {
		struct binder_object_header *hdr;
		........
		//从事务t的数据缓冲区中, 获取offp位置的binder_object_header对象
		hdr = (struct binder_object_header *)(t->buffer->data + *offp);
		off_min = *offp + object_size;
		switch (hdr->type) {
		case BINDER_TYPE_BINDER:
		case BINDER_TYPE_WEAK_BINDER: {//flat_binder_object类型为Binder实体
			struct flat_binder_object *fp;
            //根据hdr属性获取flat_binder_object对象
			fp = to_flat_binder_object(hdr);
			//从源进程的节点红黑树nodes中, 根据fp对象的binder属性查找Binder节点,若没有则创建一个binder_node节点
            //并把这个节点添加到nodes树中(nodes树的生成),同时在目标进程中创建binder_ref并指向这个binder_node
            //然后把这个binder_ref添加到目标进程的引用树refs_by_desc和refs_by_node中(引用树的生成)
            //同时修改fp对象的引用类型为BINDER_TYPE_HANDLE.
			ret = binder_translate_binder(fp, t, thread);
			........
		} break;
		case BINDER_TYPE_HANDLE:
		case BINDER_TYPE_WEAK_HANDLE: {//flat_binder_object类型为Binder引用
			struct flat_binder_object *fp;
            //根据hdr属性获取flat_binder_object对象
			fp = to_flat_binder_object(hdr);
			//根据fp的句柄值在源进程的refs_by_desc引用树中查找到对应的binder_ref
            //然后根据binder_ref找到对应的binder_node,进而找到这个binder_ref的目标进程,如果目标进程
            //与事务的目标进程一致,则修改fp对象的引用类型为BINDER_TYPE_BINDER.
			ret = binder_translate_handle(fp, t, thread);
			........
		} break;
		........
		default:
			........
		}
	}	
	tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;//设置tcomplete的类型
	t->work.type = BINDER_WORK_TRANSACTION;//设置事务t的类型

	if (reply) {
		........
	} else if (!(t->flags & TF_ONE_WAY)) {//非oneway,同步操作
		........
		binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete);//把tcomplete的添加到源线程的todo队列
		t->need_reply = 1;//同步需要回复
		t->from_parent = thread->transaction_stack;
		thread->transaction_stack = t;//因为是同步操作,所以把当前要发送的事务t添加到源线程事务栈栈顶
		........
		//函数binder_proc_transaction实现了把事务t添加到目标线程或目标进程的同步或异步todo队列上, 并唤醒目标线程的等待队列
		if (!binder_proc_transaction(t, target_proc, target_thread)) {
			........
		}
	} else {
		........
		//异步操作,把tcomplete的添加到源线程的todo队列,并置thread的process_todo为true,标记线程是否有work需要处理
		binder_enqueue_thread_work(thread, tcomplete);
		//函数binder_proc_transaction实现了把事务t添加到目标线程或目标进程的同步或异步todo队列上, 并唤醒目标线程的等待队列
		if (!binder_proc_transaction(t, target_proc, NULL))
			goto err_dead_proc_or_thread;
	}	
	........
}

注释写的很详细,不需要再次描述了,其中的重点方法如下:

根据句柄值在本进程中的引用树 refs_by_desc 中查找对应的 binder_ref 的 binder_get_ref_olocked 方法,代码如下:

static struct binder_ref *binder_get_ref_olocked(struct binder_proc *proc,
						 u32 desc, bool need_strong_ref)
{
	struct rb_node *n = proc->refs_by_desc.rb_node;//找到进程的refs_by_desc树
	struct binder_ref *ref;

	while (n) {//遍历refs_by_desc树,查找匹配句柄值的binder_ref,并返回,如果没有找到返回null
		ref = rb_entry(n, struct binder_ref, rb_node_desc);

		if (desc < ref->data.desc) {
			n = n->rb_left;
		} else if (desc > ref->data.desc) {
			n = n->rb_right;
		} else if (need_strong_ref && !ref->data.strong) {
			binder_user_error("tried to use weak ref as strong ref\n");
			return NULL;
		} else {
			return ref;
		}
	}
	return NULL;
}

根据查找到的 binder_ref 引用,找到其对应的目标节点 target_node 以及目标节点所在的目标进程 target_proc

static struct binder_node *binder_get_node_refs_for_txn(
		struct binder_node *node,
		struct binder_proc **procp,
		uint32_t *error)
{
	struct binder_node *target_node = NULL;

	binder_node_inner_lock(node);
	if (node->proc) {
		target_node = node;//目标节点
		binder_inc_node_nilocked(node, 1, 0, NULL);
		binder_inc_node_tmpref_ilocked(node);
		node->proc->tmp_ref++;
		*procp = node->proc;//目标节点对应的目标进程
	} else
		*error = BR_DEAD_REPLY;
	binder_node_inner_unlock(node);

	return target_node;
}

如果 fp 的类型是 BINDER_TYPE_BINDER,则为实体 Binder,则执行 binder_translate_binder 函数,代码如下:

static int binder_translate_binder(struct flat_binder_object *fp,
				   struct binder_transaction *t,
				   struct binder_thread *thread)
{
	struct binder_node *node;
	struct binder_proc *proc = thread->proc;
	struct binder_proc *target_proc = t->to_proc;
	struct binder_ref_data rdata;
	int ret = 0;

	node = binder_get_node(proc, fp->binder);//从nodes树中获取对应的binder_node
	if (!node) {
		node = binder_new_node(proc, fp);//没有找到的话,创建一个新的binder_node,并添加到nodes树中
		if (!node)
			return -ENOMEM;
	}
	........
    //目标进程中生成一个binder_ref引用,并添加到目标进程的引用树中
	ret = binder_inc_ref_for_node(target_proc, node,
			fp->hdr.type == BINDER_TYPE_BINDER,
			&thread->todo, &rdata);
	if (ret)
		goto done;

	if (fp->hdr.type == BINDER_TYPE_BINDER)
		fp->hdr.type = BINDER_TYPE_HANDLE;//修改fp类型为BINDER_TYPE_HANDLE
	else
		fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
	fp->binder = 0;
	fp->handle = rdata.desc;//把新的句柄值赋值给handle
	fp->cookie = 0;

	........
	return ret;
}

生成一个新的 binder_node

static struct binder_node *binder_new_node(struct binder_proc *proc,
					   struct flat_binder_object *fp)
{
	struct binder_node *node;
	struct binder_node *new_node = kzalloc(sizeof(*node), GFP_KERNEL);

	if (!new_node)
		return NULL;
	binder_inner_proc_lock(proc);
	node = binder_init_node_ilocked(proc, new_node, fp);//初始化binder_node
	binder_inner_proc_unlock(proc);
	if (node != new_node)
		/*
		 * The node was already added by another thread
		 */
		kfree(new_node);

	return node;
}

并对这个新生成的 binder_node 初始化

static struct binder_node *binder_init_node_ilocked(
						struct binder_proc *proc,
						struct binder_node *new_node,
						struct flat_binder_object *fp)
{
	struct rb_node **p = &proc->nodes.rb_node;
	struct rb_node *parent = NULL;
	struct binder_node *node;
	binder_uintptr_t ptr = fp ? fp->binder : 0;
	binder_uintptr_t cookie = fp ? fp->cookie : 0;
	__u32 flags = fp ? fp->flags : 0;
	s8 priority;

	assert_spin_locked(&proc->inner_lock);

	while (*p) {

		parent = *p;
		node = rb_entry(parent, struct binder_node, rb_node);

		if (ptr < node->ptr)
			p = &(*p)->rb_left;
		else if (ptr > node->ptr)
			p = &(*p)->rb_right;
		else {
			/*
			 * A matching node is already in
			 * the rb tree. Abandon the init
			 * and return it.
			 */
			binder_inc_node_tmpref_ilocked(node);
			return node;
		}
	}
	node = new_node;
	binder_stats_created(BINDER_STAT_NODE);
	node->tmp_refs++;
	rb_link_node(&node->rb_node, parent, p);
	rb_insert_color(&node->rb_node, &proc->nodes);//把这个新生成的binder_node插入到nodes树中(nodes树的生成)
	node->debug_id = atomic_inc_return(&binder_last_id);
	node->proc = proc;
	node->ptr = ptr;
	node->cookie = cookie;
	node->work.type = BINDER_WORK_NODE;
	priority = flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
	node->sched_policy = (flags & FLAT_BINDER_FLAG_SCHED_POLICY_MASK) >>
		FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT;
	node->min_priority = to_kernel_prio(node->sched_policy, priority);
	node->accept_fds = !!(flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
	node->inherit_rt = !!(flags & FLAT_BINDER_FLAG_INHERIT_RT);
	spin_lock_init(&node->lock);
	INIT_LIST_HEAD(&node->work.entry);
	INIT_LIST_HEAD(&node->async_todo);
	........
	return node;
}

目标进程中生成一个 binder_ref 引用,并添加到目标进程的引用树中的 binder_inc_ref_for_node 方法

static int binder_inc_ref_for_node(struct binder_proc *proc,
			struct binder_node *node,
			bool strong,
			struct list_head *target_list,
			struct binder_ref_data *rdata)
{
	struct binder_ref *ref;
	struct binder_ref *new_ref = NULL;
	int ret = 0;

	binder_proc_lock(proc);
	ref = binder_get_ref_for_node_olocked(proc, node, NULL);//根据binder_node从refs_by_node树中查询对应的引用
	if (!ref) {
		binder_proc_unlock(proc);
		new_ref = kzalloc(sizeof(*ref), GFP_KERNEL);//找不到则创建
		if (!new_ref)
			return -ENOMEM;
		binder_proc_lock(proc);
		ref = binder_get_ref_for_node_olocked(proc, node, new_ref);//初始化这个binder_ref
	}
	ret = binder_inc_ref_olocked(ref, strong, target_list);
	*rdata = ref->data;
	binder_proc_unlock(proc);
	if (new_ref && ref != new_ref)
		/*
		 * Another thread created the ref first so
		 * free the one we allocated
		 */
		kfree(new_ref);
	return ret;
}

根据 binder_node 从 refs_by_node 树中查询对应的引用

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;//refs_by_node引用树
	struct rb_node *parent = NULL;
	struct binder_ref *ref;
	struct rb_node *n;

	while (*p) {//遍历refs_by_node引用树,找到匹配的binder_ref
		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);
	//对新生成的binder_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);//把这个binder_ref插入到目标进程的引用树中

	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;//句柄值的来历,也就是递增加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.desc < ref->data.desc)
			p = &(*p)->rb_left;
		else if (new_ref->data.desc > ref->data.desc)
			p = &(*p)->rb_right;
		else
			BUG();
	}
	rb_link_node(&new_ref->rb_node_desc, parent, p);
	rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc);

	binder_node_lock(node);
	hlist_add_head(&new_ref->node_entry, &node->refs);

	........
	binder_node_unlock(node);
	return new_ref;
}

针对 fp 类型为 BINDER_TYPE_HANDLE 的操作 binder_translate_handle,代码如下:

static int binder_translate_handle(struct flat_binder_object *fp,
				   struct binder_transaction *t,
				   struct binder_thread *thread)
{
	struct binder_proc *proc = thread->proc;
	struct binder_proc *target_proc = t->to_proc;
	struct binder_node *node;
	struct binder_ref_data src_rdata;
	int ret = 0;
    //根据句柄值找到对应的binder_ref进而找到对应的binder_node
	node = binder_get_node_from_ref(proc, fp->handle,
			fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata);
	........

	binder_node_lock(node);
	if (node->proc == target_proc) {
		if (fp->hdr.type == BINDER_TYPE_HANDLE)
			fp->hdr.type = BINDER_TYPE_BINDER;//修改fp的type为BINDER_TYPE_BINDER
		else
			fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
		fp->binder = node->ptr;//对fp的binder赋值
		fp->cookie = node->cookie;//对fp的cookie赋值
		........
	} else {
		........
	}
done:
	binder_put_node(node);
	return ret;
}

把任务添加到目标线程或目标进程的 todo 队列,然后唤醒目标线程或目标进程对事务进程处理.

static bool binder_proc_transaction(struct binder_transaction *t,
				    struct binder_proc *proc,
				    struct binder_thread *thread)
{
	struct binder_node *node = t->buffer->target_node;
	struct binder_priority node_prio;
	bool oneway = !!(t->flags & TF_ONE_WAY);
	bool pending_async = false;
	........
	if (oneway) {//异步事务,在系列1中已经说明过,这里设置pending_async就是为了给同步事务让路,优先处理同步事务
		if (node->has_async_transaction) {//如果目标节点里边已经有异步事务要处理,则设置pending_async为true
			pending_async = true;
		} else {
			node->has_async_transaction = 1;
		}
	}
    ........
   //没有找到目标线程,但是是同步操作或node第一次处理异步操作,则优先处理,所以从进程中waiting_threads中找到一个线程处理
	if (!thread && !pending_async)
		thread = binder_select_thread_ilocked(proc);

	if (thread) {//如果有对应的目标线程
		binder_transaction_priority(thread->task, t, node_prio,
					    node->inherit_rt);
		binder_enqueue_thread_work_ilocked(thread, &t->work);//把事务添加的目标线程的todo中处理
	} else if (!pending_async) {//没有找到目标线程,则只能添加到目标进程的todo中
		binder_enqueue_work_ilocked(&t->work, &proc->todo);//把事务添加的进程的todo中处理
	} else {
    	//node中已经有异步任务了,则把事务添加的node的async_todo中排队待处理
		binder_enqueue_work_ilocked(&t->work, &node->async_todo);
	}

	if (!pending_async)//如果是同步或者第一次执行异步操作,则唤醒目标进程或目标线程
		binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);

	binder_inner_proc_unlock(proc);
	binder_node_unlock(node);

	return true;
}

唤醒目标线程

static void binder_wakeup_thread_ilocked(struct binder_proc *proc,
					 struct binder_thread *thread,
					 bool sync)
{
	assert_spin_locked(&proc->inner_lock);

	if (thread) {
		if (sync)
			wake_up_interruptible_sync(&thread->wait);
		else
			wake_up_interruptible(&thread->wait);
		return;
	}

	/* Didn't find a thread waiting for proc work; this can happen
	 * in two scenarios:
	 * 1. All threads are busy handling transactions
	 *    In that case, one of those threads should call back into
	 *    the kernel driver soon and pick up this work.
	 * 2. Threads are using the (e)poll interface, in which case
	 *    they may be blocked on the waitqueue without having been
	 *    added to waiting_threads. For this case, we just iterate
	 *    over all threads not handling transaction work, and
	 *    wake them all up. We wake all because we don't know whether
	 *    a thread that called into (e)poll is handling non-binder
	 *    work currently.
	 */
	binder_wakeup_poll_threads_ilocked(proc, sync);
}

以上就是 binder_transaction 函数的全部内容.

四 总结

Binder 驱动的路由,也就是如何找到目的 Binder 实体的过程,主要的操作都在 binder_transaction 函数中实现,总结如下:

  • 创建一个 binder_transaction 事务,这个事务将会被发送到目标进程或目标线程中
  • 根据句柄值找到对应的目标节点 target_node 和目标节点所在的目标进程 target_proc
  • 如果是同步操作的话,需要做进一步的优化,优化方式是通过发送线程的事务栈 transaction_stack 信息,查找是否存在一个可以重复利用的目标线程 target_thread
  • 对 binder_transaction 事务进程做初始化赋值操作,为发送做准备
  • 遍历传输数据中存在的 Binder 结构 flat_binder_object,分别生成各自进程内对应的 nodes 树和引用树的节点,并建立引用和节点之间的连接关系
  • 发送binder_transaction事务,把这个事务插入到目标进程或目标线程的todo队列中,并唤醒目标进程或线程

以上步骤就是 Binder 驱动路由的基本流程,等找到了目标进程或目标线程之后,就把这个 binder_transaction 事务插入到对应目标进程或目标线程的 todo 队列中,接下来在下一篇文章中介绍接收端如何接收并处理这个事务.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值