4、Binder线程模型:Binder主线程、Binder线程池、BC_ENTER_LOOPER/BC_EXIT_LOOPER、线程唤醒机制
Binder 线程模型是理解跨进程调用超时与重试的基石。Binder 驱动并不直接创建线程,而是通过 BC_ENTER_LOOPER 和 BC_EXIT_LOOPER 协议与用户空间交互,由用户空间管理线程的创建与销毁。本章深入剖析 Binder 主线程、线程池的运作机制,以及驱动如何唤醒等待线程处理事务。
4.1 Binder 线程模型架构
Binder 线程模型采用 主线程 + 线程池 的架构。每个进程在首次通过 open() 打开 Binder 设备时,会隐式创建一个 Binder 主线程。随后,通过 ioctl() 发送 BC_ENTER_LOOPER 命令,该线程进入循环等待状态。线程池则通过 BC_REGISTER_LOOPER 注册辅助线程,用于处理并发请求。
关键概念: Binder 主线程是进程内第一个进入循环的线程,通常由
ProcessState::startThreadPool() 启动。线程池中的线程数量由 BC_ACQUIRE_RESULT 和驱动协商决定,最大上限为 16 个(Android 默认配置)。
4.2 Binder 主线程与 BC_ENTER_LOOPER
Binder 主线程是进程内第一个与驱动交互的线程。其生命周期如下:
- 打开设备: 调用
open("/dev/binder", O_RDWR)获取文件描述符。 - 内存映射: 调用
mmap()分配内核缓冲区,用于数据传输。 - 进入循环: 通过
ioctl()发送BC_ENTER_LOOPER命令,通知驱动该线程成为主线程。 - 等待事务: 线程进入
BINDER_LOOPER_STATE_ENTERED状态,阻塞在ioctl()的BINDER_WRITE_READ操作上。
代码示例: 主线程进入循环的核心流程
// frameworks/native/libs/binder/ProcessState.cpp
void ProcessState::startThreadPool() {
AutoMutex _l(mLock);
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true); // true 表示主线程
}
}
void ProcessState::spawnPooledThread(bool isMain) {
sp<Thread> t = new PoolThread(isMain);
t->run("Binder_%d", mThreadPoolSeq++);
}
// PoolThread 的 threadLoop 最终调用 IPCThreadState::joinThreadPool
void IPCThreadState::joinThreadPool(bool isMain) {
// 发送 BC_ENTER_LOOPER 或 BC_REGISTER_LOOPER
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
// 进入循环
do {
processPendingDerefs();
result = getAndExecuteCommand(); // 阻塞在 ioctl
} while (result != TIMED_OUT && result != -ECONNREFUSED);
// 退出时发送 BC_EXIT_LOOPER
mOut.writeInt32(BC_EXIT_LOOPER);
talkWithDriver();
}
4.3 Binder 线程池与 BC_REGISTER_LOOPER
线程池用于处理并发请求。当主线程繁忙时,驱动会通过 BC_TRANSACTION 通知用户空间创建新线程。线程池的管理遵循以下规则:
| 命令 | 含义 | 触发时机 |
|---|---|---|
BC_ENTER_LOOPER |
声明当前线程为主线程 | 进程启动时,由 startThreadPool() 发送 |
BC_REGISTER_LOOPER |
声明当前线程为线程池线程 | 驱动通过 BC_TRANSACTION 请求创建新线程时 |
BC_EXIT_LOOPER |
声明当前线程退出循环 | 线程空闲超时或进程销毁时 |
注意: 线程池的最大线程数由
BINDER_MAX_THREADS 宏定义(默认 15,加上主线程共 16 个)。超过此限制时,驱动会将事务放入等待队列,直到有线程空闲。
4.4 BC_EXIT_LOOPER 与线程销毁
当线程无事可做或进程退出时,会发送 BC_EXIT_LOOPER 命令。驱动收到该命令后,会执行以下操作:
- 移除线程记录: 从驱动的线程红黑树中删除该线程节点。
- 释放资源: 释放线程持有的内核缓冲区引用。
- 唤醒等待线程: 如果等待队列中有其他线程,驱动会尝试唤醒一个线程来处理剩余事务。
4.5 线程唤醒机制
Binder 驱动使用 Linux 内核的 等待队列(wait_queue_head_t) 实现线程唤醒。当驱动接收到新事务时,会执行以下唤醒逻辑:
- 查找目标进程: 根据事务的目标 handle 找到目标进程的 proc 结构体。
- 检查就绪线程: 遍历目标进程的
todo_list,查找处于BINDER_LOOPER_STATE_WAITING状态的线程。 - 唤醒线程: 调用
wake_up_interruptible()唤醒等待队列中的线程。 - 分配事务: 将事务挂载到被唤醒线程的
todo_list中,线程从ioctl()返回并处理事务。
内核源码关键路径:
// kernel/drivers/android/binder.c
static void binder_wakeup_thread(struct binder_proc *proc,
struct binder_thread *thread,
bool sync) {
// 如果指定了线程,直接唤醒
if (thread) {
wake_up_interruptible(&thread->wait);
return;
}
// 否则遍历进程的线程红黑树,找到等待线程
struct rb_node *n = rb_first(&proc->threads);
while (n) {
thread = rb_entry(n, struct binder_thread, rb_node);
if (thread->looper & BINDER_LOOPER_STATE_WAITING) {
wake_up_interruptible(&thread->wait);
break;
}
n = rb_next(n);
}
}
4.6 线程模型与超时重试的关系
理解线程模型对分析超时与重试至关重要:
| 场景 | 线程状态 | 超时影响 | 重试策略 |
|---|---|---|---|
| 主线程繁忙 | 正在处理事务 | 新事务进入等待队列,可能超时 | 驱动尝试唤醒线程池线程 |
| 线程池满 | 所有线程均忙碌 | 事务排队,等待时间不可控 | 客户端需自行重试或增加超时时间 |
| 线程退出 | 发送 BC_EXIT_LOOPER | 驱动释放资源,事务可能丢失 | 驱动返回 BR_DEAD_REPLY,客户端重试 |
| 线程唤醒失败 | 等待队列中但未收到信号 | 事务永久阻塞,触发超时 | 客户端超时后重试,驱动清理僵尸线程 |
实践建议: 在分析超时问题时,首先检查目标进程的线程池是否耗尽。可以通过
adb shell ls /proc/[pid]/task/ | wc -l 查看线程数,若接近 16 个,则说明线程池已满,需要优化服务端处理速度或增加线程池上限。