本篇是 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 部分得到执行。
最后总结一下数据写入操作的函数调用流程: