Binder线程上限
setThreadPoolMaxThreadCount() 只写入用户态配置并通过 ioctl 告诉驱动上限;它不会同步创建线程,也不能把已经启动的线程强行缩容。下图区分配置 owner、驱动计数和实际消费者。
本文承接 Binder线程池创建。核心问题是“设置为 3 到底限制了什么”:Android 17 中这个值限制的是 Binder 驱动可以懒启动的请求线程数,不是进程中所有 Binder looper 的总数,也不会删除已经创建的线程。本文从 ProcessState 的 setter 追到 ioctl、驱动字段和统计函数,并用真实测试说明几个容易误读的数字。
1. Native入口
源码文件:frameworks/native/libs/binder/ProcessState.cpp
status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {
LOG_ALWAYS_FATAL_IF(mThreadPoolStarted && maxThreads < mMaxThreads,
"Binder threadpool cannot be shrunk after starting");
status_t result = NO_ERROR;
if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &maxThreads) != -1) {
mMaxThreads = maxThreads;
} else {
result = -errno;
ALOGE("Binder ioctl to set max threads failed: %s", strerror(-result));
}
return result;
}setter 有两个 owner:用户态缓存 mMaxThreads,驱动进程对象里的 max_threads。只有 ioctl 成功后才更新用户态缓存;失败时返回负 errno,避免用户态以为新上限已经生效。
2. NDK入口
源码文件:frameworks/native/libs/binder/ndk/process.cpp
bool ABinderProcess_setThreadPoolMaxThreadCount(uint32_t numThreads) {
return ProcessState::self()->setThreadPoolMaxThreadCount(numThreads) == 0;
}NDK API 把 status_t 压缩为 bool,调用方如果需要区分 EBADF、驱动拒绝等原因,应使用 Native ProcessState 或读取错误日志。NDK 头文件还要求该配置在 startThreadPool 前完成。
3. 驱动存储
源码文件:kernel/common/drivers/android/binder.c
case BINDER_SET_MAX_THREADS: {
u32 max_threads;
if (copy_from_user(&max_threads, ubuf, sizeof(max_threads)))
return -EFAULT;
binder_inner_proc_lock(proc);
proc->max_threads = max_threads;
binder_inner_proc_unlock(proc);
break;
}驱动只保存上限,不在 ioctl 返回时创建线程。真正的创建仍由后续事务读取路径在满足“没有等待线程、没有未响应请求、当前启动数低于上限”等条件时发出 BR_SPAWN_LOOPER。
4. 不可收缩
ProcessState 在池已经启动后禁止把上限调小,直接触发 fatal。这个限制是必要的:驱动可能已经请求线程,用户态计数和 looper 状态不能安全地回滚;而且已经运行的线程不会因降低配置自动退出。增加上限则可以在启动后写入驱动,但新线程仍按需懒启动。
5. 总数计算
源码文件:frameworks/native/libs/binder/ProcessState.cpp
size_t ProcessState::getThreadPoolMaxTotalThreadCount() const {
size_t kernelStarted = mKernelStartedThreads;
if (mThreadPoolStarted) {
size_t max = mMaxThreads;
size_t current = mCurrentThreads;
size_t threads = 1; // startThreadPool
threads += max; // kernel may start these
if (current > kernelStarted) {
threads += current - kernelStarted; // direct joinThreadPool callers
}
return threads;
}
return mCurrentThreads;
}返回值是“理论最大总数”的估算:首个 startThreadPool 线程 + 驱动最多懒启动的线程 + 直接调用 joinThreadPool 的额外线程。它不是当前 live thread 数,且注释明确承认 mKernelStartedThreads 与 mCurrentThreads 存在竞态窗口。
6. 配置时机
正确顺序是:设置上限、调用 startThreadPool、必要时让主线程 joinThreadPool。如果先启动线程池再把值设小,会 fatal;如果把上限设为 0,startThreadPool 仍会创建首线程,只是驱动不再请求额外 worker。上限为 0 不是“禁止 Binder 服务工作”,而是单 looper 配置。
7. 测试边界
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
测试通过 BINDER_LIB_TEST_GET_MAX_THREAD_COUNT 查询服务端计算值,并注明结果可能是 kKernelThreads + 1 或 +2,因为线程创建和计数更新存在竞态。随后并行占满线程池,再增加一次调用,验证超出可用 looper 后会阻塞。
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
驱动接口测试直接对 BINDER_SET_MAX_THREADS 传入合法值、空指针和错误参数,验证 ioctl 的用户指针复制边界。它证明的是驱动接口错误处理,不证明线程已经按配置创建。
8. 常见误读
“maxThreads=3,所以最多 3 个线程”错误地漏掉首线程和手动加入线程;“调小后线程会减少”与源码相反;“setter 返回 true 就已经有 3 个 worker”也不对,因为驱动采用懒启动。排查应同时查看 mMaxThreads、requested_threads_started、mCurrentThreads 和实际 looper 状态。
9. 阅读检查
给定配置值 2,复述三种数量:Native setter 写入的驱动上限、getThreadPoolMaxTotalThreadCount() 的理论值、当前可能已经创建的线程数。再指出为何启动后缩容会 fatal,以及哪个源码分支决定新 worker 何时真正出现。
