一 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