上一篇笔记我们看到了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_ref
ref = 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_node
node = binder_get_node(proc, fp->binder);
if (!node) {
// 2 如果没有查找到,为调用进程创建一个binder_node
node = binder_new_node(proc, fp);
if (!node)
return -ENOMEM;
}
// 3 查找目标进程是否已经存储有这个binder_ref,如果没有则为其创建一个binder_ref
ret = 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;
else
fp->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对象是如何传输的,到这里也大概也了解了,目的也算达成,后续有机会再去研究共享内存机制。