上一篇笔记我们看到了binder_transaction,这个方法很长,这一篇我们将把这个方法拆分开来看binder_transaction做了什么,从而学习binder是如何跨进程通信的。
1 binder_transaction
static void binder_transaction(struct binder_proc *proc,struct binder_thread *thread,struct binder_transaction_data *tr, int reply,binder_size_t extra_buffers_size)
首先我们来看函数的参数,第一个参数proc为调用进程的binder_proc
(也就是MediaServer进程所有的binder_proc
);第二个参数thread我们这里不讨论,主要用于多任务通信;第三个参数tr为从用户空间拷贝过来的binder_transaction_data
。
函数一开始声明了一串变量,我们挑出一部分给出注释:
// 目标进程的binder_proc struct binder_proc *target_proc = NULL;// 目标进程的binder_node struct binder_node *target_node = NULL;// 上下文(servicemanager)struct binder_context *context = proc->context;// 用户空间数据指针const void __user *user_buffer = (const void __user *)(uintptr_t)tr->data.ptr.buffer;
由于我们这边传递的cmd为BC_TRANSACTION,所以这里先不看reply分支的内容:
// 1. 判断target handle是否为0,如果不为0则进入以下分支if (tr->target.handle) {struct binder_ref *ref;// 2. 查找binder_refref = binder_get_ref_olocked(proc, tr->target.handle,true);if (ref) {target_node = binder_get_node_refs_for_txn(ref->node, &target_proc,&return_error);} } else {// 3. 如果target handle为0,如果不为0则target为servicemanager// servicemanager的binder_node可以从proc->context中获得target_node = context->binder_context_mgr_node;if (target_node)target_node = binder_get_node_refs_for_txn(target_node, &target_proc,&return_error);}
以上代码是根据Handle查找目标进程的binder_node
,这里分了两种情况:
- handle为0,说明目标进程为ServiceManager,
binder_context
存储的binder_node
即为ServiceManager进程所属的。 - handle不为0,说明是其他进程,这里要引出一个新的结构体变量binder_ref,这个东西在我们每次从servicemanager获取服务代理时,由binder驱动帮助我们创建,binder_ref用于存储服务的进程信息,除此之外binder驱动为该代理创建一个handle也存储在binder_ref中,有了这个handle就可以找到目标进程了,最后把创建的binder_ref存储在调用进程的
binder_proc
的一个红黑树列表中。
这里讲的可能比较拗口,用一张图来表示下:
我们还是回到handle为0的情况,调用servicemanager注册服务的流程中。接下来这部分我没有仔细去研究,没太看懂,只能写一些我的猜测在这里,大概就是从用户空间拷贝我们一开始打包的Parcel数据到目标进程的共享内存当中:
struct binder_transaction *t;t = kzalloc(sizeof(*t), GFP_KERNEL);t->to_proc = target_proc;t->to_thread = target_thread;t->code = tr->code;t->flags = tr->flags;t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,tr->offsets_size, extra_buffers_size,!reply && (t->flags & TF_ONE_WAY), current->tgid);t->buffer->transaction = t;t->buffer->target_node = target_node;t->buffer->clear_on_free = !!(t->flags & TF_CLEAR_BUF);if (binder_alloc_copy_user_to_buffer(&target_proc->alloc,t->buffer,ALIGN(tr->data_size, sizeof(void *)),(const void __user *)(uintptr_t)tr->data.ptr.offsets,tr->offsets_size)) {}
仅仅做完拷贝还没结束,还需要对拷贝的buffer中的一些特殊内容做一些处理,我这里只看是如何处理传递过来的binder对象的:
hdr = &object.hdr;off_min = object_offset + object_size;switch (hdr->type) {case BINDER_TYPE_BINDER:case BINDER_TYPE_WEAK_BINDER: {struct flat_binder_object *fp;fp = to_flat_binder_object(hdr);ret = binder_translate_binder(fp, t, thread);} break;case BINDER_TYPE_HANDLE:case BINDER_TYPE_WEAK_HANDLE: {struct flat_binder_object *fp;fp = to_flat_binder_object(hdr);ret = binder_translate_handle(fp, t, thread);} break;
这里主要看binder_translate_binder是如何translate处理的:
static int binder_translate_binder(struct flat_binder_object *fp,struct binder_transaction *t,struct binder_thread *thread)
{struct binder_node *node;struct binder_proc *proc = thread->proc;struct binder_proc *target_proc = t->to_proc;struct binder_ref_data rdata;int ret = 0;// 1 从调用进程中查找要传递的binder对象的binder_nodenode = binder_get_node(proc, fp->binder);if (!node) {// 2 如果没有查找到,为调用进程创建一个binder_nodenode = binder_new_node(proc, fp);if (!node)return -ENOMEM;}// 3 查找目标进程是否已经存储有这个binder_ref,如果没有则为其创建一个binder_refret = binder_inc_ref_for_node(target_proc, node,fp->hdr.type == BINDER_TYPE_BINDER,&thread->todo, &rdata);if (ret)goto done;// 将binder实体转换为了引用if (fp->hdr.type == BINDER_TYPE_BINDER)fp->hdr.type = BINDER_TYPE_HANDLE;elsefp->hdr.type = BINDER_TYPE_WEAK_HANDLE;fp->binder = 0;fp->handle = rdata.desc;fp->cookie = 0;
done:binder_put_node(node);return ret;
}
这里主要做了如下几件事情:
- 从调用进程中查找要传递的binder对象的
binder_node
,binder_proc
中同样有一个红黑树链表存储有该进程中的binder_node
对象; - 如果没有查找到,为调用进程创建一个
binder_node
,说明传递的对象是属于调用进程的; - 查找目标进程是否已经有了
binder_node
对应的binder_ref
,如果没有则说明目标进程还没有这个要传递的binder对象的引用,为目标进程创建一个binder_ref
引用(handle),存储到flat_binder_object
中写到共享内存当中。
binder驱动会将我们传递的binder实体转化为属于调用进程的binder_node
,并且提供一个binder_ref
给目标进程,目标进程持有的永远都是代理。
到这里,binder数据的传输就结束了。
总结:这一篇笔记写的比较水,最重要的共享内存并没有理解清楚,有想了解的小伙伴可以去看看其他大神的博文。其实我的目标是研究binder对象是如何传输的,到这里也大概也了解了,目的也算达成,后续有机会再去研究共享内存机制。