3.5 receive async requet 接收异步方式发送过来的请求和接收同步方式发来的请求基本一样,不同的是,在将binder_transaction的数据转换到binder_transaction_data之后,将会释放掉binder_transaction数据结构的空间。 由于binder驱动对异步通信做了分流的处理,如果当前目标进程已经有一个异步通信正在处理,那么为了保证同步通信的实时性,所以会将后来发给该进程的异步通信任务放在一个等待队列async_todo中,直到前面那个异步通信任务完成后才会从异步等待队列中取出一个任务放进前次处理异步任务的task的todo队列中去。 (从这里可以看出,如果某个线程正在处理异步任务,当完成的时候发现异步等待队列中还有异步任务需要处理,那么这个等待的异步任务也会被当前这个线程处理,直到这个时间段内的异步任务处理完。隔了段时间之后,如果再有异步任务到来的话,此时驱动可能会分配其他的线程来处理接下来时间段内的异步任务。简单点说,在某线程执行任何一个异步任务未完成之前就已经排到异步等待队列中来的异步任务,都将会由这个线程来执行。)。 不过,我们在binder_thread_read()函数的最后没有看到将异步任务移入线程的todo队列中的动作,这个函数和异步请求接收相关的只有如下地方: static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, void __user *buffer, int size, signed long *consumed, int non_block) { … while (1) { if (!t) // 非 BINDER_WORK_TRANSACTION 的情况,放弃执行后面的重新循环 continue; … if (t->from) { // 记录发送线程的binder_thread … // 同步传输时 } else { // reply 或者异步传输时 tr.sender_pid = 0; } … list_del(&t->work.entry); t->buffer->allow_user_free = 1; if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {// 同步传输 t->to_parent = thread->transaction_stack; t->to_thread = thread; thread->transaction_stack = t; } else { /* 如果收到的是回复数据或者而是异步请求,这里将会释放掉这次单边 传输的binder_transaction结构体,另外所有的传输的binder_buffer结构体空间都是通过上层发送命令BC_FREE_BUFFER来通知binder驱动释放的,因为这部分空间是驱动在管理。*/ t->buffer->transaction = NULL; kfree(t); binder_stats_deleted(BINDER_STAT_TRANSACTION); } break; }// while(1) } 那究竟是在哪里移入下一个异步等待任务的呢?其实我们可以想一下,这个binder_thread_read()函数执行完的时候,异步任务还没开始执行,驱动还会将binder_transaction_data结构体传回上层程序,上层程序才真正开始执行异步任务,不过通常上层应用程序在执行完异步任务(其实不只是异步任务,应该是所有类型的任务)被执行完,都应该发送BC_FREE_BUFFER这个命令到binder驱动,通知驱动释放掉一次单边传输时的binder_buffer内存空间。到这里之后,这个异步任务才算得上真正完成。所以我们的前面提到的移入异步任务的事情就是在这个时候做的,请看源码: int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,void __user *buffer, int size, signed long *consumed) { uint32_t cmd; void __user *ptr = buffer + *consumed; void __user *end = buffer + size;
while (ptr < end && thread->return_error == BR_OK) { … switch (cmd) { … case BC_FREE_BUFFER: { // BC_FREE_BUFFER = _IOW('c', 3, int), // cmd | data_ptr (data_ptr是指binder_buffer.data开始地址)
void __user *data_ptr; struct binder_buffer *buffer;
if (get_user(data_ptr, (void * __user *)ptr)) return -EFAULT; ptr += sizeof(void *);
buffer = binder_buffer_lookup(proc, data_ptr); // 取出binder_buffer结构体指针 … /* 当前释放的binder_buffer如果是异步传输所用,并且目标binder_node存在 (非回复的情况下)。*/ if (buffer->async_transaction && buffer->target_node) { BUG_ON(!buffer->target_node->has_async_transaction);// 异常检查 if (list_empty(&buffer->target_node->async_todo)) // 为NULL,表明没有异步任务在等待执行了。 buffer->target_node->has_async_transaction = 0; else // 不为NULL,将最早等待的异步任务加入当前task的todo队列中 list_move_tail(buffer->target_node->async_todo.next, &thread->todo); } … }// switch(cmd) … } // while(…) return 0; } 这样的话,当这个task再次调用ioctl读或者调用poll(sslect)的时候,都会发现私有todo队列中有异步任务需要执行的。
3.6 transaction reply 当接收者task处理完请求之后,也会在上层的用户空间组织一个binder_transaction_data的数据结构体用ioctl传递进binder驱动,这个时候用到的命令字就是BC_REPLY(前面发送请求的命令字是BC_TRANSACTION)。 同样ioctl()调用binder_thread_write(),最后调用到binder_transaction()函数。 binder_transaction(proc, thread, &tr, cmd == BC_REPLY);这里最后传递进去的参数为1。 static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply) { struct binder_transaction *t; struct binder_work *tcomplete; … struct binder_proc *target_proc; struct binder_thread *target_thread = NULL; struct binder_node *target_node = NULL; struct list_head *target_list; wait_queue_head_t *target_wait; struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; … if (reply) { // 发送回复数据 in_reply_to = thread->transaction_stack; /* 一次单边传输过程,不管是同步还是异步,都只存在一个binder_transacti on结构体,同步传输返回信息也是使用的发送请求时创建的binder_transaction结构体。*/ if (in_reply_to == NULL) { …/* 只要是同步传输,都会有发送回复数据的过程。got reply transaction wi th no transaction stack。*/ } binder_set_nice(in_reply_to->saved_priority); // 将当前task的nice优先级还原成处理接收数据之前的本来拥有的优先级 … /* 当前线程在之前接收数据的时候保存了自己的线程地址到binder_transactio n.to_thread中,这里用来做校验。*/ if (in_reply_to->to_thread != thread) { …// 验证不成功的错误处理 } thread->transaction_stack = in_reply_to->to_parent; /* 在当期这次同步通信中,接收方将发送请求时的binder_transaction结构体从 传输链表上摘下。这个时候这个结构体还挂在发送请求的发送方的transaction_stack上。这个结构体在什么时候从发送请求方的transaction_stack链表上摘下呢?请往下看。*/ target_thread = in_reply_to->from; // 将之前的请求发送方变成回复数据接收方。 if (target_thread == NULL){//唯有BC_REPLY和异步传输时这个from才为NULL …// 错误处理 } if (target_thread->transaction_stack != in_reply_to) { /* 如前所述,一次单边传输只有一个binder_transaction,所以发送方和接 收方线程的transaction_stack指向同一个binder_transaction结构体。如果这里不相等,那就说明前面发送请求就出了问题,不过,这种错误几乎不会发生,但是必须得留这么一手。*/ … goto err_dead_binder; } target_proc = target_thread->proc; /* 好像没有看到设置target_node这个指针呢?对,在发送回复数据的时候确实 不用这个binder_node了。为什么?一般的接收方都是server,是具有binder实体的,而发送方一般是client,是没有binder实体,所以这个不用设置。除非发送方和接收方都具有binder实体,才有可能。 */ }else { // 发送的是请求数据 … ref = binder_get_ref(proc, tr->target.handle); target_node = ref->node; 或者 target_node = binder_context_mgr_node; … target_proc = target_node->proc; … } // else
if (target_thread) {// 发送回复数据时,这个由binder驱动记录,非NULL target_list = &target_thread->todo; target_wait = &target_thread->wait; } else { target_list = &target_proc->todo; target_wait = &target_proc->wait; } … // 申请binder_transaction和binder_work的内存空间 if (!reply && !(tr->flags & TF_ONE_WAY)) t->from = thread; else // 发送回复或者是异步发送请求 t->from = NULL; … t->to_proc = target_proc; t->to_thread = target_thread; … /* 申请binder_buffer的内存空间,binder_buffer.async_transaction = 1,从 binder_alloc_buf()函数的最后一个参数可以看出,如果是发送回复数据的时候,binder_transaction_data.flags的TF_ONE_WAY需要为1才行,因为既然是发送的回复数据,那肯定就不需要再让对方回信息了,除非没完没了。*/ t->buffer->allow_user_free = 0; t->buffer->debug_id = t->debug_id; t->buffer->transaction = t; t->buffer->target_node = target_node; // BC_REPLY时应该为NULL
…// 完成数据在进程间的拷贝,同时处理包含在数据中的falt_binder_object结构体
if (reply) { BUG_ON(t->buffer->async_transaction != 0); // 等于1才ok,否则就是异常 binder_pop_transaction(target_thread, in_reply_to);// note3.6_1 /* 这里表示一次同步通讯过程中,接收方已经将回复数据发送给发送方,这 里就可以pop出之前,发送方给接收方发送时创建的binder_transaction数据结构,释放其占用的内存空间。*/ /* 这次同步通信中,发送请求时候产生的binder_transaction数据结构在该函数前面已经从请求接收方的stack链表摘下,这里就将其从发送请求方的stack链表上摘下。 */ } else if (!(t->flags & TF_ONE_WAY)) { // 同步请求 … }else { // 异步请求 … } 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); } return; …// 错误处理 } /************************** note3.6_1 **********************/ static void binder_pop_transaction(struct binder_thread *target_thread, struct binder_transaction *t) { if (target_thread) { BUG_ON(target_thread->transaction_stack != t); BUG_ON(target_thread->transaction_stack->from != target_thread); // 异常检查 target_thread->transaction_stack = target_thread->transaction_stack->from_parent; t->from = NULL; } t->need_reply = 0; if (t->buffer) t->buffer->transaction = NULL; kfree(t); // 释放binder_transaction所占的空间。 binder_stats_deleted(BINDER_STAT_TRANSACTION); } /************************** note3.6_1 **********************/
3.7 receive reply 接收回复数据和接收请求数据大同小异,如下: static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, void __user *buffer, int size, signed long *consumed, int non_block) { void __user *ptr = buffer + *consumed; void __user *end = buffer + size; … while (1) { uint32_t cmd; struct binder_transaction_data tr; struct binder_work *w; struct binder_transaction *t = NULL; … if (t->buffer->target_node) { // 收到的是请求数据 … } else { // 收到的是BC_REPLY tr.target.ptr = NULL; tr.cookie = NULL; cmd = BR_REPLY; } … if (t->from) { // 收到的是同步请求数据 … } else { // 收到的是异步请求数据或者是REPLY数据 tr.sender_pid = 0; } … t->buffer->allow_user_free = 1; if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {// 同步传输 … } else { /* 如果收到的是回复数据或者是异步请求,这里所做的是一样,释放掉 这个单边传输是产生的binder_transaction数据结构所占内存。*/ t->buffer->transaction = NULL; kfree(t); binder_stats_deleted(BINDER_STAT_TRANSACTION); } break; }// while(1) done: *consumed = ptr - buffer; // 实际接收到的字节数 … //检查空闲线程是否够用,不够的话,申请再孵化。 return 0; } // binder_thread_read()
3.8 关于发送请求时的一点优化 (引用universus的原话)当进程P1的线程T1向进程P2发送请求时,驱动会先查看一下线程T1是否也正在处理来自P2某个线程请求但尚未完成(没有发送回复)。这种情况通常发生在两个进程都有Binder实体并互相对发时请求时。假如驱动在进程P2中发现了这样的线程,比如说T2,就会要求T2来处理T1的这次请求。因为T2既然向T1发送了请求尚未得到返回包,说明T2肯定(或将会)阻塞在读取返回包的状态。 这时候可以让T2顺便做点事情,总比等在那里闲着好。 这个场景之下,binder驱动查看T1是否正在处理来自P2某线程的请求但尚未完成的工作是在binder_transaction()函数中完成的,也就是前文2.3节中提到过,但是没有分析,现在又了前面这些分析,应该对binder_transaction、binder_buffer结构体,特别是binder_transaction.transaction_stack这个链表有所理解,这个时候来分析这个优化才是最佳时机。 代码量很小,先把代码贴上来吧! static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply) { … if (reply) { … }else { … /* 这个优化是针对同步通讯的,所以对于异步通讯就不存在这个优化了。 如上面的情景:本来是T1向P2发送的同步请求(交互1),ioctl运行到这里,而如果在这之前,T1也正在处理来自P2进程T2的请求还没结束(交互2),那么交互2中接收方T1也就是交互1的发送方了。其实这里完全可以不用优化,直接将交互1的请求投进P2进程的全局todo队列进行处理,但是为了提高效率和节省资源,反正交互2的发送方T2也在等待接收方的回复,也没有做事,这个时候让其做点事情岂不是更好,所以出于这样的考虑,才有了如下的优化过程。 交互2中发送方T2和接收方T1的binder_thread.transaction_stack都应该是指向同一个binder_transaction结构体,而交互1中的发送方T1也就是交互2中的接收方。 */ if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) { struct binder_transaction *tmp; tmp = thread->transaction_stack; if (tmp->to_thread != thread) { … // 需要当前task是前次同步传输中的接收方才能进行发送优化 return_error = BR_FAILED_REPLY; goto err_bad_call_stack; } while (tmp) { /* 如果此时T1在这之前,不仅仅只是接收到T2的请求,而 且还接收到了其他很多线程的请求均没有完成的话,那么这个transaction_stack链表中就有多个binder_transaction结构体存在,所以需要查找。查找的时候第一个条件都满足,只是第二个条件就不好满足了,就要一个一个比较,需要交互1的目标binder_proc匹配。 */ if (tmp->from && tmp->from->proc == target_proc) target_thread = tmp->from; // 找到之后,直接将交互2的发送者设置成交互1的接收者。 tmp = tmp->from_parent; // 否则,继续查找链表 } } // if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) } // else if (target_thread) { // 如果这个优化有结果,找到了对应的线程 e->to_thread = target_thread->pid; target_list = &target_thread->todo; // 目标任务列表 target_wait = &target_thread->wait; // 目标线程等待队列 } else {// 优化不成功的话,将任务送到进程对于的全局任务队列 target_list = &target_proc->todo; target_wait = &target_proc->wait; // 同上 } … } // binder_transaction()
四、BINDER_WRITE_READ其他功能 BC_FREE_BUFFER和binder实体死亡通知相关的功能在后面的文章中单独讨论。其余的 命令都比较简单,看源码就可以明白。
五、其他ioctl功能实现 ioctl的其余命令也没有几个,BINDER_SET_MAX_THREADS,BINDER_SET_CONTEXT_MGR, BINDER_THREAD_EXIT,BINDER_VERSION,也都是比较简单的,看源码即可明白。
参考资料: http://blog.csdn.net/universus/archive/2011/02/27/6211589.aspx Android Binder设计与实现 – 设计篇 |
|