[TOC]

###为何选择Binder


Linux已经拥有管道,system V IPC,socket等IPC手段,却还要依赖Binder来实现进程间通信,说明Binder具有无可比拟的优势。

####传输性能好
Binder的优点之一就是,复杂数据类型传递可以复用内存。

  • socket:是一个通用接口,导致其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。
  • 管道和消息队列:因为采用存储转发方式,所以至少需要拷贝2次数据,效率低。
  • 共享内存:虽然在传输时没有拷贝数据,但其控制机制复杂。
IPC 数据拷贝次数
共享内存 0
Binder 1
Socket/管道/消息队列 2

####安全性高
传统IPC没有任何安全措施,完全以来上层协议来确保。首先传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。

Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。可靠的身份标识只有IPC机制本身在内核中添加。

传统IPC访问接入点是开放的,无法建立私有通道,Binder可以使用匿名Binder建立私密通道,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。

易用性


Binder使用的是C/S通信方式,一个进程可以开启服务专门负责处理某个模块的业务,多个进程可以作为Client同时向Server发起请求。

使用了面向对象的设计,发起一次binder call 就像在调用本地方法一样简单。

一次拷贝?

当Client向Server发送数据时,Client会先从自己的进程空间把通信数据拷贝到内核空间,因为Server和内核共享数据,所以不在需要重新拷贝数据,而是直接通过内存地址的偏移量直接获取到数据地址。总体来说拷贝了一次。

Server和内核空间之所以能够共享一块空间数据主要是通过binder_mmap来实现的。它的主要功能是在内核的虚拟地址空间申请一块和用户虚拟内存相同大小的内存,然后在申请一个page大小的内存,将它映射到内核虚拟地址空间和用户虚拟内存空间,从而实现了用户空间缓存和内核空间缓冲同步的功能。

Binder总体架构


在Android系统中,运行在内核空间的,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动,Binder驱动虽然默默无闻,确是通信的核心,尽管叫“驱动”,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的。

面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在与Binder对象是一个可以跨进程引用的对象,他的实体位于一个进程中,而他的引用确遍布系统的各个进程中。最诱人的是这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且从一个进程传给其他进程,让大家都能访问同一个server,就像讲一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程。整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘连各个应用程序的胶水。

首先Binder分为Binder对象和Binder驱动,即Binder驱动就是主要的内核模块,而这个Binder对象是通讯的载体,可以自由的通过Bidner驱动自由穿梭任意进程。所以客户端或者服务器就可以把数据放入Binder对象里,然后进行调用和通讯。类似胞吞胞吐。

Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。

Server进程里的Binder对象指的是Binder本地对象,Client里面的对象指的是Binder代理对象;在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换;因此Binder驱动必然保存每一个跨进程的Binder对象的相关信息;在驱动中,Binder本地对象的代表是一个是一个叫做binder_node的数据结构,Binder代理对象 是用binder_ref代表的;有得地方把Binder本地对象直接称作Binder实体,把Binder代理对象直接称作 Binder引用(句柄)。

ServiceManager与实名Binder


ServiceManager是一个进程,Server是另一个进程,Server向SM中注册Binder必然会涉及到进程间通信。当前实现的是进程间通信却又要用到进程间通信,就好像蛋孵出鸡的前提却是鸡孵蛋。Binder的实现比较巧妙,预先创造一只鸡来孵蛋:SM和其他进程间同样采用Binder通信,SM是Server端,有自己的Binder对象(实体),其他进程都是Client,需要通过这个Binder的引用来实现Binder的注册,查询和获取。SM提供的Binder比较特殊,他没有名字也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册在SM时Binder驱动会自动为他创建Binder实体(这就是预先造好的鸡)。其次这个Binder的引用在所有Client中都固定为0而无须通过其他手段获得。也就是说,一个Server若要向SM注册自己Binder就必需通过0这个引用号和SM的Binder通信。类比网络通信,0号引用就好比域名服务器的地址,你必须预先手工或动态配置好。

  • 首先,Server在自己的进程中向Binder驱动申请创建一个Server的Binder实体。
  • Binder驱动为这个Server创建位于内核中的Binder实体节点以及Binder的引用。(在Binder驱动中创建一块内存)
  • 然后Server通过0这个引用号和SM的的Binder通信 将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个名叫XXX的Server。
  • SM收到数据包后,从中取出Server名字和引用,填入一张查找表中。

