Framework篇 - Binder 线程池

本文源代码基于 Android 7.0。

本文主要分析 Binder 线程池以及 Binder 线程启动过程。

 

目录:

  1. 概述
  2. 分析
  3. 总结

 

 

1. 概述

Android 系统启动完成后,ActivityManager,PackageManager 等各大服务都运行在 system server 进程, app 应用需要使用系统服务都是通过 binder 来完成进程之间的通信。Binder 线程是如何管理的呢,又是如何创建的呢? 其实无论是system server 进程,还是 app 进程,都是在进程 fork 完成后,便会在新进程中执行 onZygoteInit() 的过程中, 启动 Binder 线程池。接下来,就以此为起点展开从线程的视角来看看 Binder 的世界。

 

 

2. 分析

Binder 线程于其所在进程的创建中产生,Java 层进程的创建都是通过 Process.start() 方法,向 Zygote 进程发出创建进程的 Socket 消息, Zygote 收到消息后会调用 Zygote.forkAndSpecialize() 来 fork 出新进程,在新进程中会调用到RuntimeInit.nativeZygoteInit 方法, 该方法经过 JNI 映射,最终会调用到 app_main.cpp 中的 onZygoteInit,那么接下来从这个方法说起。

 

  • 2.1 onZygoteInit

/base/cmds/app_process/app_main.cpp

    virtual void onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        proc->startThreadPool();
    }

做了两件事:

  • 1. 获取 ProcessState 对象。
  • 2. 启动新的 Binder 线程池。

ProcessState::self() 是单例模式,主要工作是调用 open() 打开 /dev/binder 驱动设备,再利用 mmap() 映射内核的地址空间, 将Binder 驱动的 fd 赋值 ProcessState 对象中的变量 mDriverFD,用于交互操作。

startThreadPool() 是创建一个新的 Binder 线程池,不断进行 talkWithDriver()。

 

  • 2.2 PS.startThreadPool

/native/libs/binder/ProcessState.cpp

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

启动 Binder 线程池后, 则设置 mThreadPoolStarted=true。通过变量 mThreadPoolStarted 来保证每个应用进程只允许启动一个Binder 线程池,且本次创建的是 Binder 主线程 (isMain=true)。其余 Binder 线程池中的线程都是由 Binder 驱动来控制创建的。

也就是说每次 fork 出一个新进程,都会启动一个 Binder 线程池,只允许启动一个 Binder 线程池。

 

  • 2.3 PS.spawnPooledThread

/native/libs/binder/ProcessState.cpp

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp<Thread> t = new PoolThread(isMain);
        t->run(name.string());
    }
}

获取 Binder 线程名,格式为 Binderx,其中 x 为整数。每个进程中的 Binder 编码是从 1 开始,依次递增。只有通过spawnPooledThread 方法来创建的线程才符合这个格式,对于直接将当前线程通过 joinThreadPool 加入线程池的线程名则不符合这个命名规则。 另外,目前 Android N 中 Binder 命令已改为 Binder:x 格式,则对于分析问题很有帮忙,通过 Binder 名称的pid 字段可以快速定位该 Binder 线程所属的进程 p。

 

  • 2.4 PoolThread.run

/native/libs/binder/ProcessState.cpp

class PoolThread : public Thread
{
public:
    PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }
    
protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }
    
    const bool mIsMain;
};

从函数名看起来是创建线程池,其实就只是创建一个线程,该 PoolThread 继承 Thread 类。t->run() 方法最终调用 PoolThread的 threadLoop() 方法。

 

  • 2.6 IPC.joinThreadPool
     

/native/libs/binder/IPCThreadState.cpp

void IPCThreadState::joinThreadPool(bool isMain)
{
    // ...
    do {
        // 清除队列的引用
        processPendingDerefs();
        // 处理下一条指令
        result = getAndExecuteCommand();

        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
            ALOGE("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",
                  mProcess->mDriverFD, result);
            abort();
        }
        
        // 非主线程出现timeout则线程退出
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);
    // 线程退出循环
    mOut.writeInt32(BC_EXIT_LOOPER);
    // false 代表 bwr 数据的 read_buffer 为空
    talkWithDriver(false);
}

对于 isMain=true 的情况下, command 为 BCENTERLOOPER,代表的是 Binder 主线程,不会退出的线程。对于 isMain=false 的情况下,command 为 BCREGISTERLOOPER,表示是由 Binder 驱动创建的线程。

 

  • 2.7 思考

