分享

红狼博客 ? Android源码分析:大块内存的跨进程共享

 老匹夫 2014-02-22

我们知道,传统的IPC方式传递大块内存时,一般使用共享内存的方式。在Android Binder IPC中,有着自己独特的跨进程传递方式。它也同样,避免了内存拷贝的方式,可以让内存基址和偏移在进程间不断而且方便的传递。Android传递大内存块的方式稍有不同,这些大内存块往往位于特定的设备文件中,如pmemashmem匿名共享内存:Anonymous Shared Memory,当然,也支持共享使用其它特定设备的缓冲区。pmemashmem就是AndroidLinux内核中虚拟出的两个设备,前者是物理连续的大块内存区域,通常大小在8MB~16MB之间,不同的硬件平台或产品对其定义不同,它也位于系统的RAM中,不由内核的mm管理,而是划归给虚拟的设备pmem来管理,用户空间通过设备文件与其交互,一般用于Camera;后者原理与前者相同,也是通过设备文件使用,但它可能物理上不连续,大小也通常小些,作为普通的共享之用。在使用之前,必须调用mmap将设备缓冲区映射到系统的进程内存空间。

 

具体实现则是通过libbinder.so库中的IMemoryIMemoryHeapMemoryHeapBaseMemoryHeapPmem等类实现的。将某个设备(如pmemashmem)管理的内存映射(mmap)到系统的进程内存空间,这块内存称为内存堆(MemeoryHeap

IMemoryHeap定义了获取内存堆信息的接口,这个内存堆的信息有:HeapID(亦即设备文件描述符)、经过mmap映射到进程空间的基址、内存堆大小以及访问标志。可以使用下面四个API来获得它们的值:

virtual int getHeapID() const = 0;

virtual void* getBase() const = 0;

virtual size_t getSize() const = 0;

virtual uint32_t getFlags() const = 0;

Client侧调用接口类实际上调用到子类BpMemoryHeap对象。经Binder跨进程后,请求由server侧的子类MemoryHeapBaseMemoryHeapPmem完成。类的继承关系图如下:

 

 

 

IMemoryHeap接口中定义的四个纯虚函数由子类BpMemoryHeap中重载实现,它们均是直接返回对应的成员的值(见图中的BpMemeoryHeap,上面有四个私有成员变量,下面有四个共有成员函数)。不过在返回之前,都调用了映射函数assertMapped确保已经映射:

 

void BpMemoryHeap::assertMapped() const

{

if (mHeapId == -1) {//在还没有映射的情况下才执行下面代码

sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());//asBinder实际调用的是remote(),也就是得到BpBinder对象

sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get()));//通过bindercache缓存中找到对应的BpMemoryHeap对象,然后调用下面一行进行真正的映射

heap->assertReallyMapped();//进行真正的映射

if (heap->mBase != MAP_FAILED) {//没有出错则继续

Mutex::Autolock _l(mLock);

if (mHeapId == -1) {//还没有更新basesizeheapID等信息,则更新它们

mBase = heap->mBase;

mSize = heap->mSize;

android_atomic_write( dup( heap->mHeapId ), &mHeapId );

}

} else {

// something went wrong

free_heap(binder);

}

}

}

见代码注释,只有在还没有映射的情况下才进行映射,判断的依据是文件描述符是否为-1。首先获取对应的BpBinder对象,然后在全局的cache中查询BpMemoryHeap对象,接着调用BpMemoryHeap对象的assertReallyMapped进行真正的映射。

全局缓存cache(即类HeapCache)中维护着一个键值表,它是从Bpinder对象的弱指针到heap_info_t结构体的映射:

struct heap_info_t {

sp<IMemoryHeap> heap;

int32_t count; //使用计数

};

 

KeyedVector< wp<IBinder>, heap_info_t > mHeapCache;

 

HeapCachefind_heap函数首先查询其键值表,若找到则直接返回BpMemoryHeap对象强指针,并将使用计数加1;若没找到,则创建一个BpMemoryHeap对象,并将其添加到表项中:

heap_info_t info;

info.heap = interface_cast<IMemoryHeap>(binder); //binder创建一个BpMemoryHeap对象

info.count = 1;

//LOGD(“adding binder=%p, heap=%p, count=%d”,