Server初始化的时候,SM做了以下操作:

  1. 为binder分配128k的内存
  2. 通知binder驱动,使自身成为binder驱动的“DNS”
  3. 维护一个监听Server的死循环,并且维护持有所有Server句柄的svclist
  4. 添加Server的时候,进行权限,内存(充足)进行判断,如果没有添加过则将Server添加至svclist。

Client获得实名Binder的引用


Server向SM注册了Binder引用及其名字后,Client就可以通过名字获取该Binder的引用了。Client也利用保留的0号引用向SM请求访问某个Binder:我申请获得名字叫张三的Binder的引用。SM收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。从面向对象的角度,这个Binder对象 现在有两个引用:一个位于SM中,一个位于发送请求的Client中。如果接下来有更多的Client请求该binder,系统中就会有更多的引用指向该Binder,就像Java里一个对象存在多个引用一样。而且类似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。

Client与Server通讯


client向SM发送申请服务Server的请求,那么SM就可以在查找表中找到该Service的Binder引用。并把Binder引用(BpBinder)返回给Client,此时Client便可以通过这个引用向Server(间接)发起调用,Binder引用将参数包装然后交给驱动并获取Server的调用结果。

Binder的线程管理


每个binder的Server进程会创建很多线程来处理Binder请求,可以简单的理解为创建了一个Binder的线程池(虽然实际上并不完全是这样简单的线程管理方式),而真正管理这些线程并不是由这个Server端来管理的,而是由Binder驱动进行管理的。

一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。你做进程间通信时处理并发问题就会有一个底,比如使用ContentProvider时,你就很清楚他的CRUD方法只能同时有16个线程在跑。(应用与ContentProvider为不同进程时)。

四大组件中常见的2个binder服务是?

一个是实现了IActivityManager接口的ActivityManagerNative,ActivityManagerService是他的子类,提供了AMS中的所有服务,从APP调用binder call到AMS都是同步的,APP需要阻塞等待AMS执行完毕。

一个是实现了IApplicationThread接口的ApplicationThreadNative,ActivityThread中的内部类ApplicationThread是他的子类,AMS可以发起异步请求到APP,不需要等待APP执行完成。

  • BpBinder:Binder的代理对象,内部有一个成员变量mHandle,记录着远侧滑盖服务对象的handle
  • BBinder:Binder本地服务对象
binder_ref与binder_node有什么区别
  • binder_ref与binder_node都存在于内核空间
  • binder_node是实体对象、binder_ref是引用对象
  • binder_node被binder_ref引用,binder_ref被BpBinder引用
应用如何获取和添加Binder服务

获取与添加Binder服务的操作是交给大管家ServiceManager实现的;ServiceManager进程启动后会通过binder_loop睡眠等待客户端的请求,如果进程被客户端唤醒就会调用svcmgr_handler来处理获取或者添加服务的请求

ServiceManager也是一种Binder服务,当应用获取ServiceManager服务的代理时,它的handle句柄固定为0,所以才不需要去查找,

1
2
3
4
5
6
7
8
9
10
11
12
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
return getStrongProxyForHandle(0);
}
Binder协议中BC_与BR_开头的协议都有什么区别?
  • BC_:全称Binder Command,进程发送给Binder驱动数据时携带的协议
  • BR_:全程Binder Return,Binder驱动发送给进程数据时携带的协议
Binder服务在调用期间抛出了RuntimeException异常,服务端会Crash吗

