适用场景:SystemUI / Cluster / CarSettings 等上游通过同步 Binder 调用 carserver 或 system_server;服务端 Binder 线程在处理请求时,持锁同步调用 VHAL 或其他下游 Binder 服务;下游长时间不回包,最终耗尽服务端 Binder 线程池。
源码依据:
- AOSP:
~/code/android16_r2- Kernel:
~/code/android16_r2_kernel/common
1. 结论
这类问题的本质不是某个 Binder 调用单独慢,而是:
text
上游同步 Binder 调用
+ 服务端 Binder 线程持锁
+ 服务端 Binder 线程再次发起同步下游 Binder 调用
+ 下游 VHAL 或服务长期不回包
= Binder 线程无法返回线程池,锁也无法释放
= 其他 Binder 线程继续等待同一把锁或等待下游
= 服务端 Binder Thread Pool 耗尽
= 上游没有 BR_REPLY,最终卡顿或 ANR
最危险的代码形态:
java
synchronized (mLock) {
return mVhalProxy.someSyncMethod();
}
风险来自三个条件叠加:
text
持锁 + 跨进程 + 同步等待
2. 参与角色
| 角色 | 典型进程 | 本案例中的职责 |
|---|---|---|
| 上游 Client | SystemUI / Cluster / CarSettings | 同步调用 carserver 的某个 Binder 接口,等待 reply |
| 中间 Server | carserver 或 system_server | 接收上游请求;处理过程中同步调用 VHAL |
| 下游 Server | VHAL 进程或其他服务进程 | 接收 carserver 的下游同步 Binder 请求并返回 reply |
| Kernel Binder Driver | Kernel | 投递事务、调度 Binder 线程、维护事务队列 |
一个进程可以同时具有 Client 和 Server 两种角色:
text
对 SystemUI 请求:
carserver 是 Server
对 VHAL 请求:
carserver 是 Client
因此同一个 carserver Binder Thread 可以先接收请求,随后在处理该请求时发起新的同步 Binder 调用。
3. 完整时序图
4. 上游:SystemUI 同步调用 carserver
text
SystemUI / Cluster / CarSettings 进程
ICarXXXService.Stub.Proxy.xxx()
→ mRemote.transact(..., flags = 0)
→ BinderProxy.transact()
→ JNI android_os_BinderProxy_transact()
→ BpBinder::transact()
→ IPCThreadState::transact() ← 上游调用线程执行
→ writeTransactionData(BC_TRANSACTION)
→ waitForResponse()
→ talkWithDriver()
→ ioctl(BINDER_WRITE_READ)
→ Kernel
本地源码中,同步调用等待 reply 的位置:
cpp
// frameworks/native/libs/binder/IPCThreadState.cpp:868-942
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
flags |= TF_ACCEPT_FDS;
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
if (err != NO_ERROR) {
if (reply) reply->setError(err);
return (mLastError = err);
}
if ((flags & TF_ONE_WAY) == 0) {
if (reply) {
err = waitForResponse(reply); // 同步调用阻塞等待 BR_REPLY
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
} else {
err = waitForResponse(nullptr, nullptr);
}
return err;
}
这里的 IPCThreadState::transact() 运行在 SystemUI 发起调用的线程中。这个线程可能是主线程、HandlerThread 或其他业务线程;如果是主线程,carserver 长时间不返回会直接造成界面卡顿风险。
4.1 上游同步调用:等待发生在哪里
上游看到的典型调用链是:
text
SystemUI 调用线程
ICarXXXService.Stub.Proxy.xxx()
→ mRemote.transact(..., flags = 0)
→ BinderProxy.transact()
→ BpBinder::transact()
→ IPCThreadState::transact()
→ writeTransactionData(BC_TRANSACTION)
→ waitForResponse() ← 逻辑上的同步等待入口
→ talkWithDriver()
→ ioctl(BINDER_WRITE_READ) ← 常见的实际内核睡眠位置
→ 等待 BR_REPLY
同步调用链中 waitForResponse() 表示等待已经开始,但应区分两个层次:
| 层次 | 位置 | 含义 |
|---|---|---|
| 逻辑等待点 | IPCThreadState::waitForResponse() |
当前同步 Binder 调用尚未收到远端 reply |
| 实际可能阻塞点 | talkWithDriver() → ioctl(BINDER_WRITE_READ) |
线程进入 Kernel,等待 Binder Driver 返回 BR_REPLY 或其他 BR_* 命令 |
text
SystemUI 卡在 waitForResponse / ioctl
不等于 SystemUI 自己有问题
它只证明:
SystemUI 还没有收到 carserver 返回的 BR_REPLY
carserver 能否返回 BR_REPLY,取决于服务端请求处理是否开始、是否完成,以及是否最终走到 sendReply()。
5. Kernel:投递到 carserver Binder 线程池
text
Kernel Binder Driver
binder_ioctl_write_read()
→ binder_thread_write()
→ 读取 BC_TRANSACTION
→ binder_transaction()
→ 根据 target.handle 找到 carserver 的目标 Binder node
→ target_proc = carserver / system_server
→ binder_proc_transaction()
→ transaction 入队到 Binder Thread todo 或进程 todo
→ binder_wakeup_thread_ilocked()
→ 唤醒空闲 Binder Thread
核心投递逻辑:
c
// kernel/common/drivers/android/binder.c:3025-3099
static int binder_proc_transaction(struct binder_transaction *t,
struct binder_proc *proc,
struct binder_thread *thread)
{
// ...
if (!thread && !pending_async)
thread = binder_select_thread_ilocked(proc);
if (thread) {
binder_transaction_priority(thread, t, node);
binder_enqueue_thread_work_ilocked(thread, &t->work);
} else if (!pending_async) {
binder_enqueue_work_ilocked(&t->work, &proc->todo);
}
if (!pending_async)
binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);
proc->outstanding_txns++;
}
Binder 线程池是进程级资源,不是某个独立 AIDL 服务专用资源。若 CarService 与其他服务同处 system_server,CarService 的线程阻塞可能影响 system_server 中其他 Binder 服务的响应能力。
32 / 16 也不是 Android 固定值;实际可用线程数受 BINDER_SET_MAX_THREADS、主动加入线程池的线程数、Driver 请求创建线程的时机和线程阻塞状态共同影响。
6. carserver:Binder Thread 作为 Server 接收请求
text
carserver / system_server 进程
Binder Thread T1
IPCThreadState::joinThreadPool()
→ getAndExecuteCommand()
→ talkWithDriver()
→ ioctl(BINDER_WRITE_READ)
← Kernel 返回 BR_TRANSACTION
→ executeCommand(BR_TRANSACTION)
→ BBinder::transact()
→ JavaBBinder::onTransact()
→ Binder.execTransact()
→ ICarXXXService.Stub.onTransact()
→ CarService.xxx()
服务端线程池循环:
cpp
// frameworks/native/libs/binder/IPCThreadState.cpp:788-804
void IPCThreadState::joinThreadPool(bool isMain)
{
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
mIsLooper = true;
do {
processPendingDerefs();
result = getAndExecuteCommand();
} while (result != -ECONNREFUSED && result != -EBADF);
}
cpp
// frameworks/native/libs/binder/IPCThreadState.cpp:687-710
status_t IPCThreadState::getAndExecuteCommand()
{
status_t result;
int32_t cmd;
result = talkWithDriver();
if (result >= NO_ERROR) {
cmd = mIn.readInt32();
size_t newThreadsCount = mProcess->mExecutingThreadsCount.fetch_add(1) + 1;
result = executeCommand(cmd);
mProcess->mExecutingThreadsCount.fetch_sub(1);
}
return result;
}
收到 BR_TRANSACTION 后,Native Binder 调用 Java 服务端:
cpp
// frameworks/native/libs/binder/IPCThreadState.cpp:1421-1491
case BR_TRANSACTION:
{
binder_transaction_data tr;
// ... 从 mIn 读取 tr ...
Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size / sizeof(binder_size_t), freeBuffer);
if (tr.target.ptr) {
if (reinterpret_cast<RefBase::weakref_type*>(tr.target.ptr)->attemptIncStrong(this)) {
error = reinterpret_cast<BBinder*>(tr.cookie)->transact(
tr.code, buffer, &reply, tr.flags);
reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
}
}
}
此时 T1 的角色是:
text
Server
→ 正在处理 SystemUI / Cluster → carserver 的入站 Binder 请求
7. 风险点:同一个 Binder Thread 持锁同步调用 VHAL
假设业务代码形态如下:
java
void xxx() {
synchronized (mLock) {
return mVhalProxy.someSyncMethod();
}
}
此时 T1 的完整栈会变成:
text
carserver Binder Thread T1
IPCThreadState::executeCommand(BR_TRANSACTION)
→ ICarXXXService.Stub.onTransact()
→ CarService.xxx()
→ synchronized (mLock) ← T1 获得 mLock
→ mVhalProxy.someSyncMethod()
→ BinderProxy.transact()
→ JNI android_os_BinderProxy_transact()
→ BpBinder::transact()
→ IPCThreadState::transact() ← 同一个 T1 再次执行
→ writeTransactionData(BC_TRANSACTION)
→ waitForResponse() ← 等 VHAL 回复
此刻同一个线程发生角色切换:
text
前半段:T1 是 carserver 的 Server Binder Thread
接收并处理 SystemUI 发来的请求
后半段:T1 是 VHAL 的 Client Thread
向 VHAL 发起同步 Binder 调用,等待 BR_REPLY
这和 IPCThreadState.cpp 同时存在 Client / Server 路径不矛盾:它是每个使用 Native Binder 的进程都会加载的用户态传输层,同一个线程可以先处理 BR_TRANSACTION,再发送 BC_TRANSACTION,随后等待 BR_REPLY。
7.1 AIDL 自动生成的 onTransact:为什么仍然会阻塞
第二张图中的代码通常来自 AIDL 自动生成的 Stub.onTransact()。它负责:
text
读取 Parcel 参数
→ 调用真正的服务实现
→ 服务实现正常返回后写 reply
→ Native 层 sendReply
→ Kernel 将 BR_REPLY 返回上游
以当前工作区生成的 IContentService 为例:
java
// out/soong/.../gen/aidl/java/IContentService.java:485-500
case TRANSACTION_registerContentObserver:
{
Uri _arg0 = data.readTypedObject(Uri.CREATOR);
boolean _arg1 = data.readBoolean();
IContentObserver _arg2 =
IContentObserver.Stub.asInterface(data.readStrongBinder());
int _arg3 = data.readInt();
int _arg4 = data.readInt();
this.registerContentObserver(_arg0, _arg1, _arg2, _arg3, _arg4);
// ↑ 自动生成分发 → 真实服务实现的边界
reply.writeNoException();
break;
}
这里的 this.registerContentObserver(...) 虽然写在自动生成文件中,却是一次虚方法分发:
text
this 的静态类型:IContentService.Stub
this 的运行时类型:ContentService
实际执行的方法:
ContentService.registerContentObserver(...)
当前源码中的真实实现:
java
// frameworks/base/services/core/java/com/android/server/content/ContentService.java:365-400
@Override
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
IContentObserver observer, int userHandle, int targetSdkVersion) {
// 参数、用户和权限校验 ...
synchronized (mRootNode) {
mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
uid, pid, userHandle);
}
}
所以"自动生成"不等于"不会阻塞"。自动生成部分只是一个很薄的分发壳;只要真实实现没有返回,下面的:
java
reply.writeNoException();
就不会执行,上游 waitForResponse() 也不会返回。
这段 AIDL 分发链可能对应三种不同情况:
| 分类 | 服务端实际状态 | 上游结果 |
|---|---|---|
| 真实实现等待锁 | 线程在 synchronized(mRootNode) 或业务锁上 BLOCKED |
尚未写 reply,上游继续等 |
| 真实实现嵌套同步调用下游 | 线程在下游 waitForResponse() |
尚未写 reply,上游继续等 |
| Binder 线程池耗尽 | 请求尚未被任何 Binder Thread 执行,AIDL 分发逻辑甚至尚未开始 | 尚未写 reply,上游继续等 |
还有一个本案例相关的特殊失败点:data.readStrongBinder() 也可能在真实服务实现之前失败。
text
data.readStrongBinder()
→ unflattenBinder()
→ getStrongProxyForHandle()
→ javaObjectForIBinder()
→ BinderProxy.getInstance()
→ sProxyMap.set()
→ BinderProxyMapSizeException
这种情况下,ContentService.registerContentObserver() 根本没有开始执行;它是参数反扁平化阶段的 ProxyMap 泄漏崩溃,不是业务实现等待锁或等待 VHAL。
7.2 最小 Demo:自动分发、持锁同步下游与线程池耗尽
下面的代码是用于说明调用关系的最小 Demo,不是 AOSP 现有 CarService 的具体实现。
AIDL 接口
aidl
// ICarDataService.aidl
interface ICarDataService {
int getVehicleValue();
}
// IVehicleGateway.aidl
interface IVehicleGateway {
int getValueSync();
}
AIDL 自动生成的服务端分发逻辑(等价结构)
java
// ICarDataService.Stub.onTransact() 的等价结构
case TRANSACTION_getVehicleValue: {
data.enforceInterface(DESCRIPTOR);
int result = this.getVehicleValue();
// ↑ 虚方法分发到 CarDataService.getVehicleValue()
// 未返回前,下面两行不会执行
reply.writeNoException();
reply.writeInt(result);
break;
}
错误示例:持锁同步调用 VHAL
java
final class CarDataService extends ICarDataService.Stub {
private final Object mLock = new Object();
private final IVehicleGateway mVehicleGateway;
@Override
public int getVehicleValue() throws RemoteException {
synchronized (mLock) {
// 错误点:Binder Thread 持有 mLock,并同步等待下游 VHAL。
return mVehicleGateway.getValueSync();
}
}
public void updateCacheFromOtherRequest(int value) {
synchronized (mLock) {
// 这个路径与 getVehicleValue() 竞争同一把锁。
}
}
}
线程状态演化
text
T1:处理 SystemUI 的 getVehicleValue() 请求
ICarDataService.Stub.onTransact()
→ CarDataService.getVehicleValue()
→ 获得 mLock
→ mVehicleGateway.getValueSync()
→ BinderProxy.transact()
→ IPCThreadState.waitForResponse()
→ VHAL 未回复
→ T1 持锁阻塞
T2:处理另一条 updateCacheFromOtherRequest() 请求
ICarDataService.Stub.onTransact()
→ CarDataService.updateCacheFromOtherRequest()
→ 尝试获得 mLock
→ BLOCKED,等待 T1
T3、T4 ...:
→ 等同一把 mLock,或者各自同步等待 VHAL
→ 无法返回 Binder Thread Pool
新的 SystemUI 请求:
→ Kernel todo 队列
→ 没有可用 Binder Thread
→ AIDL onTransact 分发逻辑尚未开始
→ 上游继续 waitForResponse()
改进示例:锁外同步调用
java
final class CarDataService extends ICarDataService.Stub {
private final Object mLock = new Object();
private final IVehicleGateway mVehicleGateway;
private Request mPendingRequest;
@Override
public int getVehicleValue() throws RemoteException {
final Request request;
synchronized (mLock) {
request = mPendingRequest;
}
// 锁外等待下游,VHAL 慢不会长期霸占 mLock。
final int value = mVehicleGateway.getValueSync();
synchronized (mLock) {
mPendingRequest = request;
return value;
}
}
}
这个改动不能让 VHAL 自动恢复,也不会让 T1 自动回到线程池;它解决的是锁级联问题:其他 Binder Thread 不再因为同一个 mLock 被 T1 连带阻塞。
8. 下游 VHAL 不回复时的阻塞状态
text
Binder Thread T1
获得 mLock
→ 同步调用 VHAL
→ IPCThreadState::waitForResponse()
→ 等待 VHAL BR_REPLY
→ VHAL 未及时回复
→ T1 持续阻塞
此时:
text
T1 持有 mLock
T1 不能处理下一个 carserver Binder 请求
T1 不能返回 Binder 线程池
需要区分两种下游失败:
| 下游状态 | 常见结果 |
|---|---|
| VHAL 进程已死亡 | Driver 可返回 BR_DEAD_REPLY,waitForResponse() 结束并返回错误 |
| VHAL 进程仍存活,但服务线程死锁、阻塞或硬件长期不返回 | 没有 BR_REPLY,调用线程持续停在 waitForResponse() |
Binder 没有适用于所有业务调用的统一 Java 层自动超时取消机制;具体超时通常由上层业务、HAL 协议、Watchdog 或调用策略处理。
9. 后续请求如何耗尽 Binder 线程池
9.1 情况 A:其他请求也需要同一把 mLock
text
Binder Thread T1
获得 mLock
→ waitForResponse(VHAL)
→ 卡住
Binder Thread T2
收到新的 SystemUI 请求
→ CarService.yyy()
→ 尝试 synchronized (mLock)
→ BLOCKED,等待 T1 释放 mLock
Binder Thread T3
收到新的 Cluster 请求
→ CarService.zzz()
→ 尝试 synchronized (mLock)
→ BLOCKED,等待 T1 释放 mLock
T2、T3 没有停在 waitForResponse(),但它们也无法完成入站 Binder 调用,因而同样占住 Binder 线程池资源。
9.2 情况 B:多个 Binder Thread 各自同步等待 VHAL
text
Binder Thread T1 → waitForResponse(VHAL request #1)
Binder Thread T2 → waitForResponse(VHAL request #2)
Binder Thread T3 → waitForResponse(VHAL request #3)
...
Binder Thread TN → waitForResponse(VHAL request #N)
这种场景不依赖同一把锁,所有 Binder Thread 直接因为同步下游调用而被占住。
10. 耗尽后的最终状态
text
carserver Binder Thread Pool
T1 → waitForResponse(VHAL),持有 mLock
T2 → BLOCKED,等待 mLock
T3 → BLOCKED,等待 mLock
T4 → waitForResponse(VHAL)
T5 → waitForResponse(VHAL)
...
TN → BLOCKED 或 waitForResponse(VHAL)
可用 Binder Thread = 0
新的上游 Binder 请求仍可进入 Kernel:
text
SystemUI
→ BC_TRANSACTION
→ Kernel
→ carserver binder_proc.todo / binder_thread.todo
但 Kernel 找不到可用的 carserver Binder Thread 去处理:
text
新的 transaction
→ 留在 carserver todo 队列排队
→ 没有可用 Binder Thread 取出并执行
→ 没有业务方法运行
→ 没有 BR_REPLY 返回上游
上游同步调用持续等待:
text
SystemUI 主线程
→ waitForResponse()
→ 等 carserver BR_REPLY
→ carserver 无可用 Binder Thread
→ 长时间阻塞
→ Input timeout / ANR
11. 完整因果链
text
VHAL 卡住 / 回复慢
↓
carserver Binder Thread T1 持 mLock 发起同步 Binder 调用
↓
T1 卡在 IPCThreadState::waitForResponse()
↓
mLock 长时间不释放
↓
其他 carserver Binder Thread:
- 要么等待 mLock
- 要么也同步等待 VHAL
↓
carserver Binder Thread Pool 耗尽
↓
新的 SystemUI / Cluster / CarSettings Binder 请求在 Kernel todo 队列排队
↓
上游同步调用没有 BR_REPLY
↓
SystemUI 卡顿 / ANR
12. 排查证据
出现这类问题时,优先抓 carserver / system_server 的 Java 线程栈和 native Binder 事务状态。
12.1 Binder Thread 的典型阻塞栈
text
Binder:* 线程
→ BinderProxy.transact
→ 某个 VHAL AIDL / 下游服务方法
→ IPCThreadState::waitForResponse
12.2 同一把锁导致的连锁阻塞
text
Binder:* 线程 T1
→ 持有 mLock
→ BinderProxy.transact
→ waitForResponse
Binder:* 线程 T2 / T3 / ...
→ BLOCKED
→ waiting to lock <mLock>
→ owner = T1
12.3 Kernel Binder 状态
bash
adb shell cat /dev/binderfs/binder_logs/state > binder_state.txt
重点观察:
text
- carserver / system_server 的 Binder Thread 是否都处于执行中或等待中
- carserver 的 todo 队列是否出现持续堆积的 pending transaction
- 下游 VHAL 进程是否存在未完成 transaction
- Binder transaction 的 sender / target PID 是否形成等待链
13. 修复原则
避免让锁覆盖未知耗时的同步跨进程调用。
不建议:
java
synchronized (mLock) {
return mVhalProxy.someSyncMethod();
}
建议:
java
final Request request;
synchronized (mLock) {
request = buildRequestLocked();
}
// 锁外执行未知耗时的同步跨进程调用
final Result result = mVhalProxy.someSyncMethod(request);
synchronized (mLock) {
applyResultLocked(result);
}
这样不能让 VHAL 自动恢复,但可以避免 VHAL 慢调用长期霸占 mLock,防止其他 Binder Thread 因等待同一把锁而被连带耗尽。
若业务允许,还应评估:
text
- 是否可将下游调用改为异步或 oneway + 明确回调
- 是否可增加业务级超时与取消策略
- 是否应将慢路径移出 Binder 线程,交由受控工作线程执行
- 是否会发生下游回调 carserver 并再次请求同一把 mLock 的死锁环
14. VHAL 接口类型前提
本文以 AIDL Binder VHAL 为例:
text
BinderProxy → BpBinder → IPCThreadState → /dev/binder
若下游仍是旧 HIDL VHAL,用户态对象和设备节点可能变为:
text
IHwBinder / HwBinder → /dev/hwbinder
但"持锁同步等待下游 → Binder Thread 不返回线程池 → 线程池耗尽 → 上游同步调用卡住"的因果关系仍然成立。