11、Binder通信调试:Binder架构、binder信息、binder transaction、死锁分析
Binder,是Android系统里绕不开的核心话题。说实话,我刚开始接触Android系统时,对Binder的理解就是“进程间通信”。但真正上手调试后才发现,Binder远不止这么简单。它既是通信桥梁,也是性能瓶颈,更是死锁的温床。
今天我们就来聊聊Binder的调试。我会从架构讲起,再到具体的信息查看、事务分析,最后聊聊死锁。嗯,这些都是我踩过的坑,希望能帮你少走弯路。
Binder架构:一张图说清楚
先看一张我画的架构图。Binder其实分三层:应用层、驱动层、内核层。说白了,就是用户空间的进程通过驱动和内核交互。
你想想看,一个App要调用系统服务,比如获取定位,底层就是通过Binder完成的。Client发送请求,Server处理并返回结果,中间全靠Binder驱动调度。
核心要点:Binder通信的本质是内存映射。驱动层通过mmap将接收进程的地址空间映射到内核空间,数据拷贝只发生一次。这也是Binder比传统Socket高效的原因。
查看Binder信息:从/proc入手
调试Binder,我最常用的就是/proc/binder目录。这里藏着所有Binder的秘密。我记得有一次线上出现ANR,就是靠这里的信息定位到问题的。
常用的几个节点:
- /proc/binder/proc:列出所有注册了Binder的进程
- /proc/binder/state:全局Binder状态
- /proc/binder/transactions:当前活跃的事务
- /proc/binder/stats:统计信息
举个例子,查看某个进程的Binder信息:
# 查看进程1234的Binder信息
cat /proc/1234/binder/proc
# 输出示例
binder proc 1234 (com.example.app)
pending transactions: 2
threads: 5
requested threads: 8
ready threads: 3
free async space: 524288
这里有个关键指标:pending transactions。如果这个值持续增长,说明有事务堆积,很可能就是死锁或性能瓶颈。
我的习惯:每次排查ANR时,我都会先看/proc/binder/transactions。如果看到大量WAITING状态的事务,基本可以断定是Binder阻塞了。
Binder Transaction:深入事务细节
Binder事务是通信的基本单元。每个事务都有唯一的ID,包含目标进程、代码、数据等信息。我个人习惯用dumpsys binder来查看事务详情。
# 查看所有Binder事务
adb shell dumpsys binder --transactions
# 输出片段
Binder Transaction:
transaction 12345: from 1000:1234 to 1001:5678
code: 0x00000001 (TRANSACTION_TYPE)
flags: 0x00000000
data_size: 256 bytes
offsets_size: 32 bytes
status: WAITING (timeout: 5000ms)
看到WAITING状态了吗?这就是问题所在。事务在等待对方响应,如果一直等不到,就会超时。超时时间默认是5秒,超过后就会触发ANR。
为什么会等待?常见原因有:
- Server端处理太慢,比如在主线程做耗时操作
- Server端死锁,线程被其他锁阻塞
- Binder线程池耗尽,没有空闲线程处理请求
注意:Binder线程池默认最大16个线程。如果所有线程都在等待,新请求就会排队。我曾经遇到一个案例,某个服务在回调中又调用了自己,导致线程全部阻塞,最终系统重启。
死锁分析:Binder死锁的典型场景
Binder死锁,说白了就是两个进程互相等待对方释放资源。我遇到过最典型的场景是这样的:
- 进程A持有锁L1,然后通过Binder调用进程B的服务
- 进程B在处理请求时,需要获取锁L1
- 但锁L1被进程A持有,进程B只能等待
- 进程A也在等待进程B的Binder响应
- 于是,两个进程互相等待,死锁形成
用代码表示就是:
// 进程A
synchronized (lock1) {
// 调用Binder服务
binderService.doSomething(); // 等待B响应
}
// 进程B
void doSomething() {
synchronized (lock1) { // 等待A释放lock1
// 处理请求
}
}
这种死锁很难排查,因为表面上看只是ANR。我当时的排查步骤是:
- 先看
/proc/binder/transactions,发现大量WAITING事务 - 再看
/proc/binder/proc,发现两个进程的pending transactions都在增长 - 最后用
dumpsys binder --waiting查看具体等待链
避坑指南:我曾经在项目里遇到一个诡异的死锁,查了两天才发现是AIDL接口中定义了一个回调,而回调里又调用了原服务。说白了就是循环调用。解决方案很简单:Binder调用不要持有锁,回调中不要调用原服务。
调试工具与技巧
除了/proc和dumpsys,还有几个工具我经常用:
| 工具 | 用途 | 常用命令 |
|---|---|---|
| binder | 查看Binder节点和引用 | binder --nodes |
| strace | 跟踪Binder系统调用 | strace -e trace=binder |
| perfetto | 可视化Binder事务 | perfetto --txt -o trace.pb |
| dumpsys | 查看Binder整体状态 | dumpsys binder |
我个人最喜欢用strace。它可以直接看到Binder调用的耗时。比如:
# 跟踪进程1234的Binder调用
strace -p 1234 -e trace=binder -T
# 输出
binder: ioctl(3, BINDER_WRITE_READ, ...) = 0 <0.005>
binder: ioctl(3, BINDER_WRITE_READ, ...) = 0 <2.345> // 这个调用花了2.3秒!
看到那个2.3秒了吗?这就是问题所在。Binder调用超过1秒就算异常了,2秒以上基本就是阻塞。
小技巧:如果怀疑是Binder线程池耗尽,可以看/proc/binder/proc中的requested threads和ready threads。如果requested threads接近16且ready threads为0,说明线程池满了。
总结一下
Binder调试,说白了就是三件事:看状态、找等待、查死锁。我建议你养成习惯,遇到ANR先看/proc/binder/transactions,再结合dumpsys binder和strace定位具体问题。
嗯,Binder这块内容确实有点多,但掌握了这些调试方法,大部分问题都能迎刃而解。记住一点:Binder调用不要持有锁,回调不要循环调用。这两条原则能帮你避开90%的死锁。
公众号:蓝海资料掘金营,微信deep3321