一 概述
在系列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 的主线程