3. Binder 协议核心概念:实体、引用与事务

Binder IPC 的核心通信模型建立在三个基础概念之上:Binder 实体(binder_node)、Binder 引用(binder_ref) 和 Binder 事务(binder_transaction)。理解这三者的关系,是掌握 Binder 驱动工作原理的基石。

核心关系速记: 实体是“服务端对象”在内核中的代表,引用是“客户端”持有的远程句柄,事务是“通信数据”的载体。一次典型的 Binder 调用 = 客户端通过引用发送事务,驱动将事务路由到实体所在进程。

3.1 Binder 实体(binder_node)

当一个进程(通常是服务端)通过 mmap() 和 BC_ENTER_LOOPER 初始化 Binder 后,驱动会为其创建一个 binder_node 结构体。该结构体唯一标识一个可被远程调用的 Binder 对象。

字段 类型 说明
ptr void __user * 用户空间 Binder 对象的指针(服务端本地地址)
cookie void __user * 用户空间附加数据,通常指向服务端对象自身
proc struct binder_proc * 指向所属进程的 binder_proc 结构
refs struct hlist_head 指向该实体的所有引用的链表头
has_async_transaction int 是否有异步事务等待处理
internal_strong_refs int 内核内部强引用计数
调试提示: 在 /sys/kernel/debug/binder/proc/[pid]/nodes 中可以查看当前进程注册的所有 binder_node。每个节点都有一个全局唯一的 debug_id,用于在日志中追踪。

3.2 Binder 引用(binder_ref)

当客户端进程通过 BC_TRANSACTION 或 BINDER_TYPE_BINDER 协议获取到一个 Binder 实体的句柄时,驱动会在客户端进程的 binder_proc 中创建一个 binder_ref。引用是客户端视角的“远程指针”,它指向服务端进程中的 binder_node。

字段 类型 说明
desc u32 客户端进程内的句柄号(handle),从 1 开始递增
node struct binder_node * 指向被引用的 binder_node
proc struct binder_proc * 持有该引用的客户端进程
death struct binder_ref_death * 死亡通知回调(可选)
避坑指南: 句柄 desc 只在当前进程内有效。同一个 binder_node 在不同客户端进程中的 desc 值可能不同。调试时不要跨进程直接比较句柄号。

3.3 Binder 事务(binder_transaction)

事务是 Binder 通信的基本数据单元。每次跨进程调用(无论是同步还是异步)都会在内核中创建一个 binder_transaction 结构体,它封装了调用数据、目标、来源和状态。

字段 类型 说明
work struct binder_work 挂入目标进程待处理队列的工作项
buffer struct binder_buffer * 指向事务数据缓冲区(位于接收进程的 mmap 区域)
from struct binder_thread * 发起事务的线程(可为 NULL,如来自内核)
to_proc struct binder_proc * 目标进程
to_thread struct binder_thread * 目标线程(同步调用时指定)
code u32 事务代码(用户定义,如 TRANSACTION_TYPE)
flags u32 事务标志(如 TF_ONE_WAY 异步标志)
priority int 事务优先级(影响目标线程的调度)

3.4 三者的关系与数据流

下面的 SVG 图展示了从客户端发起调用到服务端处理完成的完整数据流,以及实体、引用、事务三者的协作关系。

Binder 实体、引用、事务关系图 Binder 实体 · 引用 · 事务 关系与数据流 客户端进程 (Client Proc) binder_thread binder_ref desc=5 binder_transaction 持有 发起 Binder 驱动 (Kernel Driver) 路由查找 ref->node->proc BC_TRANSACTION 服务端进程 (Server Proc) binder_node binder_thread binder_transaction 路由到 node 交付 唤醒 数据流总结 客户端线程 → binder_ref(desc=5) → binder_transaction → 驱动路由 → 服务端 binder_node → 服务端线程处理 线程 引用 事务 实体 驱动路由

3.5 关键流程:一次同步调用的生命周期

  1. 客户端准备: 客户端线程通过 ioctl(BC_TRANSACTION) 提交事务,驱动根据 handle 找到对应的 binder_ref。
  2. 引用解析: 驱动通过 binder_ref->node 找到目标 binder_node,再通过 binder_node->proc 确定目标进程。
  3. 事务创建: 驱动在目标进程的 mmap 区域分配 binder_buffer,将用户数据拷贝过去,并创建 binder_transaction 结构。
  4. 线程唤醒: 驱动将事务挂入目标进程的 todo 队列,并唤醒目标线程(或创建新线程)。
  5. 服务端处理: 目标线程通过 ioctl(BR_TRANSACTION) 读取事务,执行服务端逻辑,然后通过 BC_REPLY 返回结果。
  6. 回复路由: 驱动根据事务中的 from 字段,将回复事务路由回客户端线程。
内核调试关键点: 在 /sys/kernel/debug/binder/transactions 中可以查看当前系统中所有活跃的事务。关注 from_proc、to_proc、code 和 flags 字段,可以快速定位通信阻塞或异常。

3.6 实体与引用的生命周期管理

Binder 使用引用计数来管理实体和引用的生命周期,防止出现悬空指针或内存泄漏。

操作 影响 说明
服务端注册实体 binder_node->internal_strong_refs++ 服务端通过 BC_ENTER_LOOPER 或 BC_REGISTER_LOOPER 注册
客户端获取引用 binder_node->local_strong_refs++ 客户端通过 BINDER_TYPE_BINDER 或 BINDER_TYPE_HANDLE 获取
客户端释放引用 binder_node->local_strong_refs-- 客户端通过 BC_RELEASE 或 BC_DECREFS 显式释放
服务端退出 驱动清理所有关联的 node 和 ref 服务端进程退出时,驱动遍历所有引用并发送死亡通知
常见问题: 如果客户端忘记释放引用(BC_RELEASE),会导致服务端实体无法被销毁,造成内存泄漏。使用 adb shell dumpsys binder 可以查看当前所有进程的引用计数状态。

3.7 事务标志与类型

事务的 flags 字段决定了通信的行为模式,理解这些标志对调试性能问题至关重要。

标志 值 说明
TF_ONE_WAY 0x01 异步调用,客户端不等待回复,不阻塞
TF_CLEAR_BUF 0x02 事务完成后立即清除缓冲区(通常用于 oneway)
TF_UPDATE_TXN 0x04 允许更新已有事务(用于合并同类异步调用)
TF_ACCEPT_FDS 0x08 允许事务中传递文件描述符
性能建议: 对于不需要返回结果的调用(如通知、日志),务必使用 TF_ONE_WAY 标志。这可以避免客户端线程阻塞,减少内核上下文切换次数,提升整体吞吐量。