Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

[gitbook] Android框架分析系列之Android Binder详解

$
0
0

请支持作者原创:

https://mr-cao.gitbooks.io/android/content/android-binder.html


Android Binder详解

本文将对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, &reg);
        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下载:点击打开链接

作者:sky_pjf 发表于2016/10/26 17:30:26 原文链接
阅读:27 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>