本篇是 service 注册的第二篇,主要描述应用层、binder驱动对写入数据的处理。
向 binder 写入数据 注册服务将调用 BpServiceManager::addService():
1 2 3 4 5 6 7 8 9 10 11 12 virtual status_t addService (const String16& name, const sp<IBinder>& service, bool allowIsolated) { Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); data.writeInt32(allowIsolated ? 1 : 0 ); status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); return err == NO_ERROR ? reply.readExceptionCode() : err; }
这里通过 parcel.writeStrongBinder() 写入 IBinder 对象。由于是注册服务,这里的 IBinder 是一个 BBinder。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 status_t Parcel::writeStrongBinder(const sp<IBinder>& val){ return flatten_binder(ProcessState::self(), val, this ); } status_t flatten_binder(const sp<ProcessState>& , const sp<IBinder>& binder, Parcel* out) { flat_binder_object obj; IBinder* local = binder->localBinder(); obj.type = BINDER_TYPE_BINDER; obj.binder = reinterpret_cast <uintptr_t >(local->getWeakRefs()); obj.cookie = reinterpret_cast <uintptr_t >(local); return finish_flatten_binder(binder, obj, out); } inline static status_t finish_flatten_binder ( const sp<IBinder>& , const flat_binder_object& flat, Parcel* out) { return out->writeObject(flat, false ); }
前面我们提到,这里传递进来的 IBinder 实际上是一个 BBinder,所以 localBinder() 所指向的是:
1 2 3 4 5 BBinder* BBinder::localBinder() { return this ; }
于是,向 flat_binder_object 写入的,是 BBinder 的地址。也是通过这个地址,在 binder 驱动收到数据后,能够把数据回送给 BBinder。关于数据的接收,我们以后再讨论。
另一个需要特别注意的是,type 为 BINDER_TYPE_BINDER。
把数据都写入 Parcel 后,执行 remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);。
其中,remote() 返回的是前面我们拿到的指向 context manager 的 BpBinder,前面我们把它存在了 BpRefBase::mRemote 里。
随后,BpBinder 又通过 IPCThreadState 执行实际的写入数据操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 status_t BpBinder::transact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { if (mAlive) { status_t status = IPCThreadState::self()->transact( mHandle, code, data, reply, flags); if (status == DEAD_OBJECT) mAlive = 0 ; return status; } return DEAD_OBJECT; } status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL ); if (err != NO_ERROR) { if (reply) reply->setError(err); return (mLastError = err); } if ((flags & TF_ONE_WAY) == 0 ) { if (reply) { err = waitForResponse(reply); } else { Parcel fakeReply; err = waitForResponse(&fakeReply); } } else { err = waitForResponse(NULL , NULL ); } }
调用 writeTransactionData() 写数据
如果出错,直接返回
调用不同的 waitForResponse() 写入并读取返回的数据
下面我们先看看 writeTransactionData():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t * statusBuffer) { binder_transaction_data tr; tr.target.ptr = 0 ; tr.target.handle = handle; tr.code = code; tr.flags = binderFlags; tr.cookie = 0 ; tr.sender_pid = 0 ; tr.sender_euid = 0 ; tr.data_size = data.ipcDataSize(); tr.data.ptr.buffer = data.ipcData(); tr.offsets_size = data.ipcObjectsCount() * sizeof (binder_size_t ); tr.data.ptr.offsets = data.ipcObjects(); mOut.writeInt32(cmd); mOut.write(&tr, sizeof (tr)); return NO_ERROR; }
几个值得注意的地方:
cmd 为 BC_TRANSACTION。
tr.data.ptr.buffer 指向实际的普通数据
tr.data.ptr.offsets 指向 objects 的偏移数组。也就是前面我们通过 writeObject(flat, false) 写入的 flat_binder_object。
mOut 同样是一个 Parcel,我们将 cmd 和所构造的 binder_transaction_data 放在了这个 mOut 里面。比较容易令人迷惑的是,虽然函数名叫 writeTransactionData,实际上只是把数据写入了 mOut 里(还没有写入 binder 驱动)。
写入数据后如下图所示:
waitForResponse() 的第二个参数带有默认实参,所有上面的三个调用,acquireResult 都是 NULL。
1 2 3 4 5 6 7 8 9 10 11 status_t waitForResponse(Parcel *reply, status_t *acquireResult=NULL ); status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){ err=talkWithDriver(); }
waitForResponse() 向 binder 驱动写入数据,并读取返回值。关于返回值的处理,不是我们这里关心的东西,就直接略过了。感兴趣的读者可以自行查阅源码。函数中最关键的是 talkWithDriver(),正是它执行了读写工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 struct binder_write_read { binder_size_t write_size; binder_size_t write_consumed; binder_uintptr_t write_buffer; binder_size_t read_size; binder_size_t read_consumed; binder_uintptr_t read_buffer; }; status_t IPCThreadState::talkWithDriver(bool doReceive){ binder_write_read bwr; const bool needRead = mIn.dataPosition() >= mIn.dataSize(); const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0 ; bwr.write_size = outAvail; bwr.write_buffer = (uintptr_t )mOut.data(); if (doReceive && needRead) { bwr.read_size = mIn.dataCapacity(); bwr.read_buffer = (uintptr_t )mIn.data(); } else { bwr.read_size = 0 ; bwr.read_buffer = 0 ; } if ((bwr.write_size == 0 ) && (bwr.read_size == 0 )) return NO_ERROR; bwr.write_consumed = 0 ; bwr.read_consumed = 0 ; status_t err; do { if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0 ) err = NO_ERROR; else err = -errno; if (mProcess->mDriverFD <= 0 ) { err = -EBADF; } } while (err == -EINTR); return err; }
struct binder_write_read 作为一个媒介,在写入数据的同时,binder 驱动也通过它向应用返回数据。bwr.write_buffer 就指向 Parcel mOut 里的数据。Parcel mIn 用于读取数据。
数据的写入由 ioctl 完成,command 为 BINDER_WRITE_READ。
此时各个数据的关系如下图所示。
传递数据给 context manager 我们知道,调用 ioctl,最后是由 binder 驱动的 binder_ioctl() 完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static long binder_ioctl (struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { case BINDER_WRITE_READ: ret = binder_ioctl_write_read(filp, cmd, arg, thread); break ; } return ret; } static int binder_ioctl_write_read (struct file *filp, unsigned int cmd, unsigned long arg, struct binder_thread *thread) { int ret = 0 ; struct binder_proc *proc = filp ->private_data ; unsigned int size = _IOC_SIZE(cmd); void __user *ubuf = (void __user *)arg; struct binder_write_read bwr ; copy_from_user(&bwr, ubuf, sizeof (bwr)); if (bwr.write_size > 0 ) { ret = binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed); } else if (bwr.read_size > 0 ) { ret = binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK) } copy_to_user(ubuf, &bwr, sizeof (bwr)); return ret; }
前面我们调用 ioctl 传递进来的 struct binder_write_read 实际上是处于用户空间的。这里用 copy_from_user() 将用户空间的数据拷贝到内核。同样的,修改了 bwr 后,又通过 copy_to_user() 将结果拷贝回用户空间。
下面我们看看 binder_thread_write:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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 ; }
前面我们说,IPCThreadState::transact() 先是把数据写到 mOut 里,然后才把 mOut 的数据写到 binder 驱动。所以一次写入可能会有多个请求,这里使用 while 每个循环处理一个。
现在,我们最好再看一次上面画的图:
在 binder_ioctl_write_read() 我们第一次调用 copy_from_user(),拷贝的是 binder_write_read。bwr.write_buffer 指向的是 IPCThreadState::mOut 中的数据,所以这里再一次调用 copy_from_user(),可以读到一个 transaction 的 cmd(cmd放在头部)。
接下来,我们再次调用 copy_from_user() 把 binder_transaction_data 也拷贝到内核。然后把 binder_transaction_data 交给 binder_transaction() 处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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) { struct binder_transaction *t ; struct binder_work *tcomplete ; struct binder_proc *target_proc = NULL ; struct binder_thread *target_thread = NULL ; struct binder_node *target_node = NULL ; if (tr->target.handle) { } else { target_node = context->binder_context_mgr_node; } target_proc = target_node->proc; t = kzalloc(sizeof (*t), GFP_KERNEL); tcomplete = kzalloc(sizeof (*tcomplete), GFP_KERNEL); if (!reply && !(tr->flags & TF_ONE_WAY)) t->from = thread; else t->from = NULL ; t->sender_euid = task_euid(proc->tsk); 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)); t->buffer->allow_user_free = 0 ; t->buffer->debug_id = t->debug_id; t->buffer->transaction = t; t->buffer->target_node = target_node; off_start = (binder_size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof (void *))); offp = off_start; copy_from_user(t->buffer->data, (const void __user *)(uintptr_t ) tr->data.ptr.buffer, tr->data_size); copy_from_user(offp, (const void __user *)(uintptr_t ) tr->data.ptr.offsets, tr->offsets_size); }
这里先看 binder_transaction() 是前半部分。
分配了两个对象 binder_transaction 和 binder_work。binder_work 后面我们就会看到它的作用,这里先忽略。
调用 binder_alloc_new_buf 分配了一块缓存。这里分配的缓存是 context manager 的调用 mmap 时候所创建的。
拷贝 Parcel 的数据
拷贝 Parcel 的对象的偏移数组
我们知道,内核的页和用户空间的页同时执行 mmap 所分配的缓存。所以这里虽然是拷贝到了内核,context manager 也能够直接读取这里拷贝的到 Parcel 的数据。也就是说,从一个进程到另一个进程,数据只拷贝了一次。这就是 binder 高效的原因。
读取到数据后,开始循环处理 Parcel 里所有的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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) { off_end = (void *)off_start + tr->offsets_size; for (; offp < off_end; offp++) { struct binder_object_header *hdr ; hdr = (struct binder_object_header *)(t->buffer->data + *offp); switch (hdr->type) { case BINDER_TYPE_BINDER: { struct flat_binder_object *fp ; fp = to_flat_binder_object(hdr); ret = binder_translate_binder(fp, t, thread); } } } }
不知道你还记不记得,前面把 BBinder 写入 Parcel 时,我们设置 type 为 BINDER_TYPE_BINDER。
下面继续处理 flat_binder_object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static int binder_translate_binder (struct flat_binder_object *fp, struct binder_transaction *t, struct binder_thread *thread) { node = binder_get_node(proc, fp->binder); if (!node) { node = binder_new_node(proc, fp); } ret = binder_inc_ref_for_node(target_proc, node, fp->hdr.type == BINDER_TYPE_BINDER, &thread->todo, &rdata); 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 ; binder_put_node(node); return ret; }
这里发生了一件非常关键的事:我们把 flat_binder_object 的类型修改为 BINDER_TYPE_HANDLE 了! 所以,当 context manager 从 Parcel 里面读取 IBinder 的时候,拿到的将会是 BpBinder(而不是原来的 BBinder)。这一点很重要,因为 context manager 跟我们的服务运行在不同的进程中,所以 context manager 理应持有 BpBinder。
服务向 context manager 注册的时候,是第一次传递 IBinder,所以这里的 binder_get_node() 会返回 NULL。接着便会为服务创建一个 binder_node。
binder_proc.nodes 是一个 struct rb_node。它是 Linux 内核实现的红黑树数据结构。binder_get_node 尝试获取一个 binder_node,如果找不到,就会新建一个,然后插入 binder_proc.nodes 这棵红黑树中。这里插入的是调用进程的 binder_proc。
binder_inc_ref_for_node() 则生成一个 struct binder_ref。他同时会插入 binder_proc.refs_by_desc 和 binder_proc.refs_by_node。binder_ref 可以看成 binder_node 的指针。只要持有 binder_ref,就可以拿到对应的 binder_node。(注意,函数的第一个参数是 target_proc,binder_ref 插入的是 context manager 的 binder_proc。
当其他进程向 context manager 查询服务时,binder 驱动就会为它生成一个 binder_ref,它指向对应服务的 binder_node。当然,真正的 binder_node 保存在了所属的 binder_proc 里。一个服务永远只对应一个 binder_node,binder_ref 却可以有多个。
我们下面看 binder_transaction 的最后一部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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) { tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; binder_enqueue_work(proc, tcomplete, &thread->todo); t->work.type = BINDER_WORK_TRANSACTION; if (reply) { binder_enqueue_work_ilocked(&t->work, &target_thread->todo); wake_up_interruptible_sync(&target_thread->wait); } }
我们将上面创建的 struct binder_work *tcomplete 放入调用线程(也就是要注册服务的那个线程)的工作队列 thread->todo 中。struct binder_work *t 则放入 context manager 的任务队列,然后唤醒 context manager。context manager 醒来后,就会取出工作队列中的 binder_work 进行处理(注意,这里会运行在 context manager 的 binder 线程中。
在 binder_ioctl_write_read() 中,共有两部分,write 我们已经讲完了;read 部分等到服务在 context manager 注册完成后,再进行讲解。这里的 tcomplete 任务也会在 read 部分得到执行。
最后总结一下数据写入操作的函数调用流程: