Skip to content

Binder嵌套调用

追踪 Binder transaction_stack 的嵌套同步调用、原线程回调、reply 路由、优先级恢复与死锁边界。

基于android-17.0.0_r1
AndroidBinder并发

Binder嵌套调用 ​

本文承接 Binder同步调用 和 Binder优先级继承。所谓嵌套调用,是 A 的线程同步调用 B 后,B 在处理该事务期间又同步调用 C,或回调 A。Android 17 驱动使用 transaction_stack、from_parent、to_parent 维护双向调用链,并在回调原进程时优先复用正在等待的原线程。这个机制允许可重入调用,但不能自动解决业务锁形成的环。

1. 栈节点 ​

源码文件:kernel/common/drivers/android/binder.c

每个同步 binder_transaction 保存 from、to_thread、from_parent、to_parent。发送方压栈时:

c
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;

接收方从驱动读取 BR_TRANSACTION 时:

c
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;

同一个事务因此同时连接发送线程和接收线程各自的栈顶,reply 才能反向找到准确等待者。

2. 嵌套识别 ​

源码文件:kernel/common/drivers/android/binder.c

c
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
    tmp = thread->transaction_stack;
    while (tmp) {
        from = tmp->from;
        if (from && from->proc == target_proc) {
            target_thread = from;
            is_nested = true;
            break;
        }
        tmp = tmp->from_parent;
    }
}

B 处理 A 的同步请求时再同步调用 A,驱动沿 B 当前事务的 from_parent 链找到来源进程 A,并把 A 中正在等待的原线程设为 target_thread。这不是普通线程池调度,而是一次有明确栈关系的嵌套回调。

3. 原线程回调 ​

A 的调用线程虽然在 waitForResponse 中等待 B 的 reply,但该循环会处理除 reply 外的 Binder 命令。驱动把 B→A 的回调作为 BR_TRANSACTION 投递给 A 的原线程,A 可以在等待期间进入本地 Stub,执行回调后再继续等待外层 reply。

因此同步 Binder 等待不是“线程完全不能执行代码”,而是允许处理 Binder 嵌套事务。用户代码必须按可重入模型设计,不能假定发出 IPC 到返回期间对象状态绝不会被同一线程再次访问。

4. Reply路由 ​

源码文件:kernel/common/drivers/android/binder.c

服务线程发送 BC_REPLY 时,驱动要求当前 transaction_stack 非空且 in_reply_to->to_thread == thread,然后沿 binder_get_txn_from_and_acq_inner 找到原发送线程。目标线程栈顶还必须等于该事务,否则返回 BR_FAILED_REPLY/-EPROTO。

这些检查防止 reply 被错误线程发送、事务乱序弹栈或伪造目标。内层 reply 先弹出内层节点,外层调用仍保留在栈上继续等待。

5. 优先级恢复 ​

嵌套事务进入时可能覆盖服务线程当前继承优先级。reply 路径发现 in_reply_to->is_nested 后,把保存优先级设为 pending;若同时有新的嵌套事务到来,旧 restore 会被 abort。优先级恢复因此与具体栈节点绑定,而不是每次 reply 无条件恢复到进程默认 nice。

6. 可重入状态 ​

考虑 A 在持有对象锁 L 时调用 B,B 又回调 A 的同一对象:驱动可以把回调送回 A 原线程,但回调若再次申请非递归锁 L,仍会自锁死。Binder 的线程复用解决“没有空闲线程接回调”的一类问题,不改变 C++/Java 锁语义。

安全设计通常是在跨进程调用前复制必要状态并释放业务锁,返回后重新校验版本或状态;若必须持锁,应明确整个跨进程调用图的统一锁顺序,并考虑回调和死亡通知的重入。

7. 线程池死锁 ​

A→B→C 的同步链会各占用一个等待线程。如果每个进程的所有 Binder 线程都在等待下游,而下游又需要回调一个没有栈关系的新入口,就可能没有空闲 looper。嵌套原线程复用只在驱动能沿 from_parent 找到目标进程时成立,不能为任意第三方回调创造线程。

8. 失败边界 ​

  • reply 线程没有 transaction stack:BR_FAILED_REPLY,参数为 -EPROTO。
  • reply 线程不是事务的 to_thread:坏调用栈错误。
  • 目标等待线程已经死亡:BR_DEAD_REPLY。
  • 发送线程 todo 队列头已有待处理同步事务时再发新事务:驱动拒绝,避免栈和 work queue 次序失配。
  • 同进程 self transaction 被驱动拒绝;本地 Binder 调用应走对象直接调用路径。

9. 测试场景 ​

源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp

测试服务读取客户端传入的 callback Binder,并在处理当前事务时同步 callback->transact(..., &reply2)。这构成真实 B→A 嵌套回调,验证等待中的客户端线程能够处理回调并把 reply 返回服务端。

同一测试文件的单线程 polling 服务还明确允许在处理 Binder 命令之间发出保存的 callback,覆盖单 looper 环境中的重入和工作顺序;线程池占满测试则说明无空闲线程时新增无栈关系调用会死锁。

10. 调用图 ​

11. 阅读检查 ​

画出 A→B→C→B→A 两层嵌套的 from_parent/to_parent 关系,指出每个 reply 应由哪个线程发送、唤醒哪个等待线程。再加入“A 持锁 L,B 回调 A”条件,解释为什么驱动路由正确仍可能死锁,以及应在哪个用户态边界释放锁。