正文
为什么 Android 要采用 Binder 作为 IPC 机制?
这道题想考察什么?
Binder作为IPC机制的优势。
你应该如何回答
简单来说,Binder 是android系统工程师为android 定制的一个跨进程通信方法,当然它也不是android 系统原创的,是参考了OpenBinder的实现而引进到Google的。Binder是综合了android系统的特点,从性能,设计架构,安全性等几个方面的综合平衡而设计的,具体的关于Binder的实现细节,朋友们可以参考 上面的题目 《描述下Binder机制原理》进行系统学习。
应该从几个方面与传统IPC机制做对比。
- 性能方面
- 拷贝数据需要花时间,Binder只需拷贝一次,共享内存无需拷贝,其他的需要拷贝两次。
- 从速度上来说,Binder仅次于共享内存,优于Socket,消息队列,管道,信号,信号量等。
- 特点方面
- Binder:基于C/S 架构,易用性高。
- 共享内存:
- 多个进程共享同一块内存区域,必然需要某种同步机制。
- 使用麻烦,容易出现数据不同步,死锁等问题。
- Socket:
- socket作为一款通用接口,其传输效率低,开销大。
- 主要用在跨网络的进程间通信和本机上进程间的低速通信。
- 安全性方面
- Binder:(安全性高)
- 为每个APP分配不同UID,通过UID鉴别进程身份。
- 即支持实名Binder,又支持匿名Binder。
- 传统IPC:(不安全)
- 完全依赖上层协议,只能由用户在数据包中填入UID/PID。
- 访问接入点是开放的,任何程序都可以与其建立连接。
- Binder:(安全性高)
通过上面几个比较,特别是安全性这块,所以最终Android选择使用Binder机制进行通信。
Binder线程池的工作过程是什么样?
这道题想考察什么?
这道题想考察同学对于Binder线程池的理解。
你应该如何回答
总的来说,有以下几点情况:1)binder线程池并非一个传统意义上的线程池结构,它在client进程中只有一个继承自Thread的PoolThread类。而线程的启动以及管理都是由binderDriver来控制的。2)binder线程有主线程和非主线程之分,主线程是启动的时候才会有的,每个binder线程池只有一个。其他情况下申请的都是非主线程。
3)binder线程池启动的时候,实际上只是启动了client中的binder主线程。4)binder线程(非主线程)有两种情况启动:client进程向binderDriver发送IPC请求,以及 client进程向binderDriver回复IPC请求结果。5)binder线程池的默认大小是16,1个主线程和15个非主线程。更多的细节我们就需要从Binder线程创建开始讲解了,大家可以参考下面的内容:
1.Binder线程创建
Binder线程创建与其所在进程的创建中产生,Java层进程的创建都是通过Process.start()方法,向Zygote进程发出创建进程的socket消息,Zygote收到消息后会调用Zygote.forkAndSpecialize()来fork出新进程,在新进程中会调用到RuntimeInit.nativeZygoteInit方法,该方法经过jni映射,最终会调用到app_main.cpp中的onZygoteInit,那么接下来从这个方法说起。
2.onZygoteInit
// app_main.cpp
virtual void onZygoteInit() {
//获取ProcessState对象
sp<ProcessState> proc = ProcessState::self();
//启动新binder线程
proc->startThreadPool();
}
ProcessState::self()是单例模式,主要工作是调用open()打开/dev/binder驱动设备,再利用mmap()映射内核的地址空间,将Binder驱动的fd赋值ProcessState对象中的变量mDriverFD,用于交互操作。startThreadPool()是创建一个新的binder线程,不断进行talkWithDriver()。
3.PS.startThreadPool
// ProcessState.cpp
void ProcessState::startThreadPool()
{
AutoMutex _l(mLock); //多线程同步
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
启动Binder线程池后, 则设置mThreadPoolStarted=true. 通过变量mThreadPoolStarted来保证每个应用进程只允许启动一个binder线程池, 且本次创建的是binder主线程(isMain=true). 其余binder线程池中的线程都是由Binder驱动来控制创建的。
4.PS.spawnPooledThread
// ProcessState.cpp
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
//获取Binder线程名
String8 name = makeBinderThreadName();
//此处isMain=true
sp<Thread> t = new PoolThread(isMain);
t->run(name.string());
}
}
4-1.makeBinderThreadName
// ProcessState.cpp
String8 ProcessState::makeBinderThreadName() {
int32_t s = android_atomic_add(1, &mThreadPoolSeq);
String8 name;
name.appendFormat("Binder_%X", s);
return name;
}
获取Binder线程名,格式为Binder_x, 其中x为整数。每个进程中的binder编码是从1开始,依次递增; 只有通过spawnPooledThread方法来创建的线程才符合这个格式,对于直接将当前线程通过joinThreadPool加入线程池的线程名则不符合这个命名规则。 另外,目前Android N中Binder命令已改为Binder:_x格式, 则对于分析问题很有帮忙,通过binder名称的pid字段可以快速定位该binder线程所属的进程p.
4-2.PoolThread.run
// 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()方法。
5. IPC.joinThreadPool
// IPCThreadState.cpp
void IPCThreadState::joinThreadPool(bool isMain)
{
//创建Binder线程
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
set_sched_policy(mMyThreadId, SP_FOREGROUND); //设置前台调度策略
status_t result;
do {
processPendingDerefs(); //清除队列的引用
result = getAndExecuteCommand(); //处理下一条指令
if (result < NO_ERROR && result != TIMED_OUT
&& result != -ECONNREFUSED && result != -EBADF) {
abort();
}
if(result == TIMED_OUT && !isMain) {
break; 非主线程出现timeout则线程退出
}
} while (result != -ECONNREFUSED && result != -EBADF);
mOut.writeInt32(BC_EXIT_LOOPER); // 线程退出循环
talkWithDriver(false); //false代表bwr数据的read_buffer为空
}
- 对于isMain=true的情况下, command为BC_ENTER_LOOPER,代表的是Binder主线程,不会退出的线程;
- 对于isMain=false的情况下,command为BC_REGISTER_LOOPER,表示是由binder驱动创建的线程。
6. processPendingDerefs
// IPCThreadState.cpp
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()); //弱引用减一
}
mPendingWeakDerefs.clear();
}
numPending = mPendingStrongDerefs.size();
if (numPending > 0) {
for (size_t i = 0; i < numPending; i++) {
BBinder* obj = mPendingStrongDerefs[i];
obj->decStrong(mProcess.get()); //强引用减一
}
mPendingStrongDerefs.clear();
}
}
}
7. getAndExecuteCommand
// IPCThreadState.cpp
status_t IPCThreadState::getAndExecuteCommand()
{
status_t result;
int32_t cmd;
result = talkWithDriver(); //与binder进行交互
if (result >= NO_ERROR) {
size_t IN = mIn.dataAvail();
if (IN < sizeof(int32_t)) return result;
cmd = mIn.readInt32();
pthread_mutex_lock(&mProcess->mThreadCountLock);
mProcess->mExecutingThreadsCount++;
pthread_mutex_unlock(&mProcess->mThreadCountLock);
result = executeCommand(cmd); //执行Binder响应码
pthread_mutex_lock(&mProcess->mThreadCountLock);
mProcess->mExecutingThreadsCount--;
pthread_cond_broadcast(&mProcess->mThreadCountDecrement);
pthread_mutex_unlock(&mProcess->mThreadCountLock);
set_sched_policy(mMyThreadId, SP_FOREGROUND);
}
return result;
}
8. talkWithDriver
//mOut有数据,mIn还没有数据。doReceive默认值为true
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
binder_write_read bwr;
...
// 当同时没有输入和输出数据则直接返回
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
...
do {
//ioctl执行binder读写操作,经过syscall,进入Binder驱动。调用Binder_ioctl
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
...
} while (err == -EINTR);
...
return err;
}
在这里调用的isMain=true,也就是向mOut例如写入的便是BC_ENTER_LOOPER. 经过talkWithDriver(), 接下来程序往哪进行呢?在文章彻底理解Android Binder通信架构详细讲解了Binder通信过程,那么从binder_thread_write()往下说BC_ENTER_LOOPER的处理过程。
8-1.binder_thread_write
// binder.c
static int binder_thread_write(struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed)
{
uint32_t cmd;
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
void __user *ptr = buffer + *consumed;
void __user *end = buffer + size;
while (ptr < end && thread->return_error == BR_OK) {
//拷贝用户空间的cmd命令,此时为BC_ENTER_LOOPER
if (get_user(cmd, (uint32_t __user *)ptr)) -EFAULT;
ptr += sizeof(uint32_t);
switch (cmd) {
case BC_REGISTER_LOOPER:
if (thread->looper & BINDER_LOOPER_STATE_ENTERED) {
//出错原因:线程调用完BC_ENTER_LOOPER,不能执行该分支
thread->looper |= BINDER_LOOPER_STATE_INVALID;
} else if (proc->requested_threads == 0) {
//出错原因:没有请求就创建线程
thread->looper |= BINDER_LOOPER_STATE_INVALID;
} else {
proc->requested_threads--;
proc->requested_threads_started++;
}
thread->looper |= BINDER_LOOPER_STATE_REGISTERED;
break;
case BC_ENTER_LOOPER:
if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {
//出错原因:线程调用完BC_REGISTER_LOOPER,不能立刻执行该分支
thread->looper |= BINDER_LOOPER_STATE_INVALID;
}
//创建Binder主线程
thread->looper |= BINDER_LOOPER_STATE_ENTERED;
break;
case BC_EXIT_LOOPER:
thread->looper |= BINDER_LOOPER_STATE_EXITED;
break;
}
...
}
*consumed = ptr - buffer;
}
return 0;
}
处理完BC_ENTER_LOOPER命令后,一般情况下成功设置thread->looper |= BINDER_LOOPER_STATE_ENTERED。那么binder线程的创建是在什么时候呢? 那就当该线程有事务需要处理的时候,进入binder_thread_read()过程。
8-2.binder_thread_read
binder_thread_read(){
...
retry:
//当前线程todo队列为空且transaction栈为空,则代表该线程是空闲的
wait_for_proc_work = thread->transaction_stack == NULL &&
list_empty(&thread->todo);
if (thread->return_error != BR_OK && ptr < end) {
...
put_user(thread->return_error, (uint32_t __user *)ptr);
ptr += sizeof(uint32_t);
goto done; //发生error,则直接进入done
}
thread->looper |= BINDER_LOOPER_STATE_WAITING;
if (wait_for_proc_work)
proc->ready_threads++; //可用线程个数+1
binder_unlock(__func__);
if (wait_for_proc_work) {
if (non_block) {
...
} else
//当进程todo队列没有数据,则进入休眠等待状态
ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
} else {
if (non_block) {
...
} else
//当线程todo队列没有数据,则进入休眠等待状态
ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread));
}
binder_lock(__func__);
if (wait_for_proc_work)
proc->ready_threads--; //可用线程个数-1
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
if (ret)
return ret; //对于非阻塞的调用,直接返回
while (1) {
uint32_t cmd;
struct binder_transaction_data tr;
struct binder_work *w;
struct binder_transaction *t = NULL;
//先考虑从线程todo队列获取事务数据
if (!list_empty(&thread->todo)) {
w = list_first_entry(&thread->todo, struct binder_work, entry);
//线程todo队列没有数据, 则从进程todo对获取事务数据
} else if (!list_empty(&proc->todo) && wait_for_proc_work) {
w = list_first_entry(&proc->todo, struct binder_work, entry);
} else {
... //没有数据,则返回retry
}
switch (w->type) {
case BINDER_WORK_TRANSACTION: ... break;
case BINDER_WORK_TRANSACTION_COMPLETE:... break;
case BINDER_WORK_NODE: ... break;
case BINDER_WORK_DEAD_BINDER:
case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
case BINDER_WORK_CLEAR_DEATH_NOTIFICATION:
struct binder_ref_death *death;
uint32_t cmd;
death = container_of(w, struct binder_ref_death, work);
if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;
else
cmd = BR_DEAD_BINDER;
put_user(cmd, (uint32_t __user *)ptr;
ptr += sizeof(uint32_t);
put_user(death->cookie, (void * __user *)ptr);
ptr += sizeof(void *);
...
if (cmd == BR_DEAD_BINDER)
goto done; //Binder驱动向client端发送死亡通知,则进入done
break;
}
if (!t)
continue; //只有BINDER_WORK_TRANSACTION命令才能继续往下执行
...
break;
}
done:
*consumed = ptr - buffer;
//创建线程的条件
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))) {
proc->requested_threads++;
// 生成BR_SPAWN_LOOPER命令,用于创建新的线程
put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer);
}
return 0;
}
当发生以下3种情况之一,便会进入done:
- 当前线程的return_error发生error的情况;
- 当Binder驱动向client端发送死亡通知的情况;
- 当类型为BINDER_WORK_TRANSACTION(即收到命令是BC_TRANSACTION或BC_REPLY)的情况;
任何一个Binder线程当同时满足以下条件,则会生成用于创建新线程的BR_SPAWN_LOOPER命令:
- 当前进程中没有请求创建binder线程,即requested_threads = 0;
- 当前进程没有空闲可用的binder线程,即ready_threads = 0;(线程进入休眠状态的个数就是空闲线程数)
- 当前进程已启动线程个数小于最大上限(默认15);
- 当前线程已接收到BC_ENTER_LOOPER或者BC_REGISTER_LOOPER命令,即当前处于BINDER_LOOPER_STATE_REGISTERED或者BINDER_LOOPER_STATE_ENTERED状态。前面已设置状态为BINDER_LOOPER_STATE_ENTERED,显然这条件是满足的。
从system_server的binder线程一直的执行流: IPC.joinThreadPool –> IPC.getAndExecuteCommand() -> IPC.talkWithDriver() ,但talkWithDriver收到事务之后, 便进入IPC.executeCommand(), 接下来,从executeCommand说起.
** 9.IPC.executeCommand**
status_t IPCThreadState::executeCommand(int32_t cmd)
{
status_t result = NO_ERROR;
switch ((uint32_t)cmd) {
...
case BR_SPAWN_LOOPER:
//创建新的binder线程
mProcess->spawnPooledThread(false);
break;
...
}
return result;
}
Binder主线程的创建是在其所在进程创建的过程一起创建的,后面再创建的普通binder线程是由spawnPooledThread(false)方法所创建的。
9. 总结
Binder系统中可分为3类binder线程:
- Binder主线程:进程创建过程会调用startThreadPool()过程中再进入spawnPooledThread(true),来创建Binder主线程。编号从1开始,也就是意味着binder主线程名为binder_1,并且主线程是不会退出的。
- Binder普通线程:是由Binder Driver来根据是否有空闲的binder线程来决定是否创建binder线程,回调spawnPooledThread(false) ,isMain=false,该线程名格式为binder_x。
- Binder其他线程:其他线程是指并没有调用spawnPooledThread方法,而是直接调用IPC.joinThreadPool(),将当前线程直接加入binder线程队列。例如: mediaserver和servicemanager的主线程都是binder线程,但system_server的主线程并非binder线程。
Binder的transaction有3种类型:
- call: 发起进程的线程不一定是在Binder线程, 大多數情況下,接收者只指向进程,并不确定会有哪个线程来处理,所以不指定线程;
- reply: 发起者一定是binder线程,并且接收者线程便是上次call时的发起线程(该线程不一定是binder线程,可以是任意线程)。
- async: 与call类型差不多,唯一不同的是async是oneway方式不需要回复,发起进程的线程不一定是在Binder线程, 接收者只指向进程,并不确定会有哪个线程来处理,所以不指定线程。
文末
因为内容较多,更多的面试题详解可以扫描下方二维码免费领取!