通常可以使用 libcutils 库中的 ashmem_create_region 函数创建一块共享内存区域:
#define ASHMEM_DEVICE "/dev/ashmem" /* * ashmem_create_region - creates a new ashmem region and returns the file * descriptor, or <0 on error * * `name' is an optional label to give the region (visible in /proc/pid/maps) * `size' is the size of the region, in page-aligned bytes */ int ashmem_create_region(const char *name, size_t size) { int fd, ret; fd = open(ASHMEM_DEVICE, O_RDWR); if (fd < 0) return fd; if (name) { char buf[ASHMEM_NAME_LEN]; strlcpy(buf, name, sizeof(buf)); ret = ioctl(fd, ASHMEM_SET_NAME, buf); if (ret < 0) goto error; } ret = ioctl(fd, ASHMEM_SET_SIZE, size); if (ret < 0) goto error; return fd; error: close(fd); return ret; }
拿到文件描述符后,可以通过 mmap 将 kernel 空间分配的内存映射到用户空间,这样就能像普通内存一样使用。
如果是其他进程需要使用这块内存,并不能通过 open 设备节点的方式来重新打开此块内存,而是需要获取创建此共享内存时打开的文件描述符。具体原因可以阅读 ashmen 的驱动代码(每次 open 都会创建新的共享内存区域,close 会回收之前创建的内存区域)。
那么怎么跨进程获取文件描述符呢?当然是借助于 binder,服务端通过 writeFileDescriptor 写入文件描述符号参数,客户端则通过 readFileDescriptor 读取文件描述符参数,具体实现原理参考 binder 驱动。
客户端进程获取到文件描述符后,同样通过 mmap 将其映射到用户空间,这样就实现内存共享。
为了方便使用共享内存,Android 在 C++ 层提供两个接口:IMemoryHeap 和 IMemory。
MemoryHeap 可以理解为内存堆,它是一个 Binder 接口,服务端实现类是 MemoryHeapBase,客户端实现类是 BpMemoryHeap。他们之前的继承关系如下:
在 IMemoryHeap 类中定义的接口,只有 getHeapID 是需要跨进程调用的,其它函数分别在子类中本地实现。因此通过继承来约束客户端和服务端的接口,使其保一致,者并非 Binder 的专利,而是一种没想对象的设计手段,我们同样可以对本地方法进行约束。
首先看 BpMemoryHeap 的构造函数实现:
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); } } } }
构造函数还有其它重载版本,这里不一一分析。首先通过 ashmem_create_region 创建共享内存区域,然后调用 mapfd 将内存映射到用户空间。
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; }
映射完内存,将虚拟地址保存到 mBase,文件描述符保存到 mFD,内存大小保存到 mSize,偏移量保存到 mOffset。其它属性获取函数只是简单的返回变量的值,比如 getHeapID:
int MemoryHeapBase::getHeapID() const { return mFD; }
跨继承调用的函数在父类 BnMemoryHeap 的 onTransact 处理:
status_t BnMemoryHeap::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { switch(code) { case HEAP_ID: { CHECK_INTERFACE(IMemoryHeap, data, reply); reply->writeFileDescriptor(getHeapID()); reply->writeInt32(getSize()); reply->writeInt32(getFlags()); reply->writeInt32(getOffset()); return NO_ERROR; } break; default: return BBinder::onTransact(code, data, reply, flags); } }
这里仅处理 HEAP_ID 一个 case,正如前面所说,只有 getHeapID 函数是跨进程访问的。这里主要通过 writeFileDescriptor 写入文件描述符。其它参数还有内存大小、标记和偏移。
客户端的实现相对比较复杂,先看代码:
int BpMemoryHeap::getHeapID() const { assertMapped(); return mHeapId; } void* BpMemoryHeap::getBase() const { assertMapped(); return mBase; } size_t BpMemoryHeap::getSize() const { assertMapped(); return mSize; } uint32_t BpMemoryHeap::getFlags() const { assertMapped(); return mFlags; } uint32_t BpMemoryHeap::getOffset() const { assertMapped(); return mOffset; }
首先所有的接口都调用 assertMapped 函数,并且直接返回属性的值。很显然 assertMapped 函数会对属性进行初始化:
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); } } }
构造函数中属性变量都被初始化成非法值,第一此访问时 mHeapId 等于 -1,find_heap 函数为确认 BpMemoryHeap 对象是否已经存在一个实例,assertReallyMapped 如果没有映射过内存则执行映射。这么做的目的是保证进程中只有第一个 BpMemoryHeap 对象会执行 mmap 操作,其它 BpMemoryHeap 对象直接拷贝映射好的属性值即可。
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); } } } }
通过 binder 向服务端发送 HEAP_ID 消息,通过 readFileDescriptor 获取共享内存的文件描述符,然后获取其它参数来初始化属性,然后调用 mmap,将虚拟地址保存到 mBase。注意内存的映射地址并不是通过 binder 从服务端获取,而是通过 mmap 从内核映射过来。
涉及类的继承关系:
Memory 用于表示共享内存堆中的一块内存,它可看作 MemoryHeap 的子集。
MemoryBase 是服务的实现类,其定义如下:
class MemoryBase : public BnMemory { public: MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size); virtual ~MemoryBase(); virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const; protected: size_t getSize() const { return mSize; } ssize_t getOffset() const { return mOffset; } const sp<IMemoryHeap>& getHeap() const { return mHeap; } private: size_t mSize; ssize_t mOffset; sp<IMemoryHeap> mHeap;
};
构造函数源码如下:
MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size) : mSize(size), mOffset(offset), mHeap(heap) { }
这里初始化了所有的成员变量。mHeap 表示内存所在的堆,mOffset 表示内存起始地址相对堆内存的偏移值,mSize 表示内存的大小。
MemoryBase 所有方法中,只有 getMemory 函数可以通过 Binder 跨进程访问。在 BnMemory 类中处理客户端发送的消息:
status_t BnMemory::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { switch(code) { case GET_MEMORY: { CHECK_INTERFACE(IMemory, data, reply); ssize_t offset; size_t size; reply->writeStrongBinder( getMemory(&offset, &size)->asBinder() ); reply->writeInt32(offset); reply->writeInt32(size); return NO_ERROR; } break; default: return BBinder::onTransact(code, data, reply, flags); } }
可以看到这仅仅处理 GET_MEMORY 消息。getMemory 的实现代码如下:
sp<IMemoryHeap> MemoryBase::getMemory(ssize_t* offset, size_t* size) const { if (offset) *offset = mOffset; if (size) *size = mSize; return mHeap; }
从实现看 getMemory 获取了关于这块内存的所有信息。返回值 mHeap 是 MemoryHeapBase 类型,通过 Binder 传输,客户端会获取一个 BpMemoryHeap 对象。
客户端的实现类是 BpMemory,只有 getMemory 会发起 Binder 调用。
sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const { if (mHeap == 0) { Parcel data, reply; data.writeInterfaceToken(IMemory::getInterfaceDescriptor()); if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) { sp<IBinder> heap = reply.readStrongBinder(); ssize_t o = reply.readInt32(); size_t s = reply.readInt32(); if (heap != 0) { mHeap = interface_cast<IMemoryHeap>(heap); if (mHeap != 0) { mOffset = o; mSize = s; } } } } if (offset) *offset = mOffset; if (size) *size = mSize; return mHeap; }
对于客户端,通过 interface_cast 转换后,mHeap 初始化成 BpMemoryHeap 类型的对象。该 Binder 调用同时也初始化了 mOffset 和 mSize。
那么怎么获取内存的首地址呢?父类中定义了 pointer 函数用于返回该内存的首地址。
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; }
其实就是基地址加上偏移。
Android 还提供一个内存分配的工具类 MemoryDealer,用于管理内存堆,注意该类是不具备跨进程通信能力的,它仅在服务端工作。
MemoryDealer::MemoryDealer(size_t size, const char* name) : mHeap(new MemoryHeapBase(size, 0, name)), mAllocator(new SimpleBestFitAllocator(size)) { }
从构造函数来看,首先会 new 一个内存堆 MemoryHeapBase,然后创建一个内存分配器 SimpleBestFitAllocator,后续如果服务端需要内存,则通过 allocate 函数从堆中分配一块。
sp<IMemory> MemoryDealer::allocate(size_t size) { sp<IMemory> memory; const ssize_t offset = allocator()->allocate(size); if (offset >= 0) { memory = new Allocation(this, heap(), offset, size); } return memory; }
这里调用内存分配器进行分配。分配好的内存以 Allocation 的形式返回。Allocation 是 MemoryBase 的子类。因此它是可以被客户端使用的内存。Allocation 对象在析构的时候,会自动将内存回收。具体是通过 deallocate 回收内存。
void MemoryDealer::deallocate(size_t offset) { allocator()->deallocate(offset); }
内存的分配和回收算法,请阅读 SimpleBestFitAllocator 源码。