默认地,每个进程的 Binder 线程池的线程个数上限为 15,该上限不统计通过 BCENTERLOOPER 命令创建的 Binder 主线程, 只计算 BCREGISTERLOOPER 命令创建的线程。 对此,或者很多人不理解,例个例子:某个进程的主线程执行如下方法,那么该进程可创建的 Binder 线程个数上限是多少呢?

ProcessState::self()->setThreadPoolMaxThreadCount(6); // 6个线程 

ProcessState::self()->startThreadPool(); // 1个线程 

IPCThread::self()->joinThreadPool(); // 1个线程 

首先线程池的 Binder 线程个数上限为 6 个,通过 startThreadPool() 创建的主线程不算在最大线程上限,最后一句是将当前线程成为 Binder 线程, 所以说可创建的 Binder 线程个数上限为8,如果还不理解,建议再多看看这几个方案的源码,多思考整个 Binder 架构。

 

  • 2.8 Binder 使用线程处理请求

举个例子来理解下:

Client 进程 A 中用户线程 1 -> 内核态记录进程线程信息 -> transaction 发送数据 -> 保存到目标进程的队列。
Client 进程 A 中用户线程 2 -> 内核态记录进程线程信息 -> transaction 发送数据 -> 保存到目标进程的队列。
Client 进程 A 中用户线程 3 -> 内核态记录进程线程信息 -> transaction 发送数据->保存到目标进程的队列。

假如线程 1 在目标进程的队列第一个,线程 2 在目标进程的队列第二个,线程 3 在目标进程的队列第三个。

Binder 线程是用户空间创建,内核空间控制,线程在内核空间的红黑树上。用户空间创建的线程,加入线程池中,设置调度策略(一般是分时调度策略)和优先级别,假设 Service 端创建 Binder 线程:Binder 线程 1、Binder 线程 2、Binder 线程 3。

Binder 线程信息:

struct binder_thread {
      ...
      struct binder_transaction *transaction_stack;  //要发送和接收进程和线程的信息
      ...
}

调度合适的 Binder 线程:

  • 判断一下 Binder 线程 1,是否处于空闲状态,如果空闲则取出目标进程的队列中线程 1 数据,发送到 Service 端处理数据,Service 返回处理结果,发送到 Client 端,并附带命令 BR_SPAWN_LOOPER 请求再创建一个 Binder 线程 2。
  • 判断一下 Binder 线程 1 或线程 2,是否处于空闲状态,如果线程 1 还在处理则 Binder 线程 2 来处理数据 2。
  • 判断一下 Binder 线程 1,是否处于空闲状态,如果空闲则处理数据 3。

 

 

3. 总结

Binder 设计架构中,只有第一个 Binder 主线程 (也就是 Binder_1 线程) 是由应用程序主动创建,Binder 线程池的普通线程都是由Binder 驱动根据 IPC 通信需求创建,Binder 线程的创建流程图:

 

每次由 Zygote fork 出新进程的过程中,伴随着创建 Binder 线程池,调用 spawnPooledThread 来创建 Binder 主线程。 当线程执行 binderthreadread 的过程中,发现当前没有空闲线程,没有请求创建线程,且没有达到上限,则创建新的 Binder 线程。

 

Binder 的 transaction 有 3 种类型:

  • call: 发起进程的线程不一定是在 Binder 线程, 大多数情況下,接收者只指向进程,并不确定会有哪个线程来处理,所以不指定线程。
  • reply: 发起者一定是 Binder 线程,并且接收者线程便是上次 call 时的发起线程 (该线程不一定是 Binder 线程,可以是任意线程)。
  • async: 与 call 类型差不多,唯一不同的是 async 是 oneway 方式不需要回复,发起进程的线程不一定是在 Binder 线程, 接收者只指向进程,并不确定会有哪个线程来处理,所以不指定线程。

 

Binder 系统中可分为 3 类 Binder 线程:

  • Binder 主线程:进程创建过程会调用 startThreadPool() 过程中再进入 spawnPooledThread(true),来创建 Binder 主线程。 编号从 1 开始,也就是意味着 Binder 主线程名为 binder1,并且主线程是不会退出的。
  • Binder 普通线程:是由 Binder Driver 来根据是否有空闲的 Binder 线程来决定是否创建 Binder 线程,回调spawnPooledThread(false) ,isMain=false, 该线程名格式为 binderx。
  • Binder 其他线程:其他线程是指并没有调用 spawnPooledThread 方法,而是直接调用 IPC.joinThreadPool(),将当前线程直接加入 Binder 线程队列。 例如: mediaserver 和 servicemanager 的主线程都是 Binder 线程,但 system_server 的主线程并非 Binder 线程。

 

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值