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 图展示了从客户端发起调用到服务端处理完成的完整数据流,以及实体、引用、事务三者的协作关系。
3.5 关键流程:一次同步调用的生命周期
- 客户端准备: 客户端线程通过
ioctl(BC_TRANSACTION)提交事务,驱动根据handle找到对应的binder_ref。 - 引用解析: 驱动通过
binder_ref->node找到目标binder_node,再通过binder_node->proc确定目标进程。 - 事务创建: 驱动在目标进程的
mmap区域分配binder_buffer,将用户数据拷贝过去,并创建binder_transaction结构。 - 线程唤醒: 驱动将事务挂入目标进程的
todo队列,并唤醒目标线程(或创建新线程)。 - 服务端处理: 目标线程通过
ioctl(BR_TRANSACTION)读取事务,执行服务端逻辑,然后通过BC_REPLY返回结果。 - 回复路由: 驱动根据事务中的
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 标志。这可以避免客户端线程阻塞,减少内核上下文切换次数,提升整体吞吐量。