理论
??数据交互的相关步骤:第一步、调用进程通过系统调用进入内核态数据交互这个步骤是拷贝了进程间所需要数据的指针;第二步、在内核态将进程间所需要数据拷贝到对应的进程申请的内存中,这个步骤确实发生了进程间所需要数据拷贝;第三步、对应的进程再将数据从内核态拷贝到用户态,这个步骤拷贝的同样是数据指针。这样总共经过3次数据交互,将数据从调用进程传递到目标进程。其实也是经过了3次数据拷贝,只不过第一次和第三次拷贝的都是Binder机制相关数据结构所需要的数据的拷贝,只有第二步发生了将进程间所需要数据的实际拷贝。
代码实现
Binder交互过程中协议命令
??上图就是Binder事务进行一次交互的协议命令,根据协议命令主要查看数据处理相关的。
第一步
??其中第一次的数据拷贝是在BC_TRANSACTION指令中发生的在BC_TRANSACTION命令中的处理,相关代码在kernel\common\drivers\android\binder.c中
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;
struct binder_context *context = proc->context;
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.cmd == BR_OK) {
int ret;
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
…………
switch (cmd) {
…………
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
break;
}
…………
}
*consumed = ptr - buffer;
}
return 0;
}
??binder_thread_write()函数实现在Binder驱动中,也就是这段代码运行的时候是在内核态。其中变量ptr是用户态内存数据的指针,通过copy_from_user(&tr, ptr, sizeof(tr))将用户态数据拷贝到内核态。这是第一次数据拷贝。其中tr.data.ptr.buffer是进程间交互数据的用户态地址,所以这个步骤只是将用户态数据地址拷贝了过来。
第二步
??接着就是调用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)
{
int ret;
struct binder_transaction *t;
…………
t = kzalloc(sizeof(*t), GFP_KERNEL);
…………
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);
…………
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)) {
binder_user_error("%d:%d got transaction with invalid offsets ptr\n",
proc->pid, thread->pid);
return_error = BR_FAILED_REPLY;
return_error_param = -EFAULT;
return_error_line = __LINE__;
goto err_copy_data_failed;
}
…………
for (buffer_offset = off_start_offset; buffer_offset < off_end_offset;
buffer_offset += sizeof(binder_size_t)) {
struct binder_object_header *hdr;
size_t object_size;
struct binder_object object;
binder_size_t object_offset;
binder_size_t copy_size;
if (binder_alloc_copy_from_buffer(&target_proc->alloc,
&object_offset,
t->buffer,
buffer_offset,
sizeof(object_offset))) {
return_error = BR_FAILED_REPLY;
return_error_param = -EINVAL;
return_error_line = __LINE__;
goto err_bad_offset;
}
copy_size = object_offset - user_offset;
if (copy_size && (user_offset > object_offset ||
binder_alloc_copy_user_to_buffer(
&target_proc->alloc,
t->buffer, user_offset,
user_buffer + user_offset,
copy_size))) {
binder_user_error("%d:%d got transaction with invalid data ptr\n",
proc->pid, thread->pid);
return_error = BR_FAILED_REPLY;
return_error_param = -EFAULT;
return_error_line = __LINE__;
goto err_copy_data_failed;
}
object_size = binder_get_object(target_proc, user_buffer,
t->buffer, object_offset, &object);
…………
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);
if (ret < 0 ||
binder_alloc_copy_to_buffer(&target_proc->alloc,
t->buffer,
object_offset,
fp, sizeof(*fp))) {
return_error = BR_FAILED_REPLY;
return_error_param = ret;
return_error_line = __LINE__;
goto err_translate_failed;
}
} 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);
if (ret < 0 ||
binder_alloc_copy_to_buffer(&target_proc->alloc,
t->buffer,
object_offset,
fp, sizeof(*fp))) {
return_error = BR_FAILED_REPLY;
return_error_param = ret;
return_error_line = __LINE__;
goto err_translate_failed;
}
} break;
case BINDER_TYPE_FD: {
struct binder_fd_object *fp = to_binder_fd_object(hdr);
binder_size_t fd_offset = object_offset +
(uintptr_t)&fp->fd - (uintptr_t)fp;
int ret = binder_translate_fd(fp->fd, fd_offset, t,
thread, in_reply_to);
fp->pad_binder = 0;
if (ret < 0 ||
binder_alloc_copy_to_buffer(&target_proc->alloc,
t->buffer,
object_offset,
fp, sizeof(*fp))) {
return_error = BR_FAILED_REPLY;
return_error_param = ret;
return_error_line = __LINE__;
goto err_translate_failed;
}
} break;
…………
}
}
if (binder_alloc_copy_user_to_buffer(
&target_proc->alloc,
t->buffer, user_offset,
user_buffer + user_offset,
tr->data_size - user_offset)) {
binder_user_error("%d:%d got transaction with invalid data ptr\n",
proc->pid, thread->pid);
return_error = BR_FAILED_REPLY;
return_error_param = -EFAULT;
return_error_line = __LINE__;
goto err_copy_data_failed;
}
……………
}
??这块就是Binder事务交互中,将调用进程的数据在内核态拷贝给被调用进程的内存中进行处理的代码。 ??代码11行用来在目标进程中找到空闲内存,并且实际分配内存页框。找到的空闲内存的大小是根据tr->data_size、tr->offsets_size和extra_buffers_size三个加在一起的和来定的。tr->data_size是数据的大小,tr->offsets_size是描写数据中关于Binder对象位置信息数据的大小,extra_buffers_size是额外数据大小,在BC_TRANSACTION命令中是0。binder_alloc_new_buf()函数就是找到管理的空闲内存,并且实际分配内存页框。 ??代码15行用来将事务数据中的关于Binder对象的相关信息拷贝到刚才找到的空闲内存中。Binder对象的相关信息这块是指,在事务数据中的内存位置,描述每个位置的数据大小是sizeof(binder_size_t)。tr->data.ptr.offsets就是这些数据的内存位置,tr->offsets_size是这些数据的大小。以2个Binder对象来举例子,图1是事务中的数据。这一步骤拷贝之后,被调用进程刚才申请的内存变成图2的内存结构了。
图1 两个个Binder对象传递数据内存
图2 两个个Binder对象被调用进程内存
??代码30行开始的循环,是用来处理数据中的包含的Binder对象的。buffer_offset是在t->buffer中的相对位置。这个位置开始就是刚才拷贝的Binder对象的地址。每个Binder对象的地址的大小是sizeof(binder_size_t)。每次循环增加一个位置的大小,直到所有的处理完毕。
??代码38行,是为了将位置从t->buffer拷贝进局部变量object_offset。
??代码53行,是为了将数据内存中Binder对象之前的数据都拷贝进t->buffer中。copy_size是将要拷贝数据的大小。object_offset是Binder对象的相对位置,user_offset是从用户态地址开始复制数据的相对位置。user_buffer是用户态数据的地址。
??代码66行,取得数据中Binder对象的大小object_size,并且将Binder对象数据放入object。下面接着就是根据Binder对象的类型来做处理,做处理的方法是binder_translate_XXX(),然后再调用binder_alloc_copy_to_buffer()将处理之后的数据拷贝到t->buffer中。当Binder对象是BINDER_TYPE_BINDER、BINDER_TYPE_WEAK_BINDER、BINDER_TYPE_HANDLE、BINDER_TYPE_WEAK_HANDLE、BINDER_TYPE_FD类型,都是这样处理的。
??代码130行,是在前面将Binder对象及之前的数据都拷贝到t->buffer中之后,还可能会有剩余。这块就是处理剩余的数据的,将剩余数据拷贝到t->buffer。
??拷贝完成之后,内存结构就如下图3所示。
图3 复制完成之后被调用进程中数据内存结构
??上面就是第二步所做的实际数据拷贝。将数据从调用进程拷贝到被调用进程的内存之中。这里需要明确的一点,被调用进程的这些拷贝的内存也是被用户态线性地址映射了的。这样再将这些内存地址放到合适的数据结构中,被调用进程返回到用户态,就可以操作它了。
第三步
??这一步是将进程间交互数据地址放到合适的数据结构中,让进程返回到用户态就能操作这些数据了。
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;
void __user *ptr = buffer + *consumed;
void __user *end = buffer + size;
…………
while (1) {
uint32_t cmd;
struct binder_transaction_data_secctx tr;
struct binder_transaction_data *trd = &tr.transaction_data;
struct binder_work *w = NULL;
struct list_head *list = NULL;
struct binder_transaction *t = NULL;
struct binder_thread *t_from;
size_t trsize = sizeof(*trd);
…………
w = binder_dequeue_work_head_ilocked(list);
if (binder_worklist_empty_ilocked(&thread->todo))
thread->process_todo = false;
switch (w->type) {
case BINDER_WORK_TRANSACTION: {
binder_inner_proc_unlock(proc);
t = container_of(w, struct binder_transaction, work);
} break;
…………
}
trd->data_size = t->buffer->data_size;
trd->offsets_size = t->buffer->offsets_size;
trd->data.ptr.buffer = (uintptr_t)t->buffer->user_data;
trd->data.ptr.offsets = trd->data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));
tr.secctx = t->security_ctx;
if (t->security_ctx) {
cmd = BR_TRANSACTION_SEC_CTX;
trsize = sizeof(tr);
}
if (put_user(cmd, (uint32_t __user *)ptr)) {
if (t_from)
binder_thread_dec_tmpref(t_from);
binder_cleanup_transaction(t, "put_user failed",
BR_FAILED_REPLY);
return -EFAULT;
}
ptr += sizeof(uint32_t);
if (copy_to_user(ptr, &tr, trsize)) {
if (t_from)
binder_thread_dec_tmpref(t_from);
binder_cleanup_transaction(t, "copy_to_user failed",
BR_FAILED_REPLY);
return -EFAULT;
}
ptr += trsize;
…………
}
…………
}
??代码33行,t->buffer->user_data就是进程间交互数据的用户态起始线性地址。这里只是将数据地址赋值给了trd->data.ptr.buffer。 ??代码43行,将cmd(可能BR_TRANSACTION或BR_TRANSACTION_SEC_CTX)复制到用户态的内存ptr中。 ??代码53行,将包含进程交互数据地址的数据结构binder_transaction_data_secctx变量tr也拷贝到用户态地址内存ptr中。 ??这样,等进程返回到用户态,就能先拿到binder_transaction_data_secctx变量,然后通过它再找到binder_transaction_data结构,最后就能找到它的成员data.ptr.buffer,它就是数据的地址。而数据大小在其成员data_size,数据中包含的Binder对象的数据地址是成员data.ptr.offsets,Binder对象位置大小是成员offsets_size。 ??以上就是Binder机制中关于数据一次拷贝的过程,可见在真正拷贝数据的是第二步,在内核态执行的数据拷贝。
|