Android binder学习笔记5 - binder transact内核态与用户态交互全链路解析
1. Binder跨进程通信的核心流程Android系统的Binder机制是进程间通信IPC的基石理解其内核态与用户态的交互全链路对深入掌握Android系统至关重要。让我们从一个实际场景出发当客户端进程调用transact()方法发起跨进程请求时数据究竟经历了怎样的旅程整个过程可以概括为三个阶段用户空间数据准备、内核空间数据中转、目标进程数据接收。在这个过程中binder_write_read结构体扮演着关键角色它是用户态和内核态之间数据交换的载体。值得注意的是整个流程中只有一次数据拷贝这得益于Binder驱动精心设计的内存映射机制。2. 用户空间的数据准备2.1 IPCThreadState::transact的奥秘当我们在客户端调用transact()方法时实际上触发的是IPCThreadState::transact()的执行。这个方法做了三件关键事情设置事务标志如TF_ACCEPT_FDS调用writeTransactionData()准备事务数据通过waitForResponse()等待服务端响应status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel data, Parcel* reply, uint32_t flags) { flags | TF_ACCEPT_FDS; err writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr); if ((flags TF_ONE_WAY) 0) { if (reply) { err waitForResponse(reply); } else { Parcel fakeReply; err waitForResponse(fakeReply); } } return err; }2.2 事务数据的封装艺术writeTransactionData()方法的核心是构造binder_transaction_data结构体。这个结构体包含了目标服务的handle、事务代码、标志位以及实际要传输的数据。特别值得注意的是对于BpBinder使用handle标识目标服务对于BBinder则使用ptr指针data_size和offsets_size分别表示数据大小和Binder对象偏移量struct binder_transaction_data { union { __u32 handle; binder_uintptr_t ptr; } target; binder_uintptr_t cookie; __u32 code; __u32 flags; pid_t sender_pid; uid_t sender_euid; binder_size_t data_size; binder_size_t offsets_size; union { struct { binder_uintptr_t buffer; binder_uintptr_t offsets; } ptr; __u8 buf[8]; } data; };3. 内核空间的桥梁作用3.1 与驱动交互的关键一步当用户空间准备好数据后通过talkWithDriver()方法与Binder驱动进行交互。这个方法构造了binder_write_read结构体并通过ioctl系统调用进入内核空间status_t IPCThreadState::talkWithDriver(bool doReceive) { binder_write_read bwr; bwr.write_size mOut.dataSize(); bwr.write_buffer (uintptr_t)mOut.data(); bwr.read_size mIn.dataCapacity(); bwr.read_buffer (uintptr_t)mIn.data(); ioctl(mProcess-mDriverFD, BINDER_WRITE_READ, bwr); }3.2 内核中的事务处理流程在内核空间Binder驱动通过binder_ioctl_write_read()处理来自用户空间的请求。这个函数会根据bwr结构体中的信息决定是执行写操作、读操作还是两者都执行如果write_size 0调用binder_thread_write()如果read_size 0调用binder_thread_read()static int binder_ioctl_write_read(struct file *filp, unsigned int cmd, unsigned long arg, struct binder_thread *thread) { if (bwr.write_size 0) { ret binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, bwr.write_consumed); } 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); } }4. 数据在内核中的旅程4.1 唯一的数据拷贝时刻在binder_thread_write()中当处理BC_TRANSACTION或BC_REPLY命令时会通过copy_from_user()将用户空间的数据拷贝到内核空间。这是整个Binder通信过程中唯一的一次数据拷贝case BC_TRANSACTION: case BC_REPLY: { struct binder_transaction_data tr; if (copy_from_user(tr, ptr, sizeof(tr))) return -EFAULT; binder_transaction(proc, thread, tr, cmd BC_REPLY, 0); break; }4.2 binder_transaction的复杂逻辑binder_transaction()函数是Binder驱动中最复杂的部分之一它主要完成以下工作根据handle找到目标进程和线程在目标进程的空间中分配缓冲区将事务数据拷贝到目标缓冲区将事务加入目标线程的待处理队列唤醒可能处于等待状态的目标线程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) { // 根据handle找到目标进程 if (tr-target.handle) { ref binder_get_ref_olocked(proc, tr-target.handle, true); target_node binder_get_node_refs_for_txn(ref-node, target_proc, return_error); } else { // handle为0表示ServiceManager target_node context-binder_context_mgr_node; target_node binder_get_node_refs_for_txn(target_node, target_proc, return_error); } // 在目标进程分配缓冲区 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); // 拷贝数据到目标缓冲区 binder_alloc_copy_user_to_buffer(target_proc-alloc, t-buffer, 0, (const void __user *)(uintptr_t)tr-data.ptr.buffer, tr-data_size); // 将事务加入目标队列 if (!binder_proc_transaction(t, target_proc, target_thread)) goto err_dead_proc_or_thread; }5. 目标进程的响应处理5.1 服务端如何接收请求在服务端进程如ServiceManager通常会通过binder_loop()不断读取并处理来自Binder驱动的请求。当有新的请求到达时binder_thread_read()会被调用来处理检查当前线程的待处理队列从队列中取出工作项根据工作项类型进行相应处理将处理结果写回用户空间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) { while (1) { if (!binder_worklist_empty_ilocked(thread-todo)) list thread-todo; else if (!binder_worklist_empty_ilocked(proc-todo) wait_for_proc_work) list proc-todo; w binder_dequeue_work_head_ilocked(list); switch (w-type) { case BINDER_WORK_TRANSACTION: { t container_of(w, struct binder_transaction, work); if (t-buffer-target_node) { cmd BR_TRANSACTION; } else { cmd BR_REPLY; } // 准备返回给用户空间的数据 tr.target.ptr target_node-ptr; tr.cookie target_node-cookie; tr.code t-code; tr.flags t-flags; tr.data_size t-buffer-data_size; tr.offsets_size t-buffer-offsets_size; tr.data.ptr.buffer (binder_uintptr_t)((uintptr_t)t-buffer-data binder_alloc_get_user_buffer_offset(proc-alloc)); tr.data.ptr.offsets tr.data.ptr.buffer ALIGN(t-buffer-data_size, sizeof(void *)); // 将数据拷贝到用户空间 if (put_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; ptr sizeof(uint32_t); if (copy_to_user(ptr, tr, sizeof(tr))) return -EFAULT; ptr sizeof(tr); break; } } } }5.2 同步与异步处理的差异Binder事务可以分为同步和异步两种模式它们在处理上有重要区别同步事务非TF_ONE_WAY客户端会等待服务端响应事务会被加入目标线程的事务栈需要等待BR_REPLY才能完成异步事务TF_ONE_WAY客户端不等待响应事务处理完毕后立即释放资源适合不需要返回结果的操作if (cmd BR_TRANSACTION !(t-flags TF_ONE_WAY)) { // 同步事务处理 binder_inner_proc_lock(thread-proc); t-to_parent thread-transaction_stack; t-to_thread thread; thread-transaction_stack t; binder_inner_proc_unlock(thread-proc); } else { // 异步事务处理 binder_free_transaction(t); }6. 完整交互链路的全景视图通过上述分析我们可以绘制出Binder跨进程调用的完整交互链路客户端用户空间构造请求数据Parcel调用transact()发起请求通过ioctl进入内核内核空间处理查找目标进程和线程执行唯一的数据拷贝将事务加入目标队列唤醒目标线程服务端用户空间从内核读取请求处理请求并准备响应通过ioctl返回结果内核空间响应将响应数据传递给客户端唤醒等待的客户端线程客户端用户空间读取并处理响应完成整个调用过程7. 性能优化的关键点在实际开发中理解Binder的交互链路有助于我们优化应用性能。以下是几个关键优化点减少传输数据量精简Parcel中的数据避免传输大对象合理使用同步/异步模式不需要返回结果时使用TF_ONE_WAY同步调用注意超时处理复用Binder连接避免频繁建立和断开连接使用连接池管理长期服务注意线程模型服务端使用多线程处理并发请求避免在主线程进行耗时Binder调用在实际项目中我曾遇到一个性能问题某个服务接口响应缓慢。通过分析发现客户端频繁发起小数据量的同步调用导致大量线程阻塞在Binder通信上。优化方案是将多个小请求合并为批量请求并使用异步方式处理性能提升了3倍以上。