15. 事务队列:binder_proc 中的待处理事务队列与节点队列

好,咱们今天聊聊 binder_proc 里的两个核心队列——待处理事务队列和节点队列。说实话,这两个队列是理解 Binder 通信流程的关键。我刚开始啃 Binder 源码时,就是被这两个队列绕晕过。

先问个问题:当一个进程向另一个进程发送 Binder 调用时,这个调用请求到底存在哪?答案就是——存在目标进程的待处理事务队列里。而节点队列呢?它管的是 Binder 实体对象的生命周期。

15.1 待处理事务队列:todo_list

每个 binder_proc 都有一个 todo 链表,这就是待处理事务队列。说白了,它是一个「待办事项」列表。当驱动收到一个 Binder 事务(比如 BC_TRANSACTION),它不会立刻把数据塞给目标进程,而是先挂到这个队列里,等目标进程有空了再来取。

核心数据结构:

struct binder_proc {
    struct list_head todo;           // 待处理事务链表头
    spinlock_t inner_lock;           // 保护 todo 的自旋锁
    // ... 其他成员
};

我个人习惯把 todo_list 想象成一个「消息信箱」。发送方把信投进去,接收方什么时候取、怎么处理,那是接收方的事。驱动只负责投递,不负责强制处理。

15.2 节点队列:nodes

节点队列管理的是 binder_node 对象。每个 Binder 实体(也就是服务端暴露出来的接口)在驱动层都对应一个 binder_node。这些节点通过红黑树组织在 binder_proc->nodes 里。

为什么要用红黑树?因为查找效率高。当客户端通过 handle 找到对应的 Binder 实体时,驱动需要快速定位到目标节点。红黑树的查找复杂度是 O(log n),比链表快得多。

避坑指南:我曾经在调试一个 Binder 泄漏问题时,发现节点队列里的节点只增不减。后来定位到是服务端进程退出时没有正确清理 Binder 实体,导致节点一直挂在红黑树上。嗯,这里要注意:服务端退出时,驱动会遍历 nodes 树,把每个节点标记为「死亡」,并通知所有引用它的客户端。

15.3 两个队列的关系

你想想看,todo_list 和 nodes 队列其实是「动」与「静」的关系:

  • todo_list:动态的、临时的。事务处理完就移除。
  • nodes:静态的、持久的。只要 Binder 实体存在,节点就存在。

但两者也有交集。举个例子:当客户端发起一个跨进程调用,驱动会在目标进程的 todo_list 里插入一个 binder_work 节点。这个 work 节点里会引用目标 binder_node。所以,todo_list 里的工作项可能会「指向」nodes 树里的某个节点。

15.4 事务入队与出队流程

我画了一张图,帮你理清整个流程:

事务入队与出队流程 客户端进程 发送 BC_TRANSACTION Binder 驱动 binder_proc.todo binder_proc.nodes 服务端进程 处理 BR_TRANSACTION ① 事务请求 ② 查找目标节点 ③ 唤醒并投递 关键步骤说明: 1. 客户端通过 ioctl 发送 BC_TRANSACTION,驱动收到后创建 binder_transaction 2. 驱动在目标进程的 nodes 红黑树中查找对应的 binder_node 3. 将 binder_work 挂入目标进程的 todo_list,并唤醒目标进程 4. 目标进程从 todo_list 取出 work,处理 BR_TRANSACTION 命令

15.5 队列的并发保护

多线程环境下,这两个队列的并发访问是个大问题。驱动使用 inner_lock 自旋锁来保护 todo_list 和 nodes 树。为什么用自旋锁而不是互斥锁?因为 Binder 驱动运行在中断上下文或进程上下文,自旋锁在持有时间短的情况下性能更好。

注意:自旋锁持有期间不能睡眠。如果你在持有 inner_lock 时调用了可能睡眠的函数(比如 kmalloc(GFP_KERNEL)),那就等着死锁吧。我见过一个同事因为这个 bug 调了三天,最后发现是锁的粒度没控制好。

15.6 队列与进程状态的关系

进程的状态会影响队列的处理方式:

进程状态 todo_list 行为 nodes 行为
正常运行 正常入队出队 正常增删查
休眠等待 入队后唤醒进程 不变
正在退出 清空 todo_list,返回错误 遍历 nodes,标记死亡
已死亡 拒绝新事务入队 节点被回收

15.7 实战经验:队列积压问题

我在项目中遇到过 todo_list 积压导致 ANR 的情况。当时有个服务端进程处理 Binder 调用特别慢,导致 todo_list 里堆积了几百个待处理事务。客户端等不到响应,直接超时 ANR。

解决方案有两个方向:

  • 服务端优化:提高处理速度,或者使用异步 Binder 调用
  • 客户端限流:控制并发请求数量,避免一次性发太多

我个人更推荐后者。因为服务端的处理能力往往受限于业务逻辑,不是想优化就能优化的。客户端做好限流,反而更可控。

15.8 小结

todo_list 和 nodes 队列,一个管「事」,一个管「人」。理解这两个队列,你就掌握了 Binder 通信的「物流系统」。下次遇到 Binder 相关的问题,先看看这两个队列的状态,往往能快速定位问题。

小技巧:调试时可以用 cat /d/binder/proc/[pid] 查看进程的 todo_list 和 nodes 信息。如果 todo_list 不为空且长时间不变,那大概率是服务端卡住了。


公众号:蓝海资料掘金营,微信deep3321