Binder系列10 Binder线程池管理

一 概述

在系列1中我们知道 Binder 通信,归根结底是位于不同进程中的线程之间的通信.假如进程 S 是 Server 端,提供 Binder 实体,线程 T1 从 Client 进程 C 中通过 Binder 的引用向进程 S 发送请求。S 为了处理这个请求需要启动线程 T2,而此时线程 T1 处于接收返回数据的等待状态。T2 处理完请求就会将处理结果返回给 T1,T1 被唤醒得到处理结果.这个是 Binder 通信的基本过程.

对于 Server 进程 S 来说,可能会有许多 Client 同时向其发起请求,为了提高效率往往开辟线程池并发处理收到的请求.怎样使用线程池来实现并发处理呢?这和具体的 IPC 机制有关.对于 Binder 机制来说,它是怎么管理线程池的呢?一种简单的做法是,不管三七二十一,先创建一堆线程,每个线程都用 BINDER_WRITE_READ 命令读 Binder。这些线程会阻塞在驱动为该 Binder 设置的等待队列上,一旦有来自 Client 的数据,驱动会从等待队列中,唤醒一个线程来处理。这样做简单直观,省去了线程池,但一开始就创建一堆线程有点浪费资源。于是 Binder 协议引入了专门的命令或消息帮助 Binder 驱动管理线程池,包括:

  • BINDER_SET_MAX_THREADS
  • BC_REGISTER_LOOP
  • BC_ENTER_LOOP
  • BC_EXIT_LOOP
  • BR_SPAWN_LOOPER

首先要管理线程池就要知道池子有多大,应用程序通过 BINDER_SET_MAX_THREADS 告诉驱动,最多可以创建几个线程。以后每个线程在创建,进入主循环,退出主循环时,都要分别使用 BC_REGISTER_LOOP,BC_ENTER_LOOP,BC_EXIT_LOOP 告知驱动,以便驱动标记当前线程池中各个线程的状态。每当驱动接收完数据包,并且把数据包返回给读 Binder 线程的用户空间时,都要检查一下,线程池中是不是已经没有闲置线程了 .如果是,并且线程总数还没有达到线程池设定的最大线程数,就会在当前读出的数据包后面再追加一条 BR_SPAWN_LOOPER 命令,告诉 Server 端,线程即将不够用了,请再启动一个新线程,否则下一个请求可能不能及时响应。新线程一启动,又会通过 BC_xxx_LOOP 等一系列命令告知驱动更新线程的状态.这样确保了只要线程池的线程数量没有耗尽,总是会有空闲的线程在等待队列中随时待命,及时处理请求,这个就是 Binder 机制线程池管理的基本流程,下面比照代码详细分析.

二 Binder线程创建

我们知道在 MediaPlayerService 启动的 main 函数中,最后二行代码是关于 Binder 线程池的启动的,我们来看代码:

int main(int argc __unused, char **argv __unused)
{
    signal(SIGPIPE, SIG_IGN);
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm(defaultServiceManager());
    ALOGI("ServiceManager: %p", sm.get());
    InitializeIcuOrDie();
    MediaPlayerService::instantiate();//注册多媒体服务
    ResourceManagerService::instantiate();
    registerExtensions();
    ProcessState::self()->startThreadPool();//启动Binder线程池
    IPCThreadState::self()->joinThreadPool();//当前线程加入到线程池
}

2.1 ProcessState

2.1.1 startThreadPool

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;        
        spawnPooledThread(true);//创建线程,此处的参数true表示主线程
    }
}

启动 Binder 线程池后,则设置 mThreadPoolStarted 为 true.通过变量 mThreadPoolStarted 来保证每个应用进程只允许启动一个 Binder 线程池,且本次创建的是 Binder 主线程,以变量 isMain 为 true 为标志. 其余 Binder 线程池中的线程都是由 Binder 驱动通过发送 BR_SPAWN_LOOPER 命令来通知应用进程创建的.

2.1.2 spawnPooledThread

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();//Binder线程名称
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp<Thread> t = new PoolThread(isMain);//isMain为true表示主线程
        t->run(name.string());
    }
}
String8 ProcessState::makeBinderThreadName() {
    int32_t s = android_atomic_add(1, &mThreadPoolSeq);
    pid_t pid = getpid();
    String8 name;
    name.appendFormat("Binder:%d_%X", pid, s);//格式为Binder:pid_s(其中pid为进程号;s为序列号,每次累加1)
    return name;
}

获取 Binder 线程名称,格式为 Binder:pid_s(其中pid为进程号;s为序列号,每次累加1).每个进程中的 mThreadPoolSeq 是从1开始,依次递增; 只有通过 spawnPooledThread 方法来创建的线程才符合这个格式,对于直接将当前线程通过 joinThreadPool 加入线程池的线程名则不符合这个命名规则.

2.2 PoolThread

class PoolThread : public Thread
{
public:
    explicit PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }
    
protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);//将当前线程加入线程池,mIsMain为true
        return false;
    }
    
    const bool mIsMain;
};

该 PoolThread 类继承 Thread 类,调用它的 run() 方法最终会调用到 PoolThread 的 threadLoop() 方法.

2.3 IPCThreadState

2.3.1 joinThreadPool

