在Binder通信机制里,客户端与服务端之间的通信是在专门的IPC通信线程中进行的。这些线程构成一个线程池。线程的创建和销毁是在用户空间进行的,而对线程的控制是在驱动层进行的,即驱动控制线程池中线程的生命,而线程本身则是运行在用户空间的。驱动层是通过BR_SPAWN_LOOPER向用户空间发送创建新线程的命令。
线程池的大小可以设置, 如果没有主动去设置这个大小,则默认大小为15,如下代码所示:
在ProcessSate.cpp在构造函数里,会调用open_driver函数,里面会进行线程池默认大小的设置:
static int open_driver()
{
int fd = open("/dev/binder", O_RDWR);
if (fd >= 0) {
...
size_t maxThreads = 15;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
if (result == -1) {
ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
}
} else {
ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
}
return fd;
}
与线程池相关的几个变量设置在struct binder_proc结构体中:
struct binder_proc {
...
int max_threads;//max thread
int requested_threads;//
int requested_threads_started;//requested thread seq No
int ready_threads;//available for use
...
};
其中,max_threads表示当前进程线程池的大小。ioctl命令BINDER_SET_MAX_THREADS用来设置这个值,默认情况下是15,如上所述。ready_threads表示当前线程池中有多少可用的空闲线程。requested_threads请求开启线程的数量。requested_threads_started表示当前已经接受请求开启的线程数量。
对于IPC通信的服务端进程,一般会执行如下的调用启动线程池:
ProcessState::self()->setThreadPoolMaxThreadCount(4);
ProcessState::self()->startThreadPool();
IPCThread::self()->joinThreadPool();
上述代码会在一个进程中执行,并产生两个线程,其中一个是进程的主线程,但这两个线程都是Binder主线程,即他不会纳入线程池的管理。网上有不少说法是最后一个调用看起来是多余的,原因是去掉后,也可以正常执行,但是其实不然。在我看来,在主线程中调用IPCThread::self()->joinThreadPool()的一层目的是确保前一句调用产生的线程不会因为执行到了main函数结尾而被迫退出,因为ProcessState::self()->startThreadPool()不会导致主线程阻塞,而IPCThread::self()->joinThreadPool()调用才会导致主线程阻塞。并且,它们提供相同的功能:它能处理Binder驱动发送上来的一些请求或返回值,进一步提高了Binder命令处理的吞吐量。当然,如果在他们之间加入了让主线程阻塞的代码,则最后的函数调用是可以省略的,否则是不能省略的。
执行完上述语句后, 已经有两个服务线程了,此时线程池线程的数量为0,后续可以创建4个线程。默认情况下,线程池中没有线程,由于本身已经有了2个线程可用,一般情况下,能满足要求。但是,当有多个并发的IPC请求时,可能会触发内核增加更多的IPC通信线程来服务这些请求。当接受Binder驱动从内核中发出的BR_SPAWN_LOOPER命令时,会启动一个非Binder主线程。我们来看下在什么情况下会触发这种情况:
if (proc->requested_threads + proc->ready_threads == 0 &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
/*spawn a new thread if we leave this out */) {
proc->requested_threads++;
binder_debug(BINDER_DEBUG_THREADS,
"%d:%d BR_SPAWN_LOOPER\n",
proc->pid, thread->pid);
if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
return -EFAULT;
binder_stat_br(proc, thread, BR_SPAWN_LOOPER);
}
在请求的线程数和空闲的线程数为零且已经请求并开启的线程数小于线程池的最大允许线程数量时,就向用户空间发送命令,以开启新的接收线程来处理请求。因为此时,接收进程中所有的线程都在忙碌中。
在用户空间通过如下调用启动线程:
mProcess->spawnPooledThread(false);
最终会调用如下函数:
void IPCThreadState::joinThreadPool(bool isMain)
{
LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid());
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
…
}
这种情况下,会通过向Binder驱动发BC_REGISTER_LOOPER通知驱动用户空间线程已经创建,这样驱动也会做些数据统计:
case BC_REGISTER_LOOPER:
…
} else {
proc->requested_threads--;
proc->requested_threads_started++;
}
thread->looper |= BINDER_LOOPER_STATE_REGISTERED;
break;
驱动会更新proc->requested_threads_started来统计当前已经请求开启并成功开启的线程数量,这个值将作为判断线程池是否已经满的依据。
而在Binder驱动层,会跟用户空间的线程关联一个struct binder_thread实例,这个结构记录了内核空间的在该线程上执行的一些IPC状态信息。其数据结构定义如下:
struct binder_thread {
struct binder_proc *proc; //线程所属的进程
struct rb_node rb_node; //红黑树的结点,进程通过该结点将线程加入到红黑树中
int pid; //线程的pid
int looper; //线程所处的状态
struct binder_transaction *transaction_stack;//transaction session list on this thread
struct list_head todo; //在该线程上的Task列表
uint32_t return_error; /* Write failed, return error code in read buf */
uint32_t return_error2; /* Write failed, return error code in read */
/* buffer. Used when sending a reply to a dead process that */
/* we are also waiting on */
wait_queue_head_t wait; //该线程的等待队列
struct binder_stats stats; //统计该线程上的命令数量
};