引用计数闭环
Binder 的引用计数不是把 native sp 智能指针直接跨进程复制。至少有四个不同层次的状态: 客户端 BpBinder 的 C++ strong/weak 生命周期、客户端进程的 binder_ref strong/weak、服务端 binder_node 的 internal/local 计数,以及驱动通知服务端后等待 DONE 的 pending 状态。
本文面向已经读过 Binder代理生命周期 和 Binder对象编码 的读者,专门回答引用何时从 0 变 1,哪个边沿触发 BR_ACQUIRE/BR_INCREFS,为什么 release 可能延迟,以及 node/ref/proc 何时真正清理。
1. 三层状态
计数归属
| 层 | 结构 | 状态 | owner |
|---|---|---|---|
| native客户端 | BpBinder | RefBase strong/weak、handle | libbinder |
| 客户端内核 | binder_ref | data.strong、data.weak、desc | client proc |
| 服务端内核 | binder_node | internal/local strong/weak、tmp、pending | node |
| 服务端用户态 | BBinder | RefBase strong/weak、DONE确认 | server process |
同一 proc 对同一 node 只有一个 ref,但 ref 内的 strong/weak 可以多次变化。node 的 internal 计数来自远端 ref,local 计数来自本地用户态收到 BR 通知后的确认,两者不能合成一个数字。
2. 客户端引用
weak入口
源码文件:frameworks/native/libs/binder/BpBinder.cpp
相关函数:BpBinder::BpBinder()
BpBinder::BpBinder(BinderHandle&& handle,
int32_t trackedUid)
: BpBinder(Handle(handle)) {
mTrackedUid = trackedUid;
IPCThreadState::self()->incWeakHandle(
binderHandle(), this);
}创建 BpBinder 时先为 handle 提交 BC_INCREFS,这是代理存在的弱引用基础,不等于已经有业务 strong 引用。
strong入口
源码文件:frameworks/native/libs/binder/BpBinder.cpp
相关函数:onFirstRef()、onLastStrongRef()
void BpBinder::onFirstRef() {
IPCThreadState* ipc =
IPCThreadState::self();
if (ipc)
ipc->incStrongHandle(
binderHandle(), this);
}
void BpBinder::onLastStrongRef(
const void* /*id*/) {
IPCThreadState* ipc =
IPCThreadState::self();
if (ipc && !kDecStrongLast)
ipc->decStrongHandle(
binderHandle());
/* 清理 obituary 等本地状态 */
if (ipc && kDecStrongLast)
ipc->decStrongHandle(
binderHandle());
}具体 BC_RELEASE 位置受 kDecStrongLast 构建分支影响。稳定结论是 strong 从无到有和从有到无 对应 ACQUIRE/RELEASE,而不是固定某一行在所有构建中生效。
命令缓冲
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
void IPCThreadState::incStrongHandle(
int32_t handle, BpBinder* proxy) {
mOut.writeInt32(BC_ACQUIRE);
mOut.writeInt32(handle);
if (!flushIfNeeded()) {
proxy->incStrong(mProcess.get());
mPostWriteStrongDerefs.push(proxy);
}
}
void IPCThreadState::incWeakHandle(
int32_t handle, BpBinder* proxy) {
mOut.writeInt32(BC_INCREFS);
mOut.writeInt32(handle);
if (!flushIfNeeded()) {
proxy->getWeakRefs()->incWeak(
mProcess.get());
mPostWriteWeakDerefs.push(
proxy->getWeakRefs());
}
}命令尚未被驱动消费时,libbinder 暂时增加本地保护引用。write buffer 成功消费后, processPostWriteDerefs 才撤销这些临时引用,避免代理在命令提交途中析构。
3. ref边沿
增加逻辑
源码文件: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 的 strong/weak 从 0 变 1 时,才影响 node internal 状态。同一 ref 的第二个 strong 只增加 ref.data.strong,不再重复增加 node internal_strong_refs。
减少逻辑
源码文件: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 归零减少 node internal strong;strong 和 weak 同时归零才清理 ref。无效递减只记录协议 错误,不会让计数变成负数后继续清理。
BC入口
源码文件:kernel/common/drivers/android/binder.c
bool strong = cmd == BC_ACQUIRE ||
cmd == BC_RELEASE;
bool increment = cmd == BC_INCREFS ||
cmd == BC_ACQUIRE;
ret = binder_update_ref_for_handle(
proc, target,
increment, strong,
&rdata);四条命令共享入口,strong 与 increment 两个布尔值决定操作哪个计数和方向。handle 0 的 context manager 还有单独分支。
4. node通知
internal与local
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_inc_node_nilocked()
if (strong) {
if (internal)
node->internal_strong_refs++;
else
node->local_strong_refs++;
if (!node->has_strong_ref &&
target_list) {
binder_enqueue_deferred_thread_work_ilocked(
thread, &node->work);
}
} else {
if (!internal)
node->local_weak_refs++;
if (!node->has_weak_ref &&
target_list &&
list_empty(&node->work.entry)) {
binder_enqueue_work_ilocked(
&node->work, target_list);
}
}远端 ref 贡献 internal strong;服务进程用户态确认后形成 local strong/weak。node work 让驱动 稍后向服务线程输出 BR_ACQUIRE/BR_INCREFS。
BR到DONE
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
case BR_ACQUIRE:
refs = (RefBase::weakref_type*)
mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
obj->incStrong(mProcess.get());
mOut.writeInt32(BC_ACQUIRE_DONE);
mOut.writePointer((uintptr_t)refs);
mOut.writePointer((uintptr_t)obj);
break;
case BR_INCREFS:
refs = (RefBase::weakref_type*)
mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
refs->incWeak(mProcess.get());
mOut.writeInt32(BC_INCREFS_DONE);
mOut.writePointer((uintptr_t)refs);
mOut.writePointer((uintptr_t)obj);
break;服务端先改变 RefBase,再回写 DONE。驱动校验 node_ptr、cookie 和 pending 标志;DONE 不是 可重复调用的幂等命令。
延迟释放
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
case BR_RELEASE:
refs = (RefBase::weakref_type*)
mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
mPendingStrongDerefs.push(obj);
break;
case BR_DECREFS:
refs = (RefBase::weakref_type*)
mIn.readPointer();
mIn.readPointer();
mPendingWeakDerefs.push(refs);
break;BR_RELEASE/BR_DECREFS 先进入 pending 容器;输入命令队列安全清空后,processPendingDerefs 才执行 decStrong/decWeak。析构可能发起新 Binder 命令,因此不能在解析中任意重入。
5. node删除
删除条件
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_dec_node_nilocked()
if (strong) {
if (internal)
node->internal_strong_refs--;
else
node->local_strong_refs--;
if (node->local_strong_refs ||
node->internal_strong_refs)
return false;
} else {
if (!internal)
node->local_weak_refs--;
if (node->local_weak_refs ||
node->tmp_refs ||
!hlist_empty(&node->refs))
return false;
}
if (proc &&
(node->has_strong_ref ||
node->has_weak_ref)) {
if (list_empty(&node->work.entry))
binder_enqueue_work_ilocked(
&node->work, &proc->todo);
} else if (hlist_empty(&node->refs) &&
!node->local_strong_refs &&
!node->local_weak_refs &&
!node->tmp_refs) {
return true;
}
return false;所有 strong 归零只表示可以通知服务端释放 strong;真正删除还要求 refs、local weak、tmp ref 和 pending work 状态都收束。
ref删除
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_cleanup_ref_olocked()
rb_erase(&ref->rb_node_desc,
&ref->proc->refs_by_desc);
rb_erase(&ref->rb_node_node,
&ref->proc->refs_by_node);
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);ref 清理同时从 handle/node 两棵索引树和 node.refs 链表移除。delete_node 只在 node 的其他条件 也满足时成立。
6. 进程退出
node死亡
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_node_release()
node->proc = NULL;
node->local_strong_refs = 0;
node->local_weak_refs = 0;
hlist_add_head(
&node->dead_node,
&binder_dead_nodes);
hlist_for_each_entry(
ref, &node->refs, node_entry) {
if (!ref->death)
continue;
ref->death->work.type =
BINDER_WORK_DEAD_BINDER;
binder_enqueue_work_ilocked(
&ref->death->work,
&ref->proc->todo);
}服务进程退出后 node 进入 dead list;仍持有 ref 的客户端可收到死亡通知。node 本地计数清零, 但 node 可能因 refs/tmp_refs 暂时保留。
proc清理
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_deferred_release()
proc->is_dead = true;
while ((nodeEntry =
rb_first(&proc->nodes))) {
node = rb_entry(
nodeEntry,
struct binder_node,
rb_node);
binder_node_release(
node, incoming_refs);
}
while ((refEntry =
rb_first(&proc->refs_by_desc))) {
ref = rb_entry(
refEntry,
struct binder_ref,
rb_node_desc);
binder_cleanup_ref_olocked(ref);
binder_free_ref(ref);
}进程退出同时释放本进程发布的 nodes 和持有的 outgoing refs。跨进程引用生命周期必须处理两种 方向,而不是只看客户端 proxy 析构。
7. 测试输入
引用命令
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:IncRefsAcquireReleaseDecRefs()
const uint32_t commands[] = {
BC_INCREFS, 0,
BC_ACQUIRE, 0,
BC_RELEASE, 0,
BC_DECREFS, 0,
};
binder_write_read bwr = {};
bwr.write_buffer =
reinterpret_cast<uintptr_t>(
commands);
bwr.write_size = sizeof(commands);
binderTestIoctl(
BINDER_WRITE_READ, &bwr);
EXPECT_EQ(
sizeof(commands),
bwr.write_consumed);测试验证四条引用命令可以在一次 write buffer 中完整消费。它不能证明 node 已经发生业务调用, 也不能证明所有通知均已回写 DONE。
本地weak升级
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:PromoteLocal()
测试创建 BBinder strong 与 weak,strong 存在时 promote 成功;清空全部 strong 后再次 promote 返回 null。它验证本地 RefBase 语义,是理解 BpBinder onFirst/onLast 的前置,不等于跨进程 ref 测试。
死亡与失效
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:DeathNotificationStrongRef()、DeathNotificationMultiple()、FreedBinder()、WeakRejected()
测试分别覆盖 strong proxy 持有时服务死亡、多客户端 death recipient、失效 handle 重新使用以及 恶意 weak object 重复发送。断言包括 death callback、DEAD_OBJECT/FAILED_TRANSACTION、BAD_VALUE 以及服务仍能 ping。它们能验证失败清理不会破坏其他对象,不能证明所有厂商内核竞态。
8. 失败边界
无效递减
ref strong/weak 已为 0 时继续 BC_RELEASE/BC_DECREFS 会记录 user error,不会继续把 node 计数 减成负数。
DONE错配
node 不存在、cookie 不匹配或没有 pending acquire/increfs 时,DONE 会被拒绝或记录协议错误。 DONE 不是普通状态确认 API。
延迟析构
post-write deref、pending deref 和 driver 命令批处理都会延迟析构。观察 C++ destructor 时间, 不能直接推导最后一条 BC_RELEASE 的时间。
旧代理
node 死亡后 BpBinder 仍可能被本地 sp/wp 持有,但 transact 已返回 DEAD_OBJECT。对象内存存在 和远程目标存活是两个状态。
9. 阅读边界
强弱引用闭环
驱动引用、native BpBinder 和本地 node 的计数不是同一个整数;每次握手都要说明命令消费者和清理 owner。 图只表达闭环,具体强弱计数仍以本文各源码段和测试断言为准。
本文证明了 BpBinder strong/weak 到 BC 命令、ref 0→1/1→0 边沿、node internal/local 计数、 BR/DONE 握手、延迟 deref、ref/node 删除和 proc 退出。没有展开完整 death notification 命令、 冻结回调、allocator、线程池、RPC Binder 或 SELinux。
继续阅读 Binder代理生命周期 复习 Java/native proxy owner。 排查引用问题时按“RefBase → BC → ref 边沿 → node work → BR/DONE → pending deref → cleanup” 顺序取证。
