binder IPC TRANSACTION过程分析(BC_TRANSACTION->Binder Driver)

来源:互联网 发布:网络管理app 编辑:程序博客网 时间:2024/06/05 03:20

在Binder IPC通信过程中,进程间通信都要先通过向Binder驱动发送BC_XXX命令,然后Binder 驱动稍做处理后通过对应的BR_XXX将命令转给给目标进程。

如果有返回值,进程也是先将返回结果以BC_REPLY的形式先发给Binder驱动,然后通过驱动以BR_REPLY命令转发。


PS:从Driver发出的命令以BR开始,而发往Driver的命令以BC开头。


Binder1通过BC_Transaction将通信数据发到Binder Driver,Binder Driver通过BR_Transaction将命令转发给Binder2去处理,最后都是通过BC_REPLY的命令将返回结果传回Binder Driver,Binder Driver再通过BR_REPLY将返回结果转发给Binder1。

相关函数binder_transaction(...)

先看BC_TRANSACTION命令的处理过程(binder1->Driver)。

首先,第一步,Binder驱动判断当前命令接收方是Service Manager还是普通的Server端,判断依据是tr->target.handle.if (tr->target.handle == 0)   表示该命令是发送特殊结点,即Service Manager,而else  针对一般情况,我们需要判断Binder驱动中有没有对应的结点引用,正常情况下应该是能够找到handle对应的Binder结点引用的。通过结点引用,我们就可以定位到处理命令的Binder结点(实体结点)。总之,我们要先知道这个命令是要发往何处。

else {if (tr->target.handle) {struct binder_ref *ref;ref = binder_get_ref(proc, tr->target.handle);if (ref == NULL) {binder_user_error("%d:%d got transaction to invalid handle\n",proc->pid, thread->pid);return_error = BR_FAILED_REPLY;goto err_invalid_target_handle;}target_node = ref->node;} else {target_node = binder_context_mgr_node;if (target_node == NULL) {return_error = BR_DEAD_REPLY;goto err_no_context_mgr_node;}}

有了Binder结点的信息,我们就可以知道它所处的进程了。

target_proc = target_node->proc;if (target_proc == NULL) {return_error = BR_DEAD_REPLY;goto err_dead_binder;}

然后会有一个安全检查,主要是判断这通信双方所处的进程能不能传输数据。

if (security_binder_transaction(proc->tsk, target_proc->tsk) < 0) {return_error = BR_FAILED_REPLY;goto err_invalid_target_handle;}

接下来,对于同步通信(即two way),且当前transaction_stack链表不为空的话(也即表示当前线程至少存在一个通信会话),那么尝试获取目标线程(即通信的另一方)。因为有可能之前通信双方已经建立了通信会话,这样的话就可以重复利用。

if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {struct binder_transaction *tmp;tmp = thread->transaction_stack;if (tmp->to_thread != thread) {binder_user_error("%d:%d got new transaction with bad transaction stack, transaction %d has target %d:%d\n",proc->pid, thread->pid, tmp->debug_id,tmp->to_proc ? tmp->to_proc->pid : 0,tmp->to_thread ?tmp->to_thread->pid : 0);return_error = BR_FAILED_REPLY;goto err_bad_call_stack;}while (tmp) {if (tmp->from && tmp->from->proc == target_proc)target_thread = tmp->from;tmp = tmp->from_parent;}}}

这样,我们就知道要唤醒哪个等待队列,如果通信的另一方已经存在处理的线程,那么将唤醒对应的线程处理。如果是第一次,那么先唤醒进程等待队列,由进程后续处理。

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;}

接下来,就是将Binder1通过BC_TRANSACTION传过来的一些数据(struct binder_transaction)复制到目标进程的buffer中,之后就可以将其发送目标进程了。

t = kzalloc(sizeof(*t), GFP_KERNEL);if (t == NULL) {return_error = BR_FAILED_REPLY;goto err_alloc_t_failed;}

同时,也要告诉当前进程BC_TRANSACTION命令已经发送完成,所以需要创建相应的数据结构struct binder_work实例:

tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);if (tcomplete == NULL) {return_error = BR_FAILED_REPLY;goto err_alloc_tcomplete_failed;}

接下来就是复制数据:

对于需要对方发送返回信息的情况,此时需要记录下发送方的信息:

if (!reply && !(tr->flags & TF_ONE_WAY))t->from = thread;elset->from = NULL;

还有其他的一些重要信息:

t->sender_euid = proc->tsk->cred->euid;t->to_proc = target_proc;t->to_thread = target_thread;t->code = tr->code;t->flags = tr->flags;t->priority = task_nice(current);

以上信息通信Binder驱动就能搞定,接下来进入实质阶段,即从发送方进程复制数据:

首先需要创建一个struct binder_buffer结构体实例,通信数据将存放在该结构体变量中。

t->buffer = binder_alloc_buf(target_proc, tr->data_size,tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));if (t->buffer == NULL) {return_error = BR_FAILED_REPLY;goto err_binder_alloc_buf_failed;}t->buffer->allow_user_free = 0;t->buffer->debug_id = t->debug_id;t->buffer->transaction = t;t->buffer->target_node = target_node;trace_binder_transaction_alloc_buf(t->buffer);if (target_node)binder_inc_node(target_node, 1, 0, NULL);
将发送方进程传递过来的信息复制过来:
offp = (binder_size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)   tr->data.ptr.buffer, tr->data_size)) {binder_user_error("%d:%d got transaction with invalid data ptr\n",proc->pid, thread->pid);return_error = BR_FAILED_REPLY;goto err_copy_data_failed;}if (copy_from_user(offp, (const void __user *)(uintptr_t)   tr->data.ptr.offsets, tr->offsets_size)) {binder_user_error("%d:%d got transaction with invalid offsets ptr\n",proc->pid, thread->pid);return_error = BR_FAILED_REPLY;goto err_copy_data_failed;}     ...off_end = (void *)offp + tr->offsets_size;

