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 默认配置)。
Binder 线程模型架构图 用户空间进程 A Binder 主线程 (BC_ENTER_LOOPER) 线程池线程 1 (BC_REGISTER_LOOPER) 线程池线程 2 (BC_REGISTER_LOOPER) 线程池线程 N (BC_REGISTER_LOOPER) 内核空间 Binder 驱动 事务等待队列 (wait_queue_head_t) 就绪线程列表 (todo_list) 线程状态管理 BC_ENTER_LOOPER BC_EXIT_LOOPER 说明:主线程通过 BC_ENTER_LOOPER 进入循环,线程池线程通过 BC_REGISTER_LOOPER 注册,驱动通过等待队列管理线程唤醒。

4.2 Binder 主线程与 BC_ENTER_LOOPER

Binder 主线程是进程内第一个与驱动交互的线程。其生命周期如下:

  1. 打开设备: 调用 open("/dev/binder", O_RDWR) 获取文件描述符。
  2. 内存映射: 调用 mmap() 分配内核缓冲区,用于数据传输。
  3. 进入循环: 通过 ioctl() 发送 BC_ENTER_LOOPER 命令,通知驱动该线程成为主线程。
  4. 等待事务: 线程进入 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 命令。驱动收到该命令后,会执行以下操作:

  • 移除线程记录: 从驱动的线程红黑树中删除该线程节点。
  • 释放资源: 释放线程持有的内核缓冲区引用。
  • 唤醒等待线程: 如果等待队列中有其他线程,驱动会尝试唤醒一个线程来处理剩余事务。
BC_EXIT_LOOPER 处理流程 用户线程 发送 BC_EXIT_LOOPER Binder 驱动 移除线程红黑树节点 释放内核缓冲区引用 唤醒等待队列中的线程

4.5 线程唤醒机制

Binder 驱动使用 Linux 内核的 等待队列(wait_queue_head_t) 实现线程唤醒。当驱动接收到新事务时,会执行以下唤醒逻辑:

  1. 查找目标进程: 根据事务的目标 handle 找到目标进程的 proc 结构体。
  2. 检查就绪线程: 遍历目标进程的 todo_list,查找处于 BINDER_LOOPER_STATE_WAITING 状态的线程。
  3. 唤醒线程: 调用 wake_up_interruptible() 唤醒等待队列中的线程。
  4. 分配事务: 将事务挂载到被唤醒线程的 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 个,则说明线程池已满,需要优化服务端处理速度或增加线程池上限。

4.7 本章小结