// binder.get(), info.heap.get(), info.count);

mHeapCache.add(binder, info);

return info.heap;

 

也就是说,在使用IMemoryHeap也就是创建其子类对象时,不是直接使用interface_cast<IMemoryHeap>(binder)进行创建,而是首先在cache中查询;若没有查到,才进行创建。也就是确保内存堆代理BpMemoryHeap对象在一个进程中的唯一性。当多次使用到某个内存堆代理时,有使用计数维护引用次数。这样做的目的是确保只对设备文件进行一次mmap映射。从assertMapped调用assertReallyMapped就可以看出来:

void BpMemoryHeap::assertReallyMapped() const

{

if (mHeapId == -1) {

Parcel data, reply;

data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());

status_t err = remote()->transact(HEAP_ID, data, &reply);

int parcel_fd = reply.readFileDescriptor();//从对方获取设备文件描述符,通常文件描述符不能跨进程,但AndroidBinder IPCLinux内核中做了特殊处理,使它们共享内核中的file节点,因此可以跨进程。

ssize_t size = reply.readInt32();//得到设备内存大小

uint32_t flags = reply.readInt32();//访问标志

 

LOGE_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 ); //复制一个文件描述符

LOGE_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, 0);//映射到内存空间

if (mBase == MAP_FAILED) {//出错,则给出log信息

LOGE(“cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)”,

asBinder().get(), size, fd, strerror(errno));

close(fd);

} else {//成功,更新其余三个成员信息

mSize = size;

mFlags = flags;

android_atomic_write(fd, &mHeapId);

}

}

}

}

可见,它从对方得到相关信息后,在本进程空间重新进行mmap映射。

 

server侧,类MemoryHeapBase负责处理来自Client侧的请求,它有多个构造函数,当没有指定设备文件名称或设备文件描述符时,它就默认使用ashmem上的内存,否则使用指定的设备文件的内存。在这些构造函数中,首先检查并确保size是页对齐的,然后调用打开设备文件(若还没有文件描述符fd的话),再调用mapfd成员函数对设备文件进行mmap映射。

若使用的是pmem,则由MemoryHeapBase的子类MemoryHeapPmem来处理。当然,平台开发商也可以编写其它子类,来实现对其它内存设备的支持。

libbinder库中,还有类MemoryDealer这个类用来进行内存分配管理,它包含一个内存分配器SimpleBestFitAllocator,所分配的内存块为Allocation(继承自MemoryBase),但是当前还没有使用它们。

 

一个内存堆上,往往可以分为多个区域以供调用者使用,如Camera预览中的图像的一桢。IMemory就代表中内存堆中的这块内存区域。可以由三个变量来确定它:在哪个MemoryHeap、在堆中的偏移量offset和大小size。我们可以通过getMemory函数获得该内存区域的这三样信息:

sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const

偏移量offset和大小size通过修改实参传回,而MemoryHeap则通过函数返回。为方便起见,IMemory还提供了三个方便使用的辅助函数:通过fastPointerpointer直接得到指向内存块的指针,通过size函数可以得到内存块大小。

 

不管是内存堆MemoryHeapBase还是其子类,以及其里面的内存区域,均由使用者创建。如如CameraService依赖于Camera硬件来实现各项功能,这就是平台厂商实现的CameraHardeInterface的子类(如有的平台使用的是V4L2,对应的子类是CameraHardwareV4L2)。在Camera硬件实现中,一般都需要创建MemoryHeapBaseMemoryBase对象,即server侧对象。Camera的使用者(如应用程序)在Client侧通过调用,将得到接口对象强指针,实际指向前述BpXXX(即IMemoryHeapIMemory的子类:BpMemoryHeapBpMemory)对象。Client侧在在自己的进程里进行内存映射后,通过对应接口API就可以得到heapIDbase基址和偏移量offset,于是就可以对数据(如预览、拍照和录像的数据)进行自己的处理了。因此,在serverclient侧,它们基址多半不同,但都映射到同一设备的内存上,如pmem中的一块内存。这样,就实现了大块内存的共享。也就是说,跨进程传递的过程,实际是传递设备文件描述符、基址和偏移量等信息的过程。

 

 

本文链接地址: http://www./?p=902

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多