服务端不会Crash,RuntimeException被Binder服务端线程捕捉,随后将异常信息写入到reply中,发回Binder客户端进程,最后客户端binder线程会抛出这个异常,如果没有捕捉到这个RuntimeException,那么Binder客户端进程会Crash。

客户端调用Binder接口后抛出的DeadObjectException是什么意思?

之所以抛出DeadObjectException最常见的原因是Binder服务端进程已经死亡。客户端进行binder调用时,Binder驱动发现服务端进程不能回应请求,那么就会抛出异常给客户端。

还有一些比较生僻的原因,比如服务端此时的缓存内存空间(1016k)已经被占满了,Binder驱动就认为服务端此时并不能处理这个调用,那么就会在C++层抛出DeadObjectException到Java层。

Binder驱动加载过程中有哪些重要的步骤?
  • binder_init:初始化Binder驱动环境,内核工作队列、文件系统节点、misc设备等
  • binder_open:打开Binder设备,获取Binder驱动的文件描述符(fd)
  • binder_mmap:将用户进程地址空间映射到Binder驱动设备内存。这也是Binder能够实现一次拷贝以来的根本。
  • binder_ioctl:Binder的驱动的核心功能,用来进行数据读写操作
Binder的死亡通知机制的作用是什么,如何实现?

Binder服务端进程死亡后,依赖着Binder实体对象的客户端代理对象也会失效。当Binder服务无效时,驱动程序会发送死亡通知给各个已注册服务的客户端进程,以方便客户端做些销毁之类的操作。

思维通知机制最常用在APP和AMS服务,是典型的C/S架构,当APP端的进程死亡后,其ApplicationThreadNative会被销毁,随后Binder驱动会发出死亡通知给AMS,方便清理已经失效的四大组件及应用进程信息。

binder驱动的加载阶段会打开/dev/driver文件,当服务端的进程死亡后,系统会进入清理阶段,关闭所有与进程相关联的资源,这其中就包含了关闭/dev/driver文件描述符的操作,随后就会调用到Binder驱动的binder_release方法,结果层层调用最后会调用到binderDied这个回调方法。如果APP进程之前注册并实现过DeathRecipient这个接口,此时APP就能对Server进程的死亡做出处理。

bindService所绑定的“服务概念和Binder中的服务Server有什么区别”

bindService绑定的服务所指的是四大组件中的Service;调用context.bindService方法可以让Activity与Service形成一种“绑定”的概念。这个概念是在Android Framework定义,形成“绑定”的关系之后,Activity除了能够和Service进行通信之外,他们所在的进程存活状态也会相关联。注意,这个Service不确定是运行在本地还是远端,大部分情况下我们调用startService只是用来启动一个在本地定义的Service组件。当使用bindService时,一般是需要调用Service通过onBind返回的Binder服务接口,以此实现Activity与Service之间的通信。

Binder服务则是一个真正的C/S架构中的服务端角色。这个服务需要继承Binder类并实现一套服务接口才能生效。我们常见的ActivityManagerService正是一个继承了Binder类并运行在system_server的服务,用来提供四大组件以及进程方面的服务。

注意,调用bindService后,Service.onBind方法会返回Binder服务Stub对象,然后APP会在AMS进行登记,然后经过层层调用才会在Binder客户端调用onConnected方法,如果Binder客户端和其服务端在同一个进程,客户端拿到的应该还是Stub对象,如果不在同一进程中,客户端拿到的应该是Binder服务的Proxy对象。

本质上,bindService锁绑定的服务和Binder的服务属于同一类,他们实现进程间通信的原理都是借助了Binder这一套机制。

writeStrongBinder与readStrongBinder的作用和原理

主要作用是实现两个进程之间的双工通信。还是以APP客户端的ApplicationThread和system_server服务端的ActivityManagerService这两个Binder服务来举例子,这两个服务的接口的大致模式都相同。

