Binder线程池创建
线程池创建包含三个不同 owner:ProcessState 创建用户态线程,IPCThreadState 注册 looper,Binder 驱动根据空闲线程和 todo 队列决定是否继续请求线程。下面的时序图把这三层连接起来。
本文面向已读过 ProcessState初始化 和 IPCThreadState线程绑定 的读者。Binder 线程池不是调用 startThreadPool() 后一次创建 N 个线程:用户态只主动创建首线程,驱动在没有空闲 looper 且尚未达到上限时通过 BR_SPAWN_LOOPER 请求增员。本文追踪首线程、内核请求线程和主线程手动加入三条路径,不展开最大线程数的 ioctl 细节。
1. 启动入口
源码文件:frameworks/native/libs/binder/ProcessState.cpp
void ProcessState::startThreadPool() {
std::unique_lock<std::mutex> _l(mLock);
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}mThreadPoolStarted 是进程级 owner 状态,锁保证并发调用只创建一个首线程。重复调用不会扩容;扩容由驱动反馈触发。若 mMaxThreads == 0 仍调用该方法,源码只记录警告,因为首线程仍会被创建。
2. PoolThread
2.1 对象创建
源码文件:frameworks/native/libs/binder/ProcessState.cpp
void ProcessState::spawnPooledThread(bool isMain) {
if (mThreadPoolStarted) {
String8 name = makeBinderThreadName();
sp<Thread> t = sp<PoolThread>::make(isMain);
t->run(name.c_str());
mKernelStartedThreads++;
}
}PoolThread 交给 utils::Thread::run 创建 pthread。mKernelStartedThreads 记录由线程池机制创建的线程,但名称中的 kernel-started 指驱动请求的配额模型,不表示线程由内核直接创建。
2.2 循环入口
源码文件:frameworks/native/libs/binder/ProcessState.cpp
bool PoolThread::threadLoop() {
IPCThreadState::self()->joinThreadPool(mIsMain);
return false;
}每个工作线程拥有 thread-local IPCThreadState,进入 joinThreadPool 后持续读取 Binder 命令。返回 false 让 Thread 不再重复调用 threadLoop;真正的长期循环位于 joinThreadPool 内部。
3. Looper注册
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
mProcess->mCurrentThreads++;
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
mIsLooper = true;
do {
processPendingDerefs();
result = getAndExecuteCommand();
} while (result != -ECONNREFUSED && result != -EBADF);首线程以 BC_ENTER_LOOPER 主动进入;驱动要求创建的后续线程以 BC_REGISTER_LOOPER 回应请求。二者都进入同一命令循环,但内核用不同 looper bit 追踪线程来源,防止没有请求时伪造 registered 线程。
4. 内核扩容
源码文件:kernel/common/drivers/android/binder.c
if (proc->requested_threads == 0 &&
list_empty(&thread->proc->waiting_threads) &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))) {
proc->requested_threads++;
put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer);
}驱动同时检查四个状态:没有尚未响应的增员请求、没有空闲等待线程、已启动请求线程数低于上限、当前线程已经是合法 looper。requested_threads++ 先占住一个请求名额,避免多个返回路径同时要求用户态超额创建。
5. 用户态响应
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
case BR_SPAWN_LOOPER:
mProcess->spawnPooledThread(false);
break;收到命令的现有 Binder 线程调用 spawnPooledThread(false)。新线程随后发送 BC_REGISTER_LOOPER,内核才把“已请求”转换为“已启动”。因此 BR_SPAWN_LOOPER 不是新线程本身,而是驱动交给进程的一项待执行控制命令。
6. 手动加入
服务主线程常在 startThreadPool() 后调用 IPCThreadState::joinThreadPool();NDK 对应 ABinderProcess_startThreadPool() 与 ABinderProcess_joinThreadPool()。手动加入的当前线程额外存在,不计入驱动按 BINDER_SET_MAX_THREADS 懒启动的数量。
源码文件:frameworks/native/libs/binder/ndk/include_platform/android/binder_process.h
头文件明确说明:max count 是驱动可懒启动的线程数,除此之外还有 startThreadPool 创建的一个线程和 joinThreadPool 加入的当前线程。排查“配置 4 却看到 6 个线程”时必须纳入这两个额外来源。
7. 退出路径
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
非 main pool thread 在 getAndExecuteCommand() 返回 TIMED_OUT 时可以退出;main thread 不走该超时退出分支。退出前发送 BC_EXIT_LOOPER、清除 mIsLooper、向驱动 flush 命令,并递减 mCurrentThreads。若计数已经为零则 fatal,说明线程池配置或生命周期发生了不可能的下溢。
驱动 fd 被关闭时返回 -EBADF,连接拒绝时返回 -ECONNREFUSED,循环同样结束。这是进程/驱动终止路径,不应被业务代码当成普通空闲超时。
8. 反向测试
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
线程池测试把服务端可用线程数解释为 startThreadPool、joinThreadPool 和驱动启动线程之和,并用并行阻塞事务占满池。注释明确指出再增加一个调用会死锁。这反向证明 max thread 配置并不等于进程中 Binder looper 的总数。
源码文件:frameworks/native/libs/binder/tests/binderHostDeviceTestService.cpp
真实测试服务先 startThreadPool(),再让当前线程 joinThreadPool();若 join 返回则记录错误,证明服务 main looper 的正常生命周期是长期阻塞而非启动后立即返回。
9. 调试顺序
线程未扩容时依次检查:进程是否调用 startThreadPool;首线程是否发送 BC_ENTER_LOOPER;驱动的 waiting list 是否为空;requested_threads 是否已有未响应请求;requested_threads_started 是否达到 max_threads;用户态是否收到并处理 BR_SPAWN_LOOPER。只看线程名无法区分“驱动没有请求”和“用户态没有响应”。
10. 阅读检查
从 startThreadPool 复述到新线程发送 BC_REGISTER_LOOPER,并指出这条路径中谁拥有 mThreadPoolStarted、谁拥有 requested_threads、谁真正调用 pthread 创建线程。再解释为什么 joinThreadPool 会让实际线程数超过 max count。完成这条双向链路,才能继续分析线程上限与耗尽问题。