接下来的部分是BC_TRANSACTION命令处理的重点部分,即要将发送方命令数据中引用的一些Binder引用或实体找出来,并按如下规则处理:

如果当前传输给目标进程中包含一个本地binder对象,首先需要在Binder驱动中在当前进程创建一个实体结点(如果不存在的话)

fp = (struct flat_binder_object *)(t->buffer->data + *offp);switch (fp->type) {case BINDER_TYPE_BINDER:case BINDER_TYPE_WEAK_BINDER: {struct binder_ref *ref;struct binder_node *node = binder_get_node(proc, fp->binder);if (node == NULL) {node = binder_new_node(proc, fp->binder, fp->cookie);if (node == NULL) {return_error = BR_FAILED_REPLY;goto err_binder_new_node_failed;}node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK;node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);}


然后在目标进程中为其创建一个引用结点,并相应的修改fp的类型值:对于BINDER_TYPE_BINDER类型的结点,修改为BINDER_TYPE_HANDLE,而对于BINDER_TYPE_WEAK_BINDER,修改为BINDER_TYPE_WEAK_HANDLE。

ref = binder_get_ref_for_node(target_proc, node);if (ref == NULL) {return_error = BR_FAILED_REPLY;goto err_binder_get_ref_for_node_failed;}if (fp->type == BINDER_TYPE_BINDER)fp->type = BINDER_TYPE_HANDLE;elsefp->type = BINDER_TYPE_WEAK_HANDLE;fp->handle = ref->desc;binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE,       &thread->todo);

如果当前传输给目标进程中包含一个远程binder对象的句柄引用,那以首先获得它对应的Binder结点引用对象,

case BINDER_TYPE_HANDLE:case BINDER_TYPE_WEAK_HANDLE: {struct binder_ref *ref = binder_get_ref(proc, fp->handle);if (ref == NULL) {binder_user_error("%d:%d got transaction with invalid handle, %d\n",proc->pid,thread->pid, fp->handle);return_error = BR_FAILED_REPLY;goto err_binder_get_ref_failed;}

接下来需要判断它对应的实体结点是否处于目标进程当中,如果是的话,则相应地也需要修改fp的类型值:对于BINDER_TYPE_HANDLE,修改为BINDER_TYPE_BINDER,对于BINDER_TYPE_WEAK_BINDER,修改为BINDER_TYPE_WEAK_BINDER。

if (ref->node->proc == target_proc) {if (fp->type == BINDER_TYPE_HANDLE)fp->type = BINDER_TYPE_BINDER;elsefp->type = BINDER_TYPE_WEAK_BINDER;fp->binder = ref->node->ptr;fp->cookie = ref->node->cookie;binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);trace_binder_transaction_ref_to_node(t, ref);binder_debug(BINDER_DEBUG_TRANSACTION,     "        ref %d desc %d -> node %d u%016llx\n",     ref->debug_id, ref->desc, ref->node->debug_id,     (u64)ref->node->ptr);}

如果对应的实体结点不是处于目标进程当中,那么则需要在目标进程中获取或创建该实体结点的引用对象。

else {struct binder_ref *new_ref;new_ref = binder_get_ref_for_node(target_proc, ref->node);if (new_ref == NULL) {return_error = BR_FAILED_REPLY;goto err_binder_get_ref_for_node_failed;}fp->handle = new_ref->desc;binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);trace_binder_transaction_ref_to_ref(t, ref,    new_ref);

当然,最后都需要增加引用计数。


如果传输的数据包含一个文件描述符,则需要做的事情就是在目标进程中分配一个新的文件描述,并与对应的struct file类型的对象关联。这样两个进程中的文件描述符关联的文件是同一个文件了。此时,需要修改fp的handle值为新的文件描述符。

file = fget(fp->handle);if (file == NULL) {binder_user_error("%d:%d got transaction with invalid fd, %d\n",proc->pid, thread->pid, fp->handle);return_error = BR_FAILED_REPLY;goto err_fget_failed;}...target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);if (target_fd < 0) {fput(file);return_error = BR_FAILED_REPLY;goto err_get_unused_fd_failed;}task_fd_install(target_proc, target_fd, file);                        .../* TODO: fput? */fp->handle = target_fd;

处理完这些事情后,接下来就是准备提交到目标进程或线程的工作队列中去执行了。有一点需要注意的是,如果目标结点上已经存在了一个未完成的异步处理,则接下来的异步transaction就会暂时分流到目标结点的async_todo等待队列中去,延后处理。

else {BUG_ON(target_node == NULL);BUG_ON(t->buffer->async_transaction != 1);if (target_node->has_async_transaction) {target_list = &target_node->async_todo;target_wait = NULL;} elsetarget_node->has_async_transaction = 1;}

向当前线程反馈信息:

tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;list_add_tail(&tcomplete->entry, &thread->todo);

唤醒目标进程等待队列:

t->work.type = BINDER_WORK_TRANSACTION;list_add_tail(&t->work.entry, target_list);        ...if (target_wait)wake_up_interruptible(target_wait);







0 0