请支持作者原创:
https://mr-cao.gitbooks.io/android/content/android-binder.html
Android Binder详解
Table of Contents
本文将对Android的binder机制做一个较为深入的分析,除了讲解binder实现的细节,还会讲解binder通信中的基础原理,以及创建binder service的注意事项。本文的代码分析基于Android4.2.2。
1. binder简介
在我刚刚学习binder的时候,对于binder非常的困惑,现在想起来困惑的原因还是因为对于IPC的不了解。在学习binder之前,最好是对IPC有个基本的了解。IPC是Inter-process communication的缩写,即进程间通信。IPC是一种允许进程间互相通信交换数据的机制。在Linux平台上,进程之间是隔离的,各个进程运行在自己的虚拟地址空间中,如果不采取IPC手段,进程之间是不能互相交换数据的。为了实现进程之间的数据交换,Linux提供了多种IPC机制:
- 信号
- 管道
- Socket
- 消息队列
- 信号量
- 共享内存
Android是基于Linux系统开发,除了上面的IPC机制以外,Android又提供了一种新的选择:binder。本文不打算探究这几种机制之间的差异以及优劣,我的主要关注点在binder的实现上。binder的实现采取了面向对象的编程思想,Android提供了大量的帮助类,通过使用这些帮助类,binder程序开发人员基本不用关心数据是如何在进程之间传递的,而是集中精力设计好顶层服务接口,按照规范实现好proxy和service类就可以很方便的扩展一个本地服务。站在binder开发人员的角度来讲,一个binder的实现包括以下三个方面:
- 顶层服务接口类的定义,此类中声明了一系列的纯虚函数作为公共的服务接口。类的头文件名一般为IXXXService.h,服务接口类命令为IXXXService,XXX为服务模块名。比如Android系统提供的多媒体服务的接口类为:IMediaPlayerService,其头文件名为IMediaPlayerService.h。
- proxy端的实现。
- service端的实现。proxy是相对于service而言,proxy和service都间接继承于IXXXService顶层服务接口类,他们都实现了IXXXService中声明的虚函数接口,所以从外观上看,是没什么区别的。对于用户来说,只需要持有一个IXXXService的指针,就可以调用其服务函数,不用关心这个指针究竟是指向哪个子类的具体实现。proxy和service内部对于同一个函数的实现是有差异的,在proxy端的实现中,是将函数的参数打包进容器,然后透过Android提供的binder通信机制传递给service端,service端的实现是从这个容器中读取出对应的参数,然后调用相应的实现函数。从这个角度来说,proxy只是一个空的壳子,它并没有做实际的工作,而是把做实际工作需要的条件打包好,传递给service,由service来完成具体工作。
下面的图简单的描述了顶层服务接口和proxy代理类,与service服务类之间的关系。IXXXService是一个顶层服务接口类,它声明了一个doAction的方法,其子类proxy和service分别实现了这个方法。但是proxy是将doAction方法参数打包,发送给service,由service负责执行。
binder驱动是通信的媒介,为通信的进程在驱动层分配buffer,将用户层的参数buffer复制到驱动层buffer,完成数据的交换。下图描述了这个过程:
站在系统角度来说,binder的实现包括:
- 一个client进程
- 一个service进程
- binder驱动
其中,提供服务接口的进程为service进程,使用服务的为client进程。binder在这两者之间充当通信的媒介,所有的通信数据都是经过bidner传递到对方的用户空间。下面是一个简单的图例,描述了一次同步binder调用的过程:
proxy调用service的服务,称之为一次binder调用。binder调用有两种形式:
- 同步调用
- 异步调用
所谓同步调用就是proxy发送完数据给媒介binder driver之后,开始等待的状态。直到service端处理完本次调用,通过binder driver返回了处理结果。异步调用就简单了,proxy直接发送完数据给媒介binder driver之后就返回了,不用等到service的处理结果。
综上,binder是Android提供的一种IPC通信机制,方便进程之间交换数据。binder的实现包括一个公共的顶层服务接口,同时实现了这个公共顶层接口的proxy代理端和service端。binder driver充当通信媒介。
2. binder的实现
上一个小节中,提纲挈领的介绍了bidner的基础信息,本章主要从代码的角度来分析binder的实现。Android给用户提供了一个共享库:libbinder.so,这个库提供了一系列的接口类和帮助函数,借助这些工具类,我们可以很方便的实现自己的binder service。代码的路径:
frameworks/native/libs/binder
首先我们来看一张”全家福“:
上面这张图中比较清晰的描述了binder库中提供的类之间的关系。在面向对象编程中,一个对象的行为在它的基类中就定义好了,所以我们首先对上图中两个顶层基类IBinder和IInterface做个简单的介绍,以达到纲举目张的目的。
2.1. IBinder类简介
IBinder是负责binder通信机制的基类,它有两个子类——BpBinder和BBinder。BpBinder代表着proxy,而BBinder代表着service。IBinder中有一个函数:
virtual status_t transact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);
这是个纯虚函数,它肩负着binder数据传输的重任,从函数的名字就可以看出其重要性,子类Bpbinder和BBinder负责它的具体实现。BpBinder的此函数负责传输数据,而BBinder的此函数是负责接收数据。关于此函数的参数的解释如下:
- code:函数码值,每一个服务接口类中声明的虚函数都对应一个码值,proxy端通过将码值传递给service端,从而告知service端请求执行的函数。
- data:Parcel是Android中的容器类,用于装载数据。每个Parcel都对应一块内存buffer,用于存放数据。data中保存的是执行函数所需要的参数,proxy端把数据打包传递到service端,service端按照顺序读取参数,然后传递给对应的函数去执行。
- reply:指向Parcel对象的指针,和data不同的是,它是用来装载service执行函数后返回的结果。 proxy可以从此Parcel中读取service的返回值,比如service的函数执行完毕之后返回一个int值,那么就可以调用
reply->readInt32()
获得这个int值。 - flags:表示函数是同步调用还是异步调用。默认是同步调用,如果需要异步调用,flags会被赋值为IBinder::FLAG_ONEWAY。同步调用是阻塞的,必须等待service执行完毕返回执行结果之后proxy的执行流才得以继续,否则执行函数调用的线程就一直处于wait状态。
IBinder中有这么两个虚函数:
virtual BBinder* localBinder();
virtual BpBinder* remoteBinder();
默认的实现都是返回NULL。在BBinder中实现了localBinder:
BBinder* BBinder::localBinder()
{
return this;
}
在BpBinder中实现了remoteBinder:
BpBinder* BpBinder::remoteBinder()
{
return this;
}
所以,如果要区分一个IBinder对象是local binder还是remote binder,那么调用IBinder对象的上述两个函数,对结果进行check就可以知道了。如果localBinder返回非空,那么就是一个local binder,如果remoteBinder返回非空,那么就是一个remote binder。在binder通信中,究竟什么是local binder,什么是remote binder呢?首先,继承自IBinder类的对象,都是binder对象。BBinder因为生存在服务进程中,所以称之为local
binder,而BpBinder所对应的实体在另外一个进程中,所以称之为remote binder。BpBinder和BBinder对应关系可以参见下图:
进程Process_1中有一个BpBinder A,它对应进程Process_2中的BBinder A。相对于Process_1来说,BpBinder A就是remote binder;相对于Process_2来说,BBinder A就是local binder。
同时,进程Process_2中有一个BpBinder B,它对应进程Process_1中的BBinder B。相对于Process_2来说,BpBinder B就是个remote binder,相对于Process_1来说,BBinder B是一个local binder。
从上图我们还可以得知,Process_1调用BpBinder将打包好的函数参数发送到binder driver中Process_2对应的buffer,BBinder A从这块buffer中读取数据,然后进行处理;同理,Process_2调用BpBinder B将打包好的函数参数发送到binder driver中Process_1对应的buffer,BBinder B从这块buffer中读取数据,然后进行处理。一次binder调用从用户空间到kernel空间,数据copy的次数为1。
IBinder类另一个重要的函数:
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient, void* cookie = NULL, uint32_t flags = 0) = 0;
这个函数是用来为BBinder注册死亡通知的。在客户端进程中,持有一个BpBinder,它对应着服务进程中的某个BBinder。如果服务进程crash了,那么BBinder也就不存在了,BpBinder就无法再和BBinder通信了。因为BBinder的死亡客户端是没法主动知道的,所以需要注册个死亡通知:当BBinder不存在了,死亡通知就会被派发,以便客户端进程能做一些善后的工作。这个函数只在BpBinder中实现了——很显然,BBinder不需要为自己注册死亡通知。 DeathRecipient是IBinder的一个内部类,它有一个纯虚函数的方法,需要用户自己去实现:
class DeathRecipient : public virtual RefBase {
public:
virtual void binderDied(const wp<IBinder>& who) = 0;
};
2.2. IInterface类简介
从抽象的角度来将,基类IBinder实现的是通信数据的传输。这些通信数据来自于顶层服务接口类,所以还需要为服务接口类IXXXService定义一个基类——IInterface。每一个服务接口类IXXXService都需要继承IInterface,IInterface.h中定义了一些和服务相关的变量和函数。 首先看看IInterface的定义:
class IInterface : public virtual RefBase{
public:
IInterface();
sp<IBinder> asBinder();
sp<const IBinder> asBinder() const;
protected:
virtual ~IInterface();
virtual IBinder* onAsBinder() = 0;
};
IInterface类相对于IBinder而言,简单了许多。值得关注的是函数asBinder,它返回了一个IBinder的指针。asBinder函数内部实际上是调用的虚函数virtual IBinder* onAsBinder() = 0,由之前的“全家福”可知,这个纯虚函数是由子类BpInterface和BnInterface实现。由此可知,可以从一个IInterfcae获得一个IBinder对象。下面看看两个模板类的不同实现:
template<typename INTERFACE>
IBinder* BnInterface<INTERFACE>::onAsBinder()
{
return this;
}
template<typename INTERFACE>
inline IBinder* BpInterface<INTERFACE>::onAsBinder()
{
return remote();
}
BnInterfce的onAsBinder函数是直接返回的this指针,因为BnInterfce是由IBinder继承而来;BpInterface的onAsBinder函数调用基类BpRefBase的remote函数,返回BpRefBase内部的mRemote指针,这个指针指向的是IBinder对象,后面我们会将到这个对象实际上是一个BpBinder对象。
有没有想过,为什么需要从一个IInterfce类中获得一个IBinder对象?答案很简单,只有IBinder对象是可以跨进程传递的。如果我们要把一个IBinder对象从一个进程传递到另外一个进程,那么就首先要获得它的IBinder指针。binder驱动不仅仅支持常规的数据类型传递,具有特色的是还支持binder对象和文件句柄的传递。传输一个IBinder对象,也分两种情况,一种是传输的是BBinder,另一种传输的是BpBinder。这两种情况还是有差异的,后面我们也会讲到。
IInterface.h文件中还定义了两个非常重要的宏:
#define DECLARE_META_INTERFACE(INTERFACE) static const android::String16 descriptor; static android::sp<I##INTERFACE> asInterface( const android::sp<android::IBinder>& obj); virtual const android::String16& getInterfaceDescriptor() const; I##INTERFACE(); virtual ~I##INTERFACE();
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) const android::String16 I##INTERFACE::descriptor(NAME); const android::String16& I##INTERFACE::getInterfaceDescriptor() const { return I##INTERFACE::descriptor; } android::sp<I##INTERFACE> I##INTERFACE::asInterface( const android::sp<android::IBinder>& obj) { android::sp<I##INTERFACE> intr; if (obj != NULL) { intr = static_cast<I##INTERFACE*>( obj->queryLocalInterface( I##INTERFACE::descriptor).get()); if (intr == NULL) { intr = new Bp##INTERFACE(obj); } } return intr; } I##INTERFACE::I##INTERFACE() { } I##INTERFACE::~I##INTERFACE() { } \
当我第一次阅读binder代码,看到上面这两个宏的时候,确实是“懵”了。其实上面代码的逻辑还是很简单的,只是因为使用了宏定义所以显的比较复杂而已。DECLARE_META_INTERFACE就干了四件事情:
- 为IInterface的子类IXXXXService声明了一个描述符,这个描述符用来唯一描述IXXXService。同时这个描述符在binder通信过程中,还充当了”校验码“码的功能,当service端收到数据后,首先就要从数据中读取一个描述符,验证描述符是否匹配自身的描述符。如果是,证明数据有效,否则就是个非法的数据包,不予处理。
- 声明了函数asInterface,从一个IBinder对象获得一个IXXXService对象。
- 声明函数getInterfaceDesCriptor用来获取声明的字符串描述符。
- 声明IXXXService的构造函数和析构函数。
IMPLEMENT_META_INTERFACE宏展开之后,就是对DECLARE_META_INTERFACE宏声明的变量的定义和函数的实现了。我们只关注asInterface这个函数的实现,这里我们以Android多媒体服务的接口类IMediaPlayerService为例替换INTERFACE为MediaPlayerService,来看看这个函数的实现:
android::sp<IMediaPlayerService> IMediaPlayerService::asInterface(const android::sp<android::IBinder>& obj)
{
android::sp<IMediaPlayerService> intr;
if (obj != NULL) {
intr = static_cast<IMediaPlayerService>(
obj->queryLocalInterface(IMediaPlayerService::descriptor).get());
if (intr == NULL) {
intr = new BpMediaPlayerService(obj);
}
}
return intr;
}
既然提到了MediaPlayerService,那就上一张IMediaPlayerService的类图,这样也方便接下来的代码分析:
IMediaPlayerService::asInterface这个函数首先会调用IBinder的queryLocalInterface函数检查IBinder的子类是否是一个本地service接口。在IBinder中提供了默认的实现:
sp<IInterface> IBinder::queryLocalInterface(const String16& descriptor)
{
return NULL;
}
IBinder有两个子类:BpBinder和BBinder,BpBinder并没有对这个函数进行了重写,沿用了基类的默认实现方式。在BBinder的子类BnInterface中对这个函数进行了重写,在函数实现体中,如果检查传递的描述符字符串和自身的描述符相同,就返回this指针。
template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
const String16& _descriptor)
{
if (_descriptor == INTERFACE::descriptor) return this;
return NULL;
}
所以对于IMediaPlayerService::asInterface(const android::sp<android::IBinder>& obj)函数,如果形参IBinder引用obj指向的是一个BpBinder对象,那么obj→queryLocalInterface函数就返回NULL,需要以obj为参数构造一个BpMediaPlayerService对象(继承自IMediaPlayerService);如果形参IBinder引用obj指向的是一个BBinder对象,返回的就是this指针。因为BnInterface<IMediaPlayerService>作为BBinder的子类的同时也是IMediaPlayerService的子类。
DECLARE_META_INTERFACE费劲心思声明的asInterface函数的作用是非常重要的,它可以直接从IBinder构造一个IMediaPlayerService对象,从而屏蔽了这个对象究竟是本地service对象,还是一个remote代理对象。用户直接使用IMediaPlayerService接口的指针就可以调用具体的函数实现,丝毫不用关心底层的实现细节。
DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE宏要结合起来使用,DECLARE_META_INTERFACE在IXXXService.h的类定义体中使用;而IMPLEMENT_META_INTERFACE宏在IXXXService.cpp中使用。
IInterface.h中还定义了一个帮助函数,封装了上边讲到的asInterface。
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
比如调用interface_cast<IMediaPlayerService>(obj)就可以获得一个IMediaPlayerService指针。这里留一个悬念:interface_cast形参obj从哪里获得的呢?之后的章节会讲到。
2.3. BpBinder和BBinder简介
之前的章节已经简单的介绍了BpBinder和BBinder,我们已经知道BpBinder负责传输数据,BBinder负责接收数据和处理数据。这一小节,会深入的分析BpBinder和BBinder的关系。
在BpBinder类的内部有一个成员变量mHandle,它是一个int型变量。这个变量所代表的含义是什么呢?对于每一个经过binder driver传输的BBinder对象,binder driver都会在驱动层为它构建一个binder_node数据结构;同时为这个binder_node生成一个“引用”:binder_ref,每一个binder_ref都有一个int型的描述符。BpBinder的成员变量mHandle的值就是bidner_ref中的int型描述符,这样就建立起了用户层的Bpbinder和一个驱动空间的binder_ref数据结构的对应关系。通过
“BpBinder handle→binder_ref→binder_node→BBinder”这样的匹配关系,就可以建立一个Bpbinder对应一个BBinder的关系。 下面这张图描述了Handle和binder_ref以及binder_node的关系:
一个binder_node可能有很多个binder_ref引用它,但是一个客户进程内,对于同一个binder_node的引用只会存在一份——也就是说。也就是说,如果进程A中有一个BBinder service,进程B持有多个BpXXXService的代理类,但是这些代理类都对应同一个BpBinder对象。这点其实在我们的“全家福”里面已经体现的很清楚了。BpRefBase和BpBinder的关系不是继承关系,而是一个聚合关系。这点其实很好理解,对同一个BBinder维持多个BpBinder是一件很浪费空间的事情。另外,BpBinder的数目也影响着BBinder的生命周期,同一个BBinder使用同一个BpBinder也简化了生命周期的管理。
上面我们讲到了通过binder的传输的BBinder,驱动都会为之建立一个binder_node数据结构。那么一个BBinder是如何由一个进程传递到另一个进程,进而得到一个匹配的BpBinder的呢?这就不得不讲到Parcel了。
2.3.1. binder传输容器Parcel
Parcel是Android提供的一个容器类,通过binder传输的数据都可以写进Parcel中。但是因为Parcel中的数据最终都会copy到驱动空间,而驱动空间为每一个进程分配的内存是有效的,如果Parcel中的数据量太大就会造成数据传输失败。所以,binder通信中不适宜用Parcel传输诸如图片这样的数据,如果有需求传输大容量数据,可以使用共享内存IMemory。这个我们会在本文的最后讲解。
Parcel中不仅可以装载int,float,string这样的基础数据类型,还可以装载IBinder对象,文件句柄等特俗对象。现在我们就来看看Parcel是如何装载IBinder对象。首先看一张图:
对图中几个数据结构做下解释:
- binder_write_read:用户层传递数据给binder驱动使用的是ioctl的方式(ioctl(fd,cmd,arg)),当cmd为BINDER_WRITE_READ的时候,arg对应的就是binder_write_read结构体。除了binder_write_read以外还有几个其他的命令,但是用的最频繁的就是binder_write_read。
- binder_transaction_data:BINDER_WRITE_READ命令的数据也是以:命令+数据的格式存放。当cmd为BC_TRANSACTION的时候,对应的数据结构是binder_transaction_data。
- flat_binder_object:Parcel中存放的binder对象,以此数据结构来表征。
每一个Parcel都对应一块内存buffer,Parcel内部有三个重要的变量:mData,mObjects,mObjectsSize。
- mData:指向数据buffer,调用write接口写入的数据都依次存放在这块buffer中。
- mObjects:指向一个动态分配的一维数组,这个数组的大小可以动态的扩展。里面存放的是mData的下标值。当数据buffer中写入了binder对象,就会在mObjects中存放一条下标记录,表示在数据buffer的某个地方存放的是一个binder对象。
- mObjectsSize:mObjects数组的大小。
上图中的Parcel buffer中写入了一个IXXXService的描述符,接着是正常的参数参数,比如int之类的数据,紧接着写入了三个binder对象。第一个是BBinder,第二个是BpBinder,第三个是一个文件句柄。他们共同以flat_binder_object数据结构来表示,只是数据结构中的值不一样。当我们要传递一个IBinder到另外一个进程的时候,先要调用Parcel的writeStrongBinder函数将此IBinder对象写入到Parcel中:
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
status_t flatten_binder(const sp<ProcessState>& proc,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
ALOGE("null proxy");
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}
return finish_flatten_binder(binder, obj, out);
}
因为IBinder的引用可能是指向BpBinder,也可能指向BBinder,所以在上述的代码中首先是通过IBinder的localBinder试图获取一个本地binder对象,如果不为空,则表示传递的是一个BBinder对象。如果为空则表示传递的是一个BpBinder。如果传递的是一个BBinder,则需要在驱动层生成binder_ref数据结构,同时把BBinder在用户空间所对应的地址保存在驱动层中。
在通过BpXXXService调用函数接口的时候,实际上是把参数写入到Parcel中,然后由Parcel构造一个bidner_transaction_data数据结构。然后将BC_TRANSACTION命令和binder_transaction_data写入Parcel,在由此Parcel构造最终的binder_write_read数据结构,通过ioctl将此数据结构传递给驱动。
2.3.2. binder的传输
从上小节的图中可以得知,IBinder对象是以flat_binder_object结构体在binder驱动中传递。binder驱动通过将用户空间的Parcel所对应的buffer和mObjects所对应的buffer拷贝到目标进程的驱动内存空间,然后通过mObjects数组的偏移量在数据buffer中找到对应的flat_binder_object对象,根据type的不同创建binder_noede或者是binder_ref数据结构予以描述binder对象。
对于BBinder来说,驱动会为之创建一个binder_node数据结构,同时创建的还有binder_ref数据结构。当传递BBinder的时候,flat_binder_object的type初始值是BINDER_TYPD_BINDER,当binder驱动将此数据结构拷贝到目标进程的内存空间的时候,将type替换为binder_type_handle,并且把handle赋值为binder_ref的int描述符。
用户空间通过从Parcel中读取出对应的flat_binder_object数据结构,然后创建一个BpBinder对象。
sp<IBinder> Parcel::readStrongBinder() const{ sp<IBinder> val; unflatten_binder(ProcessState::self(), *this, &val); return val;}
status_t unflatten_binder(const sp<ProcessState>& proc,
const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);
if (flat) {
switch (flat->type) {
case BINDER_TYPE_BINDER:
*out = static_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
AutoMutex _l(mLock);
handle_entry* e = lookupHandleLocked(handle);
if (e != NULL) {
// We need to create a new BpBinder if there isn‘t currently one, OR we
// are unable to acquire a weak reference on this current one. See comment
// in getWeakProxyForHandle() for more info about this.
IBinder* b = e->binder;
if (b == NULL || !e->refs->attemptIncWeak(this)) {
b = new BpBinder(handle);
e->binder = b;
if (b) e->refs = b->getWeakRefs();
result = b;
} else {
// This little bit of nastyness is to allow us to add a primary
// reference to the remote proxy when this team doesn‘t have one
// but another team is sending the handle to us.
result.force_set(b);
e->refs->decWeak(this);
}
}
return result;
}
- 驱动通过将数据buffer 拷贝到目标进程的内存空间,用户空间以此buffer构造一个Parcel对象。所以才可以通过readStrongBinder来获取其他进程写入的binder对象。
- unflatten_binder:当type是BINDER_TYPE_HANDLE的时候,以handle为参数调用ProcessState::getStrongProxyForHandle苟赞一个BpBinder。
- ProcessState::getStrongProxyForHandle函数首先会检查此handle所对应的BpBinder是否已经创建过,如果已经创建,则返回保存的BpBinder指针;如果没有创建就重新构造一份BpBinder,并且保存在Vector中。因为在进程内ProcessState是单例的,所以对于同一个handle,整个系统只会存在一个BpBinder。
由此就完成了BBinder的传输:发送进程的BBinder,到了目标进程之后,将会得到一个BpBinder。同时驱动也会创建对应的数据结构:binder_node和binder_ref。
2.3.3. BBinder生命周期
Android对于c++ 对象的生命周期的管理是通过sp指针来管理的。一般对象的生命周期只受此对象的强引用计数的影响。当对象的强引用计数为0的时候,就会调用对象的析构函数释放对象的内存。
当我们通过要将一个BBinder传递到驱动的时候,首先要把这个BBinder对象写入到Parcel。Parcel提供了两个函数才管理BBinder对象的引用计数:
void acquire_object(const sp<ProcessState>& proc, const flat_binder_object& obj, const void* who);
void release_object(const sp<ProcessState>& proc, const flat_binder_object& obj, const void* who);
当写入BBinder到一个Parcel的时候,会调用acquire_object来增加BBinder的强引用计数,这样确保在写入驱动的过程中,用户空间的BBinder不会被析构。
当用户层和驱动的一次会话结束的时候,Parcel容器会被析构,在Parcel的析构函数中会调用release_object将对象强引用计数减1,然后释放掉自己所分配的buffer。
在驱动层检测到用户传输下来的数据中有BBinder对象,会使用命令BR_INCREFS和BR_ACQUIRE通知用户空间增加BBinder对象的弱引用计数和强引用计数。在驱动层binder_node保存着BBinder对象在用户空间的地址以及管理其在驱动层的计数关系。binder_ref相当于对binder_node引用,每增加一个binder_ref,binder_node的internal_strong_refs变量都会加1.每减少一个binder_ref的时候,binder_node的internal_strong_refs都会减1.当此变量为0的时候,就会以BR_RELEASE和BR_DECREFS通知BBinder对象,减少在用户空间的计数,如果BBinder的强引用计数减少为0就会导致BBinder的析构函数的执行。binder_ref的存在受BpBinder的影响,一个BpBinder对应着唯一的一个binder_ref,当BpBinder在用户空间的强引用计数变为0
的时候,binder_ref的引用计数也会变为0,导致binder_ref被删除。
关于binder_node和binder_ref的引用计数关系是可以从sys系统中查看:
cat sys/kernel/debug/binder/proc/PID
这里以一张传递BBinder过程中,binder_node,binder_ref计数变化的时序图结束本章的讲解:
2.4. ProcessState和IPCThreadState简介
在每一个支持binder通信的进程的代码中,必然会有这么一句代码:
sp<ProcessState> proc(ProcessState::self());
上述代码创建了一个单例对象:ProcessState。这个对象在自己的构造函数中打开binder驱动文件:/dev/binder,获得一个文件句柄。以后和驱动的交互都是通过ioctl操作这个文件句柄来完成。同时在构造函数中,还使用mmap建立一块和驱动共享的内存。正是因为每个进程和驱动都有一块共享的内存,所以binder通信的数据拷贝次数才会为1.
此外ProcessState还有两个重要的任务:
- 保存着全进程内所有的BpBinder对象
- 扩展binder线程
一般情况下,我们在进程的入口函数main中,只会开辟两个线程用于binder通信,当服务进程同时处理多个请求的时候,binder线程可能会不够用,这个时候binder驱动会通知进程扩展binder线程。扩展线程的工作是由函数: void ProcessState::spawnPooledThread(bool isMain)来完成。
如果说,ProcessState是代表着binder通信的进程,那么IPCThreadState就代表着binder通信的线程。每一个使用binder通信机制的线程,都有一个线程特定的变量:IPCThreadState。这个对象用于和驱动的直接交互,整个binder通信协议的实现都是在这个类中。
IPCThreadState和驱动的通信使用ioctl(fd,cmd,data) 其中cmd有:
#define BINDER_WRITE_READ _IOWR(‘b‘, 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW(‘b‘, 3, int64_t)
#define BINDER_SET_MAX_THREADS _IOW(‘b‘, 5, size_t)
#define BINDER_SET_IDLE_PRIORITY _IOW(‘b‘, 6, int)
#define BINDER_SET_CONTEXT_MGR _IOW(‘b‘, 7, int)
#define BINDER_THREAD_EXIT _IOW(‘b‘, 8, int)
#define BINDER_VERSION _IOWR(‘b‘, 9, struct binder_version)
平时客户端调用服务端的接口,使用的通信协议命令就是BINDER_WRITE_READ。有上述定义可知,每条命名都对应了不同的数据结构。每条通信命令 都对应一个唯一的整数,这个32位的整数的含义如下:
以_IOW为例,其第一个参数对应着type,第二个参数对应这nr,第三个参数对应着size。其中_IOX,X可以是W或者是R,代表着数据传输的方向,W为1,R为2.
以上的每一条命令对应的数据包又是以cmd+数据的方式存放。具体的可以参考binder.h中的定义。
2.5. ServiceManager简介
每个binder service都应该有一个“入口地址”,这个“入口地址”,也就是handle句柄是由ServiceManager管理的。所有的客户端,如果想要调用binder service的服务,必然要先从SerivceManager处获得binder service的handle句柄,然后构造一个BpBinder,再接着以此BpBinder构造BpXXXService。而客户端和ServiceManager之间的通信也是透过binder进行,客户端必要也要知道ServiceManager的handle句柄,为了方便,Android规定ServiceManager的handle为0。
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
if (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)
{
return getStrongProxyForHandle(0);
}
上述代码描述的就是IServiceManager创建的过程。通过指定handle为0,获得一个IBinder(其实是IBinder的子类BpBinder)对象,然后构造出一个IServiceManager对象(其实是其子类BpServiceManager)。用户通过IServiceManager对象来查询以及注册binder service。
下面有一张图,描述了ServiceManager和Client,Service之间的关系。
- Service进程调用addService函数来注册一个IBinder(BBinder),ServiceManager中保存着IBinder(BBinder)对应的handle句柄
- Client进程通过getService函数来获得一个BBinder对应的句柄,构造出代理对象。
是不是所有的进程都可以注册binder service呢?也不是。ServiceManager中对于注册service的权限是有限制的。
static struct {
unsigned uid;
const char *name;
} allowed[] = {
{ AID_MEDIA, "media.audio_flingIMemoryer" },
{ AID_MEDIA, "media.player" },
{ AID_MEDIA, "media.camera" },
{ AID_MEDIA, "media.audio_policy" },
{ AID_DRM, "drm.drmManager" },
{ AID_NFC, "nfc" },
{ AID_BLUETOOTH, "bluetooth" },
{ AID_RADIO, "radio.phone" },
{ AID_RADIO, "radio.sms" },
{ AID_RADIO, "radio.phonesubinfo" },
{ AID_RADIO, "radio.simphonebook" },
{ AID_RADIO, "phone" },
{ AID_RADIO, "sip" },
{ AID_RADIO, "isms" },
{ AID_RADIO, "iphonesubinfo" },
{ AID_RADIO, "simphonebook" },
{ AID_MEDIA, "common_time.clock" },
{ AID_MEDIA, "common_time.config" },
};
int svc_can_register(unsigned uid, uint16_t *name)
{
unsigned n;
if ((uid == 0) || (uid == AID_SYSTEM))
return 1;
for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
return 1;
return 0;
}
ServiceManager在接收到Serivce进程的注册请求之后,会对Service进程的uid进程检查。检查规则如下:
- 如果Service进程的uid为root,或者AID_SYSTEM,可以注册;
- 如果uid在allowed数组中,并且注册的binder service的名字也和allowed数组中的字符串匹配,就允许注册;
- 除上述两种情况以外,都不允许注册
也就是说,只有指定的uid才是可以注册binder service。
2.6. IMemory简介
因为binder驱动为每个参与通信的进程只分配了总数为1M的共享内存空间,所以在传输大容量的数据的时候,不能直接数据写入Parcel传递给binder驱动。
如果我们需要传递大容量数据,比如一张图片,这个时候就需要使用IMemory类了。IMemory象征着一块共享内存,用于在两个进程之间交换数据,我们先来看一个例子。
sp<MemoryHeapBase> heap = new MemoryHeapBase(size, 0, "MetadataRetrieverClient");
sp<IMemory> mAlbumArt = new MemoryBase(heap, 0, size);
memcpy(mAlbumArt->pointer(), albumArtata, albumArtmSize);
上面三行代码表示了IMemory最简单的用法:
- MemoryHeapBase:代表一块堆内存。通过构造MemoryHeapBase,获得一份共享内存。其构造函数第一个参数size,指定了创建内存的大小;第二个变量是flag标记,指定为0即可;第三个是这个共享内存的名字。
- MemoryBase:代表上述分配的内存中的一个小块内存。在需要创建多份共享内存的时候,为了避免创建MemoryHeapBase带来的开销,一般是创建一份MemoryHeapBase,然后将其分为多个小的MemoryBase。
- 获得一个IMemory对象之后,就可以同通过其pointer()方法获得其指针地址,然后往这个指针所指向的地址写入数据。
在写入数据完毕之后,可以把这个IMemory写入到Parcel传递到client进程。client就可以采取相反的动作,从这个IMemory中读取数据。
为什么IMemory可以跨进程传递?因为它是基于binder实现,下面是它的类图关系:
从图中可以发现,绿色部分标记的类IMemory和IMemoryHeap是顶层接口类。其中IMemoryHeap代表分配的大内存,而IMemory代表在IMemoryHeap上分配的一小块内存。IMemoryHeap的子类MemoryHeapBase负责实现内存的分配。
IMemory和IMemoryHeap的关系可以用下图来描述:
在客户端,就可以通过以下代码获取一个IMemory和这块内存在本进程所对应的地址和大小:
sp<IMemory> artWorkMemory = interface_cast<IMemory>(ibinder);
void *data = artWorkMemory->pointer();
int size = artWorkMemory->size();
其实关于IMemory的使用还是很简单的,第一步创建一个MemoryHeapBase,负责创建整块内存;第二步创建一个MemoryBase,从整块内存中分配一小块内存;第三步使用pointer()方法获取到MemoryBase所对应的地址,往地址中写入数据;第四步将MemoryBase的基类IBinder写入到Parcel中传递到客户端;第五步客户端从Parcel中读取一个IBinder,构造一个IMemory对象,同样调用pointer()方法获得内存地址,以及调用size()大小获得内存的大小。
\ 如果向创建多个IMemory,那就设计到如何有效的从一整块内存中分配小内存的问题了,必然要引入一个内存管理器,好在Android已经帮我们写好了帮助内。下面的代码是一个试例:
sp<MemoryDealer> memoryDealer = new MemoryDealer(length1+length2, "ArtWork");
sp<IMemory> artwokMemory = memoryDealer->allocate(length1);
sp<IMemory> artwokMemory = memoryDealer->allocate(length2);
这段代码和最初的代码不同之处在于IMemory的分配不在由我们手动分配,而是使用帮助类MemoryDealer来处理。下面是MemoryDealer的构造函数:
MemoryDealer::MemoryDealer(size_t size, const char* name)
: mHeap(new MemoryHeapBase(size, 0, name)),
mAllocator(new SimpleBestFitAllocator(size))
{
}
在其构造函数中,构造了一份MemoryHeapBase,同时构造了一份内存分配器:SimpleBeastFitAllocator,负责从一块大内存中分配小的内存。
上面我们提到了MemoryHeapBse负责分配一块大的共享内存,用来在两个进程之间交换数据。我们来回想下Linux上共享内存的实现方式。两个进程都打开一个文件,然后将文件映射到彼此的虚拟内存空间。但是打开文件需要知道文件的路径,同时涉及到权限等问题。在Android中提供了另外一种方式来创建共享内存:ashmem。ashmem是一个设备文件,其路径为/dev/ashmem。创建MemoryHeapBse的时候,打开这个设备,获得一份句柄fd,service端首先将此文件句柄映射到虚拟内存空间,以此来获得一份共享内存。然后通过透过binder将fd跨进程传递,客户端接收到fd之后,也将此文件句柄映射到自己的虚拟内存空间。于是两个进程映射了同一份文件句柄到彼此的虚拟内存空间,从而实现了数据的交换,而不用管理路径和权限的问题。
客户端和服务端利用ashmem交换数据示意图如下:
服务端是在构造MemoryHeapBase对象的时候,打开/dev/ashmem,然后映射文件句柄到内存空间:
MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
: mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
mDevice(0), mNeedUnmap(false), mOffset(0)
{
const size_t pagesize = getpagesize();
size = ((size + pagesize-1) & ~(pagesize-1));
int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
ALOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno));
if (fd >= 0) {
if (mapfd(fd, size) == NO_ERROR) {
if (flags & READ_ONLY) {
ashmem_set_prot_region(fd, PROT_READ);
}
}
}
}
status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset)
{
if (size == 0) {
// try to figure out the size automatically
#ifdef HAVE_ANDROID_OS
// first try the PMEM ioctl
pmem_region reg;
int err = ioctl(fd, PMEM_GET_TOTAL_SIZE, ®);
if (err == 0)
size = reg.len;
#endif
if (size == 0) { // try fstat
struct stat sb;
if (fstat(fd, &sb) == 0)
size = sb.st_size;
}
// if it didn‘t work, let mmap() fail.
}
if ((mFlags & DONT_MAP_LOCALLY) == 0) {
void* base = (uint8_t*)mmap(0, size,
PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
if (base == MAP_FAILED) {
ALOGE("mmap(fd=%d, size=%u) failed (%s)",
fd, uint32_t(size), strerror(errno));
close(fd);
return -errno;
}
//ALOGD("mmap(fd=%d, base=%p, size=%lu)", fd, base, size);
mBase = base;
mNeedUnmap = true;
} else {
mBase = 0; // not MAP_FAILED
mNeedUnmap = false;
}
mFD = fd;
mSize = size;
mOffset = offset;
return NO_ERROR;
}
- ashmem_create_region 打开/dev/ashmem
- mapfd 映射fd到内存空间
客户端是在调用pointer的时候,映射fd到内存空间:
void* IMemory::pointer() const {
ssize_t offset;
sp<IMemoryHeap> heap = getMemory(&offset);
void* const base = heap!=0 ? heap->base() : MAP_FAILED;
if (base == MAP_FAILED)
return 0;
return static_cast<char*>(base) + offset;
}
pointer函数中会调用虚函数getMemory获得一个IMemoryHeap对象。其中getMemory函数由子类BpMemory来实现。之后调用IMemoryHeap的base方法,获得基地址指针。我们来看看base方法的实现:
void* base() const { return getBase(); }
IMemoryHeap又调用了子类的getBase实现,这下又需要看看子类BpMemoryHeap的getBase方法的实现了:
void* BpMemoryHeap::getBase() const {
assertMapped();
return mBase;
}
void BpMemoryHeap::assertMapped() const
{
if (mHeapId == -1) {
sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());
sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get()));
heap->assertReallyMapped();
if (heap->mBase != MAP_FAILED) {
Mutex::Autolock _l(mLock);
if (mHeapId == -1) {
mBase = heap->mBase;
mSize = heap->mSize;
mOffset = heap->mOffset;
android_atomic_write( dup( heap->mHeapId ), &mHeapId );
}
} else {
// something went wrong
free_heap(binder);
}
}
}
void BpMemoryHeap::assertReallyMapped() const
{
if (mHeapId == -1) {
// remote call without mLock held, worse case scenario, we end up
// calling transact() from multiple threads, but that‘s not a problem,
// only mmap below must be in the critical section.
Parcel data, reply;
data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());
status_t err = remote()->transact(HEAP_ID, data, &reply);
int parcel_fd = reply.readFileDescriptor();
ssize_t size = reply.readInt32();
uint32_t flags = reply.readInt32();
uint32_t offset = reply.readInt32();
ALOGE_IF(err, "binder=%p transaction failed fd=%d, size=%ld, err=%d (%s)",
asBinder().get(), parcel_fd, size, err, strerror(-err));
int fd = dup( parcel_fd );
ALOGE_IF(fd==-1, "cannot dup fd=%d, size=%ld, err=%d (%s)",
parcel_fd, size, err, strerror(errno));
int access = PROT_READ;
if (!(flags & READ_ONLY)) {
access |= PROT_WRITE;
}
Mutex::Autolock _l(mLock);
if (mHeapId == -1) {
mRealHeap = true;
mBase = mmap(0, size, access, MAP_SHARED, fd, offset);
if (mBase == MAP_FAILED) {
ALOGE("cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)",
asBinder().get(), size, fd, strerror(errno));
close(fd);
} else {
mSize = size;
mFlags = flags;
mOffset = offset;
android_atomic_write(fd, &mHeapId);
}
}
}
}
上述方法的调用流程为:
IMemory::pointer() → IMemoryHeap::base() → BpMemoryHeap::getBase() →BpMemoryHeap::assertMapped() → BpMemoryHeap::assertReallyMapped(),最终在assertReallyMapped方法中获取到MemoryHeapBase映射文件所对应的句柄fd,然后将此fd映射到客户端的虚拟地址空间。
到此,服务端和客户端都映射了同一份/dev/ashmem文件到自己的虚拟地址空间,他们可以愉快的进行跨进程的大容量数据交换了。
PDF下载:点击打开链接