bwr.read_consumed = 0; if (copy_to_user(ubuf, &bwr, sizeof(bwr))) ret = -EFAULT; goto out; } } if (bwr.read_size > 0) {//读数据 ret = binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK); trace_binder_read_done(ret); if (!list_empty(&proc->todo)) //唤醒等待状态的线程 wake_up_interruptible(&proc->wait); if (ret < 0) { //读失败 if (copy_to_user(ubuf, &bwr, sizeof(bwr))) ret = -EFAULT; goto out; } }
可见 binder 驱动内部依赖用户空间的 binder_write_read 决定是要读取还是写入数据:其内部变量 read_size>0 则代表要读取数据,write_size>0 代表要写入数据,若都大于 0 则先写入,后读取。
至此焦点应该集中在 binder_thread_write() 和 binder_thread_read(),下面分析这两个方法。
4.binder_thread_write
在上面的 binder_ioctl_write_read() 方法中调用 binder_thread_write() 时传入了 bwr.write_buffer、bwr.write_size 等,先搞清楚这些参数是什么。
最开始是在用户空间 IPCThreadState 的 transact() 中通过 writeTransactionData() 方法创建数据并写入 mOut 的,writeTransactionData 方法代码如下:
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; tr.target.handle = handle; //目标 server 的 binder 的句柄 //请求码,getService() 服务对应的是 GET_SERVICE_TRANSACTION 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 {…} mOut.writeInt32(cmd); // transact 传入的 cmd 是 BC_TRANSACTION mOut.write(&tr, sizeof(tr)); //打包成 binder_transaction_data return NO_ERROR; }
然后在 IPCThreadState 的 talkWithDriver() 方法中对 write_buffer 赋值:
bwr.write_buffer?=?(uintptr_t)mOut.data();
搞清楚了数据的来源,再来看 binder_thread_write() 方法,binder_thread_write() 方法中处理了大量的 BC_XXX 命令,代码很长,这里我们只关注当前正在处理的 BC_TRANSACTION 命令,简化后代码如下:
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; 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 == BR_OK) { if (get_user(cmd, (uint32_t __user *)ptr)) // 读取一个 cmd return -EFAULT; //跳过 cmd 所占的空间,指向要处理的数据 ptr += sizeof(uint32_t); switch (cmd) { case BC_TRANSACTION: case BC_REPLY: { //与 writeTransactionData 中准备的数据结构体对应 struct binder_transaction_data tr; //拷贝到内核空间 tr 中 if (copy_from_user(&tr, ptr, sizeof(tr))) return -EFAULT; //跳过数据所占空间,指向下一个 cmd ptr += sizeof(tr); //处理数据 binder_transaction(proc, thread, &tr, cmd == BC_REPLY); break; } 处理其他 BC_XX 命令… } //被写入处理消耗的数据量,对应于用户空间的 bwr.write_consumed *consumed = ptr - buffer;
binder_thread_write() 中从 bwr.write_buffer 中取出了 cmd 和 cmd 对应的数据,进一步交给 binder_transaction() 处理,需要注意的是,BC_TRANSACTION、BC_REPLY 这两个命令都是由 binder_transaction() 处理的。
简单梳理一下,由 binder_ioctl -> binder_ioctl_write_read -> binder_thread_write ,到目前为止还只是在准备数据,没有看到跟目标进程相关的任何处理,都属于 “准备数据,根据命令分发给具体的方法去处理” 第 1 个工作。
而到此为止,第 1 个工作便结束,下一步的 binder_transaction() 方法终于要开始后面的工作了。
5.binder_transaction
binder_transaction() 方法中代码较长,先总结它干了哪些事:对应开头列出的工作,此方法中做了非常关键的 2-4 步:
- 找到目标进程的相关信息
- 将数据一次拷贝到目标进程所映射的物理内存块
- 记录待处理的任务,唤醒目标线程
以这些工作为线索,将代码分为对应的部分来看,首先是**「找到目标进程的相关信息」**,简化后代码如下:
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply){ struct binder_transaction *t; //用于描述本次 server 端要进行的 transaction struct binder_work *tcomplete; //用于描述当前调用线程未完成的 transaction binder_size_t *offp, *off_end; struct binder_proc *target_proc; //目标进程 struct binder_thread *target_thread = NULL; //目标线程 struct binder_node *target_node = NULL; //目标 binder 节点 struct list_head *target_list; //目标 TODO 队列 wait_queue_head_t *target_wait; //目标等待队列 if(reply){ in_reply_to = thread->transaction_stack; …处理 BC_REPLY,暂不关注 }else{ //处理 BC_TRANSACTION if (tr->target.handle) { //handle 不为 0 struct binder_ref *ref; //根据 handle 找到目标 binder 实体节点的引用 ref = binder_get_ref(proc, tr->target.handle); target_node = ref->node; //拿到目标 binder 节点 } else { // handle 为 0 则代表目标 binder 是 service manager // 对于本次调用来说目标就是 service manager target_node = binder_context_mgr_node; } } target_proc = target_node->proc; //拿到目标进程 if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) { struct binder_transaction *tmp; tmp = thread->transaction_stack; while (tmp) { if (tmp->from && tmp->from->proc == target_proc) target_thread = tmp->from; //拿到目标线程 tmp = tmp->from_parent; } } target_list = &target_thread->todo; //拿到目标 TODO 队列 target_wait = &target_thread->wait; //拿到目标等待队列
binder_transaction、binder_work 等结构体在上一篇中有介绍,上面代码中也详细注释了它们的含义。比较关键的是 binder_get_ref() 方法,它是如何找到目标 binder 的呢?这里暂不延伸,下文再做分析。
继续看 binder_transaction() 方法的第 2 个工作,「将数据一次拷贝到目标进程所映射的物理内存块」:
t = kzalloc(sizeof(*t), GFP_KERNEL); //创建用于描述本次 server 端要进行的 transaction tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); //创建用于描述当前调用线程未完成的 transaction if (!reply && !(tr->flags & TF_ONE_WAY)) //将信息记录到 t 中: t->from = thread; //记录调用线程 else t->from = NULL; t->sender_euid = task_euid(proc->tsk); t->to_proc = target_proc; //记录目标进程 t->to_thread = target_thread; //记录目标线程 t->code = tr->code; //记录请求码,getService() 对应的是 GET_SERVICE_TRANSACTION t->flags = tr->flags; //实际申请目标进程所映射的物理内存,准备接收要传输的数据 t->buffer = binder_alloc_buf(target_proc, tr->data_size, tr->offsets_size, !reply && (t->flags & TF_ONE_WAY)); //申请到 t->buffer 后,从用户空间将数据拷贝进来,这里就是一次拷贝数据的地方!! if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t) tr->data.ptr.buffer, tr->data_size)) { return_error = BR_FAILED_REPLY; goto err_copy_data_failed; }
为什么在拷贝之前要先申请物理内存呢?之前介绍 binder_mmap() 方法时详细分析过,虽然 binder_mmap() 直接映射了 (1M-8K) 的虚拟内存,但却只申请了 1 页的物理页面,等到实际使用时再动态申请。也就是说,在 binder_ioctl() 实际传输数据的时候,再通过 binder_alloc_buf() 方法去申请物理内存。
至此已经将要传输的数据拷贝到目标进程,目标进程可以直接读取到了,接下来要做的就是将目标进程要处理的任务记录起来,然后唤醒目标进程,这样在目标进程被唤醒后,才能知道要处理什么任务。
最后来看 binder_transaction() 方法的第 3 个工作,「记录待处理的任务,唤醒目标线程」:
if (reply) { //如果是处理 BC_REPLY,pop 出来栈顶记录的 transaction(实际上是删除链表头元素) binder_pop_transaction(target_thread, in_reply_to); } else if (!(t->flags & TF_ONE_WAY)) { //如果不是 oneway,将 server 端要处理的 transaction 记录到当前调用线程 t->need_reply = 1; t->from_parent = thread->transaction_stack; thread->transaction_stack = t; } else { …暂不关注 oneway 的情况 } t->work.type = BINDER_WORK_TRANSACTION; list_add_tail(&t->work.entry, target_list); //加入目标的处理队列中 tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; //设置调用线程待处理的任务类型 list_add_tail(&tcomplete->entry, &thread->todo); //记录调用线程待处理的任务 if (target_wait) wake_up_interruptible(target_wait); //唤醒目标线程
再次梳理一下,至此已经完成了前四个工作:
- 准备数据,根据命令分发给具体的方法去处理
- 找到目标进程的相关信息
- 将数据一次拷贝到目标进程所映射的物理内存块
- 记录待处理的任务,唤醒目标线程
其中第 1 个工作涉及到的方法为 binder_ioctl() -> binder_get_thread() -> binder_ioctl_write_read() ?-> binder_thread_write() ,主要是一些数据的准备和方法转跳,没做什么实质的事情。而 binder_transaction() ?方法中做了非常重要的 2-4 工作。
剩下的工作还有:
- 调用线程进入休眠
- 目标进程直接拿到数据进行处理,处理完后唤醒调用线程
- 调用线程返回处理结果
可以想到,5 和 6 其实没有时序上的限制,而是并行处理的。下面先来看第 5 个工作:调用线程是如何进入休眠等待服务端执行结果的。
6.binder_thread_read
在唤醒目标线程后,调用线程就执行完 binder_thread_write() 写完了数据,返回到 binder_ioctl_write_read() 方法中,接着执行 binder_thread_read() 方法。
而调用线程的休眠就是在此方法中触发的,下面将 binder_thread_read() 分为两部分来看,首先是是否阻塞当前线程的判断逻辑:
static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, size_t size, binder_size_t *consumed, int non_block){ void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //bwr.read_buffer void __user *ptr = buffer + *consumed; //数据起始地址 void __user *end = buffer + size; //数据结束地址 if (*consumed == 0) { if (put_user(BR_NOOP, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); } //是否要准备睡眠当前线程 wait_for_proc_work = thread->transaction_stack == NULL && list_empty(&thread->todo); if (wait_for_proc_work) { if (non_block) { //non_block 为 false if (!binder_has_proc_work(proc, thread)) ret = -EAGAIN; } else ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread)); } else { if (non_block) { //non_block 为 false if (!binder_has_thread_work(thread)) ret = -EAGAIN; } else ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread)); }
consumed 即用户空间的 bwr.read_consumed,这里是 0 ,所以将一个 BR_NOOP 加到了 ptr 中。
怎么理解 wait_for_proc_work 条件呢?在 binder_transaction() 方法中将 server 端要处理的 transaction 记录到了当前调用线程 thread->transaction_stack 中;将当前调用线程待处理的任务记录到了 thread->todo 中。
所以这里的 thread->transaction_stack 和 thread->todo 都不为空,wait_for_proc_work 为 false,代表不准备阻塞当前线程。
但 wait_for_proc_work 并不是决定是否睡眠的最终条件,接着往下看,其中 non_block 恒为 false,那是否要睡眠当前线程就取决于 binder_has_thread_work() 的返回值,binder_has_thread_work() 方法如下:
static int binder_has_thread_work(struct binder_thread *thread){ return !list_empty(&thread->todo) || thread->return_error != BR_OK || (thread->looper & BINDER_LOOPER_STATE_NEED_RETURN); }
thread->todo 不为空,所以 binder_has_thread_work() 返回 true,当前调用线程不进入休眠,继续往下执行。你可能会有疑问,不是说调用线程的休眠就是在 binder_thread_read() 方法中触发的吗?确实是,只不过不是本次,先接着分析 binder_thread_read() 继续往下要执行的逻辑:
struct binder_work *w; w = list_first_entry(&thread->todo, struct binder_work,entry); switch (w->type) { case BINDER_WORK_TRANSACTION_COMPLETE: { cmd = BR_TRANSACTION_COMPLETE; if (put_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); binder_stat_br(proc, thread, cmd); list_del(&w->entry); //删除 binder_work 在 thread->todo 中的引用 kfree(w); } case BINDER_WORK_NODE{…} case BINDER_WORK_DEAD_BINDER{…} …
在上面 binder_transaction() 方法最后,将 BINDER_WORK_TRANSACTION_COMPLETE 类型的 binder_work 加入到 thread->todo 中。而这里就是对这个 binder_work 进行处理,将一个 BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。
梳理一下目前的逻辑,至此已经顺序执行完 binder_thread_write()、binder_thread_read() 方法,并且在 binder_thread_read() 中往用户空间传输了两个命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE。
inder_transaction() 方法最后,将 BINDER_WORK_TRANSACTION_COMPLETE 类型的 binder_work 加入到 thread->todo 中。而这里就是对这个 binder_work 进行处理,将一个 BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。
梳理一下目前的逻辑,至此已经顺序执行完 binder_thread_write()、binder_thread_read() 方法,并且在 binder_thread_read() 中往用户空间传输了两个命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE。
|