当前位置: 首页 > news >正文

Android binder学习笔记5 - binder transact内核态与用户态交互全链路解析

1. Binder跨进程通信的核心流程

Android系统的Binder机制是进程间通信(IPC)的基石,理解其内核态与用户态的交互全链路对深入掌握Android系统至关重要。让我们从一个实际场景出发:当客户端进程调用transact()方法发起跨进程请求时,数据究竟经历了怎样的旅程?

整个过程可以概括为三个阶段:用户空间数据准备、内核空间数据中转、目标进程数据接收。在这个过程中,binder_write_read结构体扮演着关键角色,它是用户态和内核态之间数据交换的载体。值得注意的是,整个流程中只有一次数据拷贝,这得益于Binder驱动精心设计的内存映射机制。

2. 用户空间的数据准备

2.1 IPCThreadState::transact的奥秘

当我们在客户端调用transact()方法时,实际上触发的是IPCThreadState::transact()的执行。这个方法做了三件关键事情:

  1. 设置事务标志(如TF_ACCEPT_FDS)
  2. 调用writeTransactionData()准备事务数据
  3. 通过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结构体中的信息决定是执行写操作、读操作还是两者都执行:

  1. 如果write_size > 0,调用binder_thread_write()
  2. 如果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_TRANSACTIONBC_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驱动中最复杂的部分之一,它主要完成以下工作:

  1. 根据handle找到目标进程和线程
  2. 在目标进程的空间中分配缓冲区
  3. 将事务数据拷贝到目标缓冲区
  4. 将事务加入目标线程的待处理队列
  5. 唤醒可能处于等待状态的目标线程
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()会被调用来处理:

  1. 检查当前线程的待处理队列
  2. 从队列中取出工作项
  3. 根据工作项类型进行相应处理
  4. 将处理结果写回用户空间
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事务可以分为同步和异步两种模式,它们在处理上有重要区别:

  1. 同步事务(非TF_ONE_WAY)

    • 客户端会等待服务端响应
    • 事务会被加入目标线程的事务栈
    • 需要等待BR_REPLY才能完成
  2. 异步事务(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跨进程调用的完整交互链路:

  1. 客户端用户空间

    • 构造请求数据(Parcel)
    • 调用transact()发起请求
    • 通过ioctl进入内核
  2. 内核空间处理

    • 查找目标进程和线程
    • 执行唯一的数据拷贝
    • 将事务加入目标队列
    • 唤醒目标线程
  3. 服务端用户空间

    • 从内核读取请求
    • 处理请求并准备响应
    • 通过ioctl返回结果
  4. 内核空间响应

    • 将响应数据传递给客户端
    • 唤醒等待的客户端线程
  5. 客户端用户空间

    • 读取并处理响应
    • 完成整个调用过程

7. 性能优化的关键点

在实际开发中,理解Binder的交互链路有助于我们优化应用性能。以下是几个关键优化点:

  1. 减少传输数据量

    • 精简Parcel中的数据
    • 避免传输大对象
  2. 合理使用同步/异步模式

    • 不需要返回结果时使用TF_ONE_WAY
    • 同步调用注意超时处理
  3. 复用Binder连接

    • 避免频繁建立和断开连接
    • 使用连接池管理长期服务
  4. 注意线程模型

    • 服务端使用多线程处理并发请求
    • 避免在主线程进行耗时Binder调用

在实际项目中,我曾遇到一个性能问题:某个服务接口响应缓慢。通过分析发现,客户端频繁发起小数据量的同步调用,导致大量线程阻塞在Binder通信上。优化方案是将多个小请求合并为批量请求,并使用异步方式处理,性能提升了3倍以上。

http://www.jsqmd.com/news/798453/

相关文章:

  • 彻底告别Ubuntu 20.04休眠唤醒黑屏:除了降级驱动,你还可以这样一劳永逸地禁用挂起
  • 终极指南:如何解决FanControl风扇突然“隐身“问题 - 快速恢复硬件识别的完整教程
  • centos10.1上安装mysql 9.6
  • YOLOv11 改进 - 注意力机制 GAM全局注意力机制:通道与空间注意力协同抑制背景干扰,强化目标关键特征
  • javascript中的caller和Error.stack
  • 工厂货物智能入库全流程自动化:基于实在Agent与ISSUT技术的2026工业自动化实战指南
  • Fluent Launch界面深度解析:从串行到并行的性能跃迁之路
  • 别再手动编译了!用Buildroot 2024.02为树莓派4B一键构建定制Linux系统(附完整配置流程)
  • Windows任务栏透明美化终极指南:TranslucentTB快速配置完整教程
  • 设计程序核算职场各类福利发放数据,对比福利成本与员工积极性变化,测算最优福利发放标准,控制企业人力开发同时提升员工幸福感。
  • MCDF顶层验证环境复用策略与实现
  • 雀魂Mod Plus终极指南:免费解锁全角色皮肤的最简单方法
  • CMake-GUI可视化编译OpenCV 3:给命令行恐惧症患者的Ubuntu图形化安装指南
  • YOLOv11 改进 - 注意力机制 Focused Linear Attention 聚焦线性注意力:增强特征聚焦与多样性,优化多尺度目标检测
  • 用Python和OpenCV搞定车道曲率计算:从像素到真实世界的保姆级转换指南
  • 从渔船到货轮:一文看懂AIS B类与A类设备的区别及数据解析要点
  • 【Mac效率】告别窗口切换烦恼:用AfloatX解锁AlwaysOnTop、置底与透明度的窗口管理新姿势
  • 如何用HsMod插件让炉石传说游戏体验提升300%:终极完整指南
  • Navicat和DBeaver连接Oracle 19c保姆级教程:从配置文件修改到用户授权,一次搞定
  • Zotero插件市场:让插件管理变得前所未有的简单
  • 终极指南:如何免费批量下载网易云音乐FLAC无损音质歌曲
  • OOXML 文档格式剖析:哈希、ZIP结构与识别
  • 探索FanControl:Windows平台专业风扇控制软件完全指南
  • 打工人高效工具!OpenClaw 汉化版部署全程教学
  • 从LC谐振到SAW滤波器:浅谈手机里的射频前端是怎么‘过滤’信号的
  • TensorPool:AI-Native RAN的3D异构计算引擎设计与优化
  • 【2024最新】Midjourney Encaustic风格Prompt公式库(含17组已验证英文提示模板+中文翻译对照表)
  • 避开这些坑,你的Z7板子DDR才能稳如老狗:PCB Layout信号完整性实战解析
  • 怪物猎人世界终极叠加层工具:HunterPie 5分钟快速上手指南
  • MySQL安装报错libssl.so.10找不到?一份给Linux新手的依赖问题排查与解决指南