void IPCThreadState::joinThreadPool(bool isMain)
{
    //线程进入循环,isMain为true表示主线程, false表示Binder驱动通知应用进程创建的线程
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    status_t result;
    do {
        processPendingDerefs();//清除队列的引用
        result = getAndExecuteCommand();//获取并执行指令
        ........
        //非主线程且timeout,则跳出循环,结束线程(主线程不会退出)
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);
    ........
    mOut.writeInt32(BC_EXIT_LOOPER);//通知Binder驱动线程退出
    talkWithDriver(false);//false表示不读Binder驱动数据,只写
}

joinThreadPool 的实际工作就是循环调用 getAndExecuteCommand 函数,这个 getAndExecuteCommand 函数的主要作用就是从 Binder 驱动读取数据并处理,主线程不会退出,非主线程超时的时候会退出,退出的时候需要向 Binder 驱动发送命令码 BC_EXIT_LOOPER 告知驱动.

2.3.2 processPendingDerefs

void IPCThreadState::processPendingDerefs()
{
    if (mIn.dataPosition() >= mIn.dataSize()) {
        size_t numPending = mPendingWeakDerefs.size();
        if (numPending > 0) {
            for (size_t i = 0; i < numPending; i++) {
                RefBase::weakref_type* refs = mPendingWeakDerefs[i];
                refs->decWeak(mProcess.get());//弱引用减1
            }
            mPendingWeakDerefs.clear();
        }

        numPending = mPendingStrongDerefs.size();
        if (numPending > 0) {
            for (size_t i = 0; i < numPending; i++) {
                BBinder* obj = mPendingStrongDerefs[i];
                obj->decStrong(mProcess.get());//强引用减1
            }
            mPendingStrongDerefs.clear();
        }
    }
}

2.3.3 getAndExecuteCommand

status_t IPCThreadState::getAndExecuteCommand()
{
    status_t result;
    int32_t cmd;

    result = talkWithDriver();//调用ioctl与Binder驱动交互
    if (result >= NO_ERROR) {
        size_t IN = mIn.dataAvail();
        if (IN < sizeof(int32_t)) return result;
        cmd = mIn.readInt32();
        ........
        result = executeCommand(cmd);//解析从Binder驱动读取的命令
       ........
    }
    return result;
}

getAndExecuteCommand 不断调用 talkWithDriver 读取从 Binder 驱动传输过来的数据,然后调用 executeCommand 函数解析并处理,关于 talkWithDriver 函数与 executeCommand 函数我们之前已经讲过,在此不再赘述.下面专门单独分析下 BR_SPAWN_LOOPER 命令的处理.

2.3.4 executeCommand

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    BBinder* obj;
    RefBase::weakref_type* refs;
    status_t result = NO_ERROR;

    switch ((uint32_t)cmd) {
    ........
    case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;
    ........
    }
    ........
    return result;
}

这个就是 Binder 驱动通过发送命令 BR_SPAWN_LOOPER 主动要求用户进程创建的非主线程,同样是调用 spawnPooledThread 函数用来创建线程,不同的是参数为 false 表示非主线程.

三 总结

3.1 Binder 线程池数量

我们知道我们可以通过 BINDER_SET_MAX_THREADS 命令来告知 Binder 驱动每个进程可以创建的最大的 Binder 线程的个数,一般来说这个值默认值为15,当然我们可以自己设定.但是要注意的是,这个不是说 Binder 线程池中最大的线程数目就是15个了,这个值仅仅是对Binder 驱动来说的,它只统计使用 BC_REGISTER_LOOPER 命令创建的线程个数,如果达到就不在创建了;举个我们讨论的 MediaPlayerService 的例子:

int main(int argc __unused, char **argv __unused)
{
    signal(SIGPIPE, SIG_IGN);
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm(defaultServiceManager());
    ALOGI("ServiceManager: %p", sm.get());
    InitializeIcuOrDie();
    MediaPlayerService::instantiate();//注册多媒体服务
    ResourceManagerService::instantiate();
    registerExtensions();
    ProcessState::self()->startThreadPool();//启动Binder线程池
    IPCThreadState::self()->joinThreadPool();//当前线程加入到线程池
}

这个进程的线程池中最多可以有几个 Binder 线程呢?我们来计算下,因为在 ProcessState 初始化的过程中会调用 open_driver 函数,在这个函数中设置了最大线程数为15,所以这15个线程是保底的,另外 main 函数的最后二行代码的 startThreadPool,我们知道是新启动了一个线程,并设置为主线程,是通过 BC_ENTER_LOOP 来创建的,不计入15个之列,所以又可以创建一个线程;同时看最后一行代码 IPCThreadState::self()->joinThreadPool() 是把当前的线程添加到线程池中,joinThreadPool 默认参数为 true,所以也是主线程,也是通过 BC_ENTER_LOOP 命令来创建的,所以又可以创建一个线程了,所以这个Media服务进程最多可以有17个线程在工作,大家要理解.

3.2 Binder线程的分类

从以上对 Binder 线程池数量的分析我们已经知道了 Binder 线程可以有三类,总结如下:

  • Binder主线程:在进程的 main 函数中通过调用 startThreadPool() 函数创建的线程
  • Binder普通线程:是由 Binder 驱动通过发送 BR_SPAWN_LOOPER 命令,然后应用进程调用 spawnPooledThread 函数创建的线程
  • Binder其它线程:是调用 IPC.joinThreadPool(),将当前线程直接加入 Binder 线程队列的线程,例如 media 的主线程
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值