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 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值