每个进程最多存在多少个Binder线程,这些线程都被占满后会导致什么问题?
1
2
3
4
5
6
7
8
9
10
11
12
// --frameworks/nativebs/binder/ProcessState.cpp
#define DEFAULT_MAX_BINDER_THREADS 15
static int open_driver()
{
int fd = open("/dev/binder", O_RDWR);
if (fd >= 0) {
...
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
}
return fd;
}

Binder线程池中的线程数量是在Binder驱动初始化时被定义的,进程池中的线程个数上限为15个,加上主Binder线程,一共最大能存在16个binder线程;

当binder线程都在执行工作时,也就是当出现线程饥饿的时候,从别的进程调用的binder请求如果是同步的话,会在todo队列中阻塞等待,直到线程池中有空闲的binder进程来处理请求。

binder传输数据的最大限制是多少,占满后会导致什么问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// --frameworks/nativebs/binder/ProcessState.cpp
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
ProcessState::ProcessState()
: mDriverFD(open_driver())
, mVMStart(MAP_FAILED)
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
{
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
}

在调用mmap时会指定Binder内存缓冲区的大小为1016k;当服务端的内存缓冲区被Binder进程占用满后,Binder驱动不会在处理binder调用并在c++层抛出DeadObjectException到binder客户端。
同步空间是1016k,异步空间只有他的一半,也就是508k

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// --kernel/msm-3.18/drivers/staging/android/binder_alloc.c
struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
size_t data_size,
size_t offsets_size,
size_t extra_buffers_size,
int is_async)
{
...
if (is_async &&
// 当Binder缓存空间不足时将会出现异常,Binder驱动不再会派发这个binder请求
alloc->free_async_space < size + sizeof(struct binder_buffer)) {
binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC,
"%d: binder_alloc_buf size %zd failed, no async space left\n",
alloc->pid, size);
eret = ERR_PTR(-ENOSPC);
goto error_unlock;
}
error_unlock:
mutex_unlock(&alloc->mutex);
return eret;
}
Binder驱动什么时候释放缓冲区的内存

是在binder call之后,调用Parcel.recycle来完成释放内存的。

1
2
3
4
5
6
7
8
9
10
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.recycle();
data.recycle();
return result;
}
为什么使用广播传输2MB的Bitmap会跑异常,而使用AIDL生成的Binder接口传输Bitmap就不会抛异常呢?

通过Binder直接传输Bitmap,如果bitmap的大小大于128k,那么传输Bitmap内容的方式就会使用ashmem,Binder只需要负责传输ashmem的fd到服务端即可。这种Binder+ashmem的方式在Android中很常见,比如四大组件的ContentProvider.query方法,从Provider中查找的数据通常会超过1016k这个限制,使用ashmem不仅能突破这个限制,还有提高大量数据传输的效率

使用广播来传输跨进程传输数据的话则不一样,bitmap是被填入到Bundle中,随后以Parcelable的序列化方式传输到AMS的,如果Bundle数据量大于800k,就会抛出TrasnactionTooLargeException的异常

依据Bitmap是否过大来使用ashmem还是Binder的方式传输内容的逻辑在native层的Bitmap_createFromParcel。

应用程序为什么支持Binder通信,直接可以使用四大组件呢?

所有应用的进程都是通过调用AMS.startProcessLocked方法来fork Zygote进程创建的;Zygote在启动时会在preloadClasses中预先加载上千个类,而在fork子进程时,这些操作就不需要在做了,大大节约了子进程的启动时间。

应用进程的Binder驱动初始化的操作正是在zygote fork自身之后做的。system_server与zygote的通信使用socket,之所以不使用binder的原因很简单,socket的使用对于写这个功能的工程师来说更简单。

在zygote进程的初始化操作完成后,zygote会通过socket返回给system_server pid,随后AMS会将pid和应用的Application进行绑定。也就是说在应用调用Application.onCreate之前,binder驱动的初始化就已经完成了,所以直接就可以使用Binder来通信。