Binder远程引用
客户端用户态用一个整数 handle 表示远端 Binder,但驱动不能只保存“handle → 对象地址”映射。每个持有者 proc 都需要一个 binder_ref:它同时连接当前 proc、目标 node、用户可见 descriptor、strong/weak 计数,以及可选的 death/freeze notification。
本文面向已经读过 Binder节点管理、引用计数闭环 和 Binder事务发送 的读者,重点回答 target proc 如何为 node 分配 handle、为什么需要两棵红黑树、引用何时真正删除,以及 proc 退出时如何清除反向关系。
本文不重复 node 的本地对象创建,也不完整展开死亡通知协议。BD010 讲跨用户态/内核的引用闭环;本文只讲驱动内一个 binder_ref 的结构和算法。
1. 引用关系
| 关系 | 查找输入 | 数据结构 | 消费者 |
|---|---|---|---|
| handle → ref | desc | refs_by_desc | BC 引用命令、事务目标 |
| node → ref | node 指针 | refs_by_node | 对象翻译、ref 去重 |
| node → 所有 refs | node | node->refs hlist | owner 死亡、死亡通知 |
同一 proc 对同一 node 只需要一个 ref,但 strong/weak 计数可以大于 1。另一个 proc 即使引用同一 node,也拥有自己的 ref 和 descriptor。
2. Ref结构
源码文件:kernel/common/drivers/android/binder_internal.h
相关结构:binder_ref_data、binder_ref
struct binder_ref_data {
int debug_id;
uint32_t desc;
int strong;
int weak;
};
struct binder_ref {
struct binder_ref_data data;
struct rb_node rb_node_desc;
struct rb_node rb_node_node;
struct hlist_node node_entry;
struct binder_proc *proc;
struct binder_node *node;
struct binder_ref_death *death;
struct binder_ref_freeze *freeze;
};data 是可安全复制出的计数快照;完整 ref 必须在 proc->outer_lock 下访问。两枚 rb_node 让同一对象同时进入两棵树,node_entry 又把它挂入目标 node 的反向 refs 链。death/freeze 与 handle 生命周期绑定,ref 删除时必须一并收束。
3. Handle查找
3.1 Desc索引
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_get_ref_olocked()
static struct binder_ref *binder_get_ref_olocked(
struct binder_proc *proc,
u32 desc, bool need_strong_ref) {
struct rb_node *n =
proc->refs_by_desc.rb_node;
struct binder_ref *ref;
while (n) {
ref = rb_entry(n, struct binder_ref,
rb_node_desc);
if (desc < ref->data.desc)
n = n->rb_left;
else if (desc > ref->data.desc)
n = n->rb_right;
else if (need_strong_ref &&
!ref->data.strong) {
binder_user_error(
"weak ref used as strong\n");
return NULL;
} else {
return ref;
}
}
return NULL;
}用户态 BC_ACQUIRE/RELEASE、transaction target handle、death notification 都从 desc 树查找。need_strong_ref 防止只有 weak 计数的 ref 被用作强事务目标;找到 descriptor 不等于满足调用者的引用强度要求。
3.2 Node索引
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_get_ref_for_node_olocked()
p = &proc->refs_by_node.rb_node;
parent = NULL;
while (*p) {
parent = *p;
ref = rb_entry(parent,
struct binder_ref, rb_node_node);
if (node < ref->node)
p = &(*p)->rb_left;
else if (node > ref->node)
p = &(*p)->rb_right;
else
return ref;
}
if (!new_ref)
return NULL;对象翻译拿到 node 后先从 node 树查重。已有 ref 直接复用 descriptor;没有 ref 且调用者未提供预分配结构时只返回 NULL。node 比较只用于同一内核地址空间的树序,不会泄露给用户态。
4. Descriptor分配
4.1 保留handle 0
源码文件:kernel/common/drivers/android/binder.c
相关函数:get_ref_desc_olocked()
offset =
node ==
proc->context->binder_context_mgr_node
? 0 : 1;
if (!dbitmap_enabled(dmap)) {
*desc = slow_desc_lookup_olocked(
proc, offset);
return 0;
}
if (dbitmap_acquire_next_zero_bit(
dmap, offset, &bit) == 0) {
*desc = bit;
return 0;
}descriptor 0 只为当前 context 的 manager node 保留,普通 node 从 1 开始。handle 是 proc 局部编号;两个进程的 desc 1 可以指向完全不同 node。
4.2 Bitmap扩容
源码文件:kernel/common/drivers/android/binder.c
相关函数:get_ref_desc_olocked()
nbits = dbitmap_grow_nbits(dmap);
binder_proc_unlock(proc);
new = bitmap_zalloc(nbits, GFP_KERNEL);
binder_proc_lock(proc);
dbitmap_grow(dmap, new, nbits);
return -EAGAIN;位图扩容需要睡眠分配,因此暂时释放 outer lock。锁外期间两棵 ref 树可能变化,函数返回 -EAGAIN,上层必须从 node 树根重新查找,不能拿旧 parent 指针继续插入。
4.3 Slow fallback
源码文件:kernel/common/drivers/android/binder.c
相关函数:slow_desc_lookup_olocked()
desc = offset;
for (n = rb_first(&proc->refs_by_desc);
n; n = rb_next(n)) {
ref = rb_entry(n, struct binder_ref,
rb_node_desc);
if (ref->data.desc > desc)
break;
desc = ref->data.desc + 1;
}
return desc;dbitmap 未启用时,驱动仍能顺序扫描已分配 descriptor 找最小空洞。当前实现保留 fallback,因此不能把 dbitmap 写成 ref 正确性的唯一基础。
5. Ref创建
5.1 双树插入
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_get_ref_for_node_olocked()
retry:
/* 先按 node 查重 */
if (get_ref_desc_olocked(
proc, node, &desc) == -EAGAIN)
goto retry;
binder_stats_created(BINDER_STAT_REF);
new_ref->data.debug_id =
atomic_inc_return(&binder_last_id);
new_ref->proc = proc;
new_ref->node = node;
rb_link_node(&new_ref->rb_node_node,
parent, p);
rb_insert_color(&new_ref->rb_node_node,
&proc->refs_by_node);
new_ref->data.desc = desc;
/* 再按 desc 插入 refs_by_desc */
rb_link_node(&new_ref->rb_node_desc,
parent, p);
rb_insert_color(&new_ref->rb_node_desc,
&proc->refs_by_desc);一个新 ref 必须同时出现在 node 树和 desc 树,否则一边能按对象查到、一边不能按 handle 操作。插入发生在同一 outer lock 临界区内;debug_id 用于诊断,desc 才是该 proc 的用户可见 handle。
5.2 Node反向链
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_get_ref_for_node_olocked()
binder_node_lock(node);
hlist_add_head(&new_ref->node_entry,
&node->refs);
binder_debug(BINDER_DEBUG_INTERNAL_REFS,
"%d new ref %d desc %d for node %d\n",
proc->pid, new_ref->data.debug_id,
new_ref->data.desc, node->debug_id);
binder_node_unlock(node);node->refs 是跨 proc 的反向集合。owner proc 死亡时,驱动遍历它发送 death work;ref 删除时必须在 node lock 下摘除 node_entry。
5.3 锁外分配
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_inc_ref_for_node()
binder_proc_lock(proc);
ref = binder_get_ref_for_node_olocked(
proc, node, NULL);
if (!ref) {
binder_proc_unlock(proc);
new_ref = kzalloc(
sizeof(*ref), GFP_KERNEL);
if (!new_ref)
return -ENOMEM;
binder_proc_lock(proc);
ref = binder_get_ref_for_node_olocked(
proc, node, new_ref);
}
ret = binder_inc_ref_olocked(
ref, strong, target_list);ref 创建和 thread/node 创建一样采用锁外预分配、锁内二次查找。若另一线程抢先创建,new_ref 被释放;若新 ref 的首次引用增加失败,当前代码会清理已插入结构,避免留下 strong/weak 都为零的空 ref。
6. 对象翻译
6.1 Node到handle
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_translate_binder()
ret = binder_inc_ref_for_node(
target_proc, node,
fp->hdr.type == BINDER_TYPE_BINDER,
&thread->todo, &rdata);
if (ret)
goto done;
fp->hdr.type =
fp->hdr.type == BINDER_TYPE_BINDER
? BINDER_TYPE_HANDLE
: BINDER_TYPE_WEAK_HANDLE;
fp->binder = 0;
fp->handle = rdata.desc;
fp->cookie = 0;目标 proc 建立或复用 ref 后,transaction buffer 中的对象被改写为目标 handle。target_list=&thread->todo 允许 node strong/weak 首边沿把 node work 延迟到发送线程,保证引用通知与对象传递顺序。
6.2 Handle转发
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_translate_handle()
node = binder_get_node_from_ref(
proc, fp->handle,
fp->hdr.type == BINDER_TYPE_HANDLE,
&src_rdata);
if (!node)
return -EINVAL;
if (node->proc == target_proc) {
/* 还原为目标本地 BINDER 对象 */
} else {
ret = binder_inc_ref_for_node(
target_proc, node,
fp->hdr.type == BINDER_TYPE_HANDLE,
NULL, &dest_rdata);
fp->binder = 0;
fp->handle = dest_rdata.desc;
fp->cookie = 0;
}转发 handle 时,驱动先从 source ref 找 node。若 node owner 就是 target proc,目标看到本地对象;否则 target proc 得到自己的 ref/descriptor。source handle 不能原样复制给另一个 proc。
7. 计数边沿
7.1 增加引用
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_inc_ref_olocked()
if (strong) {
if (ref->data.strong == 0) {
ret = binder_inc_node(
ref->node, 1, 1, target_list);
if (ret)
return ret;
}
ref->data.strong++;
} else {
if (ref->data.weak == 0) {
ret = binder_inc_node(
ref->node, 0, 1, target_list);
if (ret)
return ret;
}
ref->data.weak++;
}只有 ref 的 0→1 边沿改变 node internal 状态。第二个 strong 仍只增加 ref->data.strong;这保证一个 proc 对一个 node 的内部边沿不会随用户态 sp 数量重复计算。
7.2 减少引用
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_dec_ref_olocked()
if (strong) {
if (ref->data.strong == 0)
return false;
ref->data.strong--;
if (ref->data.strong == 0)
binder_dec_node(
ref->node, 1, 1);
} else {
if (ref->data.weak == 0)
return false;
ref->data.weak--;
}
if (ref->data.strong == 0 &&
ref->data.weak == 0) {
binder_cleanup_ref_olocked(ref);
return true;
}strong 降到 0 时关闭 node internal strong 边沿;ref 只有 strong 和 weak 都为 0 才进入结构清理。非法重复 decrement 只记录用户错误,不让计数变成负数。
7.3 BC命令入口
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_write()
strong = cmd == BC_ACQUIRE ||
cmd == BC_RELEASE;
increment = cmd == BC_INCREFS ||
cmd == BC_ACQUIRE;
ret = binder_update_ref_for_handle(
proc, target, increment, strong,
&rdata);
if (!ret && rdata.desc != target)
binder_user_error(
"descriptor changed unexpectedly\n");用户态的 BC_INCREFS/ACQUIRE/RELEASE/DECREFS 都先按 handle 找 ref,再更新计数。handle 0 的首次增加会走 context manager 特殊入口;普通 descriptor 必须已经存在。
8. Ref删除
8.1 双树与descriptor
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_cleanup_ref_olocked()
if (dbitmap_enabled(dmap))
dbitmap_clear_bit(
dmap, ref->data.desc);
rb_erase(&ref->rb_node_desc,
&ref->proc->refs_by_desc);
rb_erase(&ref->rb_node_node,
&ref->proc->refs_by_node);清理首先释放 descriptor 位并从两棵树同时摘除。之后同一个数值可被后续新 ref 复用,所以用户态不能在 ref 生命周期结束后缓存 handle 并假设它永远代表原 node。
8.2 Node关系
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_cleanup_ref_olocked()
binder_node_inner_lock(ref->node);
if (ref->data.strong)
binder_dec_node_nilocked(
ref->node, 1, 1);
hlist_del(&ref->node_entry);
delete_node = binder_dec_node_nilocked(
ref->node, 0, 1);
binder_node_inner_unlock(ref->node);
if (!delete_node)
ref->node = NULL;ref 删除关闭残留 strong 边沿、摘除 node 反向链,再检查 node 是否因此满足最终删除条件。ref->node 只有在调用者需要继续 free node 时保留;否则清空以避免 binder_free_ref() 重复释放。
8.3 通知附件
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_cleanup_ref_olocked()、binder_free_ref()
if (ref->death) {
binder_dequeue_work(
ref->proc, &ref->death->work);
binder_stats_deleted(BINDER_STAT_DEATH);
}
if (ref->freeze) {
binder_dequeue_work(
ref->proc, &ref->freeze->work);
binder_stats_deleted(BINDER_STAT_FREEZE);
}
static void binder_free_ref(
struct binder_ref *ref) {
if (ref->node)
binder_free_node(ref->node);
kfree(ref->death);
kfree(ref->freeze);
kfree(ref);
}death/freeze work 与 ref 同生共死。cleanup 先从 work queue 摘除和更新统计,最终 free 再释放附件内存;不能仅 kfree(ref) 留下队列悬挂指针。
9. Proc退出
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_deferred_release()
binder_proc_lock(proc);
while ((n = rb_first(
&proc->refs_by_desc))) {
struct binder_ref *ref =
rb_entry(n, struct binder_ref,
rb_node_desc);
binder_cleanup_ref_olocked(ref);
binder_proc_unlock(proc);
binder_free_ref(ref);
binder_proc_lock(proc);
}
binder_proc_unlock(proc);proc 退出从 refs_by_desc 反复取根节点,锁内解除所有索引和 node 关系,锁外执行最终 free,再重新加锁。由于 cleanup 可能使 dead node 满足删除条件,binder_free_ref() 也可能连带释放 node。
10. 测试输入
10.1 引用命令
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:IncRefsAcquireReleaseDecRefs
const uint32_t bc[] = {
BC_INCREFS, 0,
BC_ACQUIRE, 0,
BC_RELEASE, 0,
BC_DECREFS, 0,
};
bwr.write_buffer = (uintptr_t)bc;
bwr.write_size = sizeof(bc);
binderTestIoctl(BINDER_WRITE_READ, &bwr);
EXPECT_EQ(sizeof(bc), bwr.write_consumed);测试使用 context manager handle 0 依次增加和减少 weak/strong,断言整段 BC 被消费且后续没有 BR 错误。它覆盖命令入口和特殊 desc 0,不证明普通 descriptor 分配、dbitmap 扩容或多进程 ref 去重。
10.2 无效handle
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:FreedBinder
测试先保留 weak 引用,使 driver ref 尚未完全删除,但原 strong 已经释放;随后把该 handle 写入强 Binder object 发起 transaction,期望 FAILED_TRANSACTION。它验证 binder_get_ref_olocked(..., need_strong_ref=true) 会拒绝“存在 ref 但没有 strong”的 descriptor。 测试最后恢复原字段,避免 Parcel 析构错误操作被篡改的 handle。
10.3 死亡附件
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:DeathNotificationStrongRef、DeathNotificationMultiple
这些测试对远端 ref 注册 death recipient,令 node owner 退出,再观察多个 ref 持有者收到通知。它们证明 ref.death 与 node owner 死亡的用户可见闭环,不覆盖 freeze 附件或所有清理竞态。
11. 失败边界
| 阶段 | 失败或竞态 | 结果 |
|---|---|---|
| desc查找 | 不存在或只有weak但要求strong | 返回 NULL,事务/BC失败 |
| descriptor扩容 | outer lock 暂时释放 | -EAGAIN 后重查 node 树 |
| ref分配 | kzalloc 失败 | -ENOMEM,node不新增关系 |
| 首次inc | node死亡或状态不允许 | 清理新ref与descriptor |
| 重复dec | 计数已为0 | 记录用户错误,不下溢 |
| ref删除 | death/freeze work仍排队 | 先dequeue再free |
| proc退出 | 大量refs | 逐个锁内cleanup、锁外free |
12. 源码复现
本文主线:
target node
→ refs_by_node查重
→ descriptor 0/1+分配
→ ref插入node树和desc树
→ node->refs反向链
→ node/handle对象翻译
→ BC strong/weak边沿
→ strong=0且weak=0
→ 双树、descriptor、node_entry、通知清理
→ binder_free_ref
→ proc退出逐项收束源码搜索:
rg -n "binder_get_ref_olocked|binder_get_ref_for_node_olocked|get_ref_desc_olocked" \
kernel/common/drivers/android/binder.c
rg -n "binder_inc_ref_for_node|binder_inc_ref_olocked|binder_dec_ref_olocked" \
kernel/common/drivers/android/binder.c
rg -n "binder_cleanup_ref_olocked|binder_free_ref|refs_by_desc|refs_by_node" \
kernel/common/drivers/android/binder.c \
kernel/common/drivers/android/binder_internal.h
rg -n "IncRefsAcquireReleaseDecRefs|DeathNotificationStrongRef|DeathNotificationMultiple" \
frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp \
frameworks/native/libs/binder/tests/binderLibTest.cpp如果能够解释为什么一个 ref 同时进入两棵树、descriptor 0 为什么特殊、dbitmap 扩容为什么必须重查、strong/weak 都为零后还要清理 node/death/freeze 关系,就已经掌握了远程引用的驱动生命周期。
两棵树解决不同查询方向:handle 到 ref、node 到 ref。释放必须从两棵树和 node refs 链同时摘除,不能只删除一个索引。
