一、匿名共享内存的共享原理
在Android系统中,每一块匿名共享内存都是使用一个文件描述符来描述的,而这个文件描述符是通过打开设备文件/dev/ashmem获得的。当两个进程需要共享一块匿名共享内存时,只要把它的文件描述符从一个进程传递给别外一个进程即可。
在Linux系统中, 文件描述符其实就是一个整数,它只在进程范围内有效,即值相等的两个文件描述符在两个不同的进程中具有不同的含义。在Linux内核中, 每一个文件描述符都对应有一个文件结构体( struct file )。文件结构体是一个内核对象, 每一个打开的文件都有一个对应的文件结构体。
关系如下图所示:
不同的文件描述符可以对应于同一个文件结构体, 而不同的文件结构体也可以对应于同一个文件。当应用程序调用函数open来打开一个文件时, 文件系统就会为该文件创建一个文件结构体和一个文件描述符, 最后将这个文件描述符返回给应用程序。
打开设备文件/dev/ashmem时,Ashmem驱动程序会为它在内核中创建一块匿名共享内存。
匿名共享内存能够在两个不同的进程中共享的奥妙就在于,这两个进程分别有一个文件描述符fdl和fd2,它们指向了同一个文件结构体file1,而这个文件结构体又指向了一块匿名共享内存asma。这时候,如果这两个进程的文件描述符fd1和fd2分别被映射到各自的地址空间,那么它们就会把同一块匿名共享内存映射到各自的地址空间,从而实现在两个不同的进程中共享同一块匿名共享内存。
问题的关键是如何让两个位于不同进程中的文件描述符fd1和fd2指向同一个用来描述匿名共享内存asma的文件结构体file1。
假设进程pl首先调用函数open来打开设备文件/dev/ashmem,这样它就得到了一块匿名共享内存asma、一个文件结构体file1和一个文件描述符fd1。接着进程p2通过Binder进程间通信机制请求进程p1将文件描述符fd1返回给它,进程p1要通过Binder驱动程序将文件描述符fd1返回给进程p2。由于文件描述符fd1只在进程p1中有效,因此,Binder驱动程序就不能直接将文件描述符fd1返回给进程p2。这时候Binder驱动程序就会在进程p2中创建一个新的文件描述符fd2,使得它也指向文件结构体file1,最后再将文件描述符fd2返回给进程p2。这样,文件描述符旧l和fd2就指向同一个文件结构体filel了,即指向了同一块匿名共享内存。
由于本人不太擅长驱动开发,所以无法帮助各位大神梳理asam的驱动(其实这是内核实现的部分,不对外开放),接下来我将介绍匿名共享内存提供的运行时库以及在C/C++和Java层提供的接口介绍。
二、运行时库cutils的匿名共享内存访问接口
代码路径:/system/core/libcutils/ashmem-dev.c
(http://androidxref.com/8.0.0_r4/xref/system/core/libcutils/ashmem-dev.c)
运行时库cutils提供了五个C接口来访问Ashmem驱动程序, 它们分别是ashmem_create_region 、ashmem_pin_region、ashmem_unpin_region 、ashmem_set_prot_region和ashmem_get_size_region。
下面结合8.0.0_r4的源码分别分析一下五个函数的实现:
2.1 ashmem_create_region
函数ashmem_create_region用来请求Ashmem驱动程序为应用程序创建一块匿名共享内存, 其中,参数name和size分别表示请求创建的匿名共享内存的名称和大小。
请求Ashmem驱动程序创建一块匿名共享内存分三步来完成
代码路径:/system/core/libcutils/ashmem-dev.c
(http://androidxref.com/8.0.0_r4/xref/system/core/libcutils/ashmem-dev.c)
142/*
143 * ashmem_create_region - creates a new ashmem region and returns the file
144 * descriptor, or <0 on error
145 *
146 * `name' is an optional label to give the region (visible in /proc/pid/maps)
147 * `size' is the size of the region, in page-aligned bytes
148 */
149int ashmem_create_region(const char *name, size_t size)
150{
151 int ret, save_errno;
152
153 int fd = __ashmem_open();//
154 if (fd < 0) {
155 return fd;
156 }
...........................................
180}
第一步是在153行调用函数__ashmem_open(),在__ashmem_open()函数中80行调用__ashmem_open_locked()
75static int __ashmem_open() 76{ 77 int fd; 78 79 pthread_mutex_lock(&__ashmem_lock); 80 fd = __ashmem_open_locked(); 81 pthread_mutex_unlock(&__ashmem_lock); 82 83 return fd; 84}
在__ashmem_open_locked()函数的53行调用open函数打开设备文件ASHMEM_DEVICE,即设备文件/dev/ashmem
37#define ASHMEM_DEVICE "/dev/ashmem"
47/* logistics of getting file descriptor for ashmem */ 48static int __ashmem_open_locked() 49{ 50 int ret; 51 struct stat st; 52 53 int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR)); 54 if (fd < 0) { 55 return fd; 56 } 57 58 ret = TEMP_FAILURE_RETRY(fstat(fd, &st)); 59 if (ret < 0) { 60 int save_errno = errno; 61 close(fd); 62 errno = save_errno; 63 return ret; 64 } 65 if (!S_ISCHR(st.st_mode) || !st.st_rdev) { 66 close(fd); 67 errno = ENOTTY; 68 return -1; 69 } 70 71 __ashmem_rdev = st.st_rdev; 72 return fd; 73} 74
第二步是在162行使用IO控制命令ASHMEM_SET_NAME来请求Ashmem驱动程序将前面所创建的匿名共享内存的名称修改为name。第三步是在168行使用IO控制命令ASHMEM_SET_SIZE来请求Ashmem驱动程序将前面所创建的匿名共享内存的大小修改为size。
149int ashmem_create_region(const char *name, size_t size) 150{ ............................................ 161 strlcpy(buf, name, sizeof(buf)); 162 ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf)); 163 if (ret < 0) { 164 goto error; 165 } 166 } 167 168 ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size)); .......................................... 180}
Ashmem驱动程序在创建一块匿名共享内存时, 将它的大小设置为0
2.2 ashmem_pin_region
192int ashmem_pin_region(int fd, size_t offset, size_t len)
193{
194 struct ashmem_pin pin = { offset, len };
195
196 int ret = __ashmem_is_ashmem(fd, 1);
197 if (ret < 0) {
198 return ret;
199 }
200
201 return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin));
202}
函数ashmem_pin_region在201行使用IO控制命令ASHMEM_PIN来请求Ashmem驱动程序锁定一小块匿名共享内存。其中, 参数fd是前面打开设备文件/dev/ashmem所得到的一个文件描述符;参数。offset和len用来指定要锁定的内存块在其宿主匿名共享内存中的偏移地址和长度。
2.3 ashmem_unpin_region
204int ashmem_unpin_region(int fd, size_t offset, size_t len)
205{
206 struct ashmem_pin pin = { offset, len };
207
208 int ret = __ashmem_is_ashmem(fd, 1);
209 if (ret < 0) {
210 return ret;
211 }
212
213 return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_UNPIN, &pin));
214}
函数ashmem_unpin_region在213行使用IO控制命令ASHMEM_UNPIN来请求Ashmem驱动程序解锁一小块匿名共享内存。其中, 参数fd是前面打开设备文件/dev/ashmem所得到的一个文件描述符;参数offset和len用来指定要解锁的内存块在其宿主匿名共享内存中的偏移地址和长度。
2.4 ashmem_set_prot_region
182int ashmem_set_prot_region(int fd, int prot)
183{
184 int ret = __ashmem_is_ashmem(fd, 1);
185 if (ret < 0) {
186 return ret;
187 }
188
189 return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_PROT_MASK, prot));
190}
函数ashmem_set_prot_region在189行使用IO控制命令ASHMEM_SET_PROT MASK来请求Ashmem驱动程序修改一块匿名共享内存的访问保护位。其中, 参数fd是前面打开设备文件/dev/ashmem所得到的一个文件描述符;参数prot指定要修改的访问保护位,它的取值为PROT_EXEC 、PROT_READ 、PROT_WRITE或其组合值。
2.5 ashmem_get_size_region
216int ashmem_get_size_region(int fd)
217{
218 int ret = __ashmem_is_ashmem(fd, 1);
219 if (ret < 0) {
220 return ret;
221 }
222
223 return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
224}
函数ashmem_get_size_region在223行使用IO控制命令ASHMEM_GET_SIZE来请求Ashmem驱动程序返回一块匿名共享内存的大小。 其中, 参数fd是前面打开设备文件/dev/ashmem所得到的一个文件描述符。
三、匿名共享内存的C/C++访问接口
Android系统在应用框架层中提供了两个C++类MemoryHeapBase和MemoryBase来创建和管理匿名共享内存。完整的共享内存就使用MemoryHeapBase,部分共享就使用MemoryBase。MemoryBase类是在MemoryHeapBase类的基础上实现的。
MemoryHeapBase类的实例是一个Service组件,相应地,它会有一个对应的Client组件。Service组件必须要实现BnInterface接口,并且是运行在Server进程中的;而Client组件必须要实现BpInterface接口,并且是运行在Client进程中的。
与匿名共享内存创建和管理相关的三个类IMemoryHeap、BnMemorymHeap和MemoryHeapBase
3.1 IMemoryHeap类定义了MemoryHeapBase服务接口
五个成员函数getHeapID、getBase、getSize、getFlags和getOffset, 分别用来获取一个匿名共享内存块的文件描述符、映射地址、大小、访问保护位和偏移量。
代码路径:/frameworks/native/libs/binder/include/binder/IMemory.h
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/include/binder/IMemory.h)
42 virtual int getHeapID() const = 0;//用来获得匿名共享内存块的打开文件描述符 43 virtual void* getBase() const = 0;//用来获得匿名共享内存块的基地址 44 virtual size_t getSize() const = 0;//用来获得匿名共享内存块的大小 45 virtual uint32_t getFlags() const = 0;//用来获得匿名共享内存块的保护标识位 46 virtual uint32_t getOffset() const = 0;//用来获得匿名共享内存块中的偏移量
3.2 成员函数onTransact
BnMemoryHeap类是一个Binder本地对象类, 它只有一个成员函数onTransact, 用来处理Client进程发送过来的HEAP_ID请求。代码路径:/frameworks/native/libs/binder/IMemory.cpp(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/IMemory.cpp)
379
380IMPLEMENT_META_INTERFACE(MemoryHeap, "android.utils.IMemoryHeap");
381
382BnMemoryHeap::BnMemoryHeap() {
383}
384
385BnMemoryHeap::~BnMemoryHeap() {
386}
387
388status_t BnMemoryHeap::onTransact(
389 uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
390{
391 switch(code) {
392 case HEAP_ID: {
393 CHECK_INTERFACE(IMemoryHeap, data, reply);
394 reply->writeFileDescriptor(getHeapID());
395 reply->writeInt32(getSize());
396 reply->writeInt32(getFlags());
397 reply->writeInt32(getOffset());
398 return NO_ERROR;
399 } break;
400 default:
401 return BBinder::onTransact(code, data, reply, flags);
402 }
403}
调用由其子类重写的成员函数getHeaplD、getSize、getFlags和getOffset来获取一个匿名共享内存块的文件描述符、大小、访问保护位和偏移量, 并且将它们写入到Parcel对象reply中, 以便可以将它们返回给Client进程。
3.3 MemoryHeapBase类
MemoryHeapBase类用来描述一个匿名共享内存服务, 它继承了BnMemoryHeap类, 并且实现了IMemoryHeap接口的五个成员函数getHeaplD、getBase、getSize、getFlags和getOffset。
代码路径:/frameworks/native/libs/binder/include/binder/MemoryHeapBase.h
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/include/binder/MemoryHeapBase.h)
30class MemoryHeapBase : public virtual BnMemoryHeap 31{ 32public: ...................................................... 58 59 /* implement IMemoryHeap interface */ 60 virtual int getHeapID() const; 61 62 /* virtual address of the heap. returns MAP_FAILED in case of error */ 63 virtual void* getBase() const; 64 65 virtual size_t getSize() const; 66 virtual uint32_t getFlags() const; 67 virtual uint32_t getOffset() const; 68 .............................................. 87 88private: 89 status_t mapfd(int fd, size_t size, uint32_t offset = 0); 90 91 int mFD; 92 size_t mSize; 93 void* mBase; 94 uint32_t mFlags; 95 const char* mDevice; 96 bool mNeedUnmap; 97 uint32_t mOffset; 98}; 99 100// --------------------------------------------------------------------------- 101}; // namespace android 102 103#endif // ANDROID_MEMORY_HEAP_BASE_H 104
mFD是一个文件描述符,它是打开设备文件/dev/ashmem后得到的,用来描述一个匿名共享内存块。成员变量mSize、mBase、mFlags、mDevice、mNeedUnmap和mOffset分别用来描述这块匿名共享内存块的大小、 映射地址、访问保护位、设备文件、临时增加内存和偏移量。
3.4 MemoryHeapBase的构造函数
代码路径:/frameworks/native/libs/binder/MemoryHeapBase.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/MemoryHeapBase.cpp)37MemoryHeapBase::MemoryHeapBase()
38 : mFD(-1), mSize(0), mBase(MAP_FAILED),
39 mDevice(NULL), mNeedUnmap(false), mOffset(0)
40{
41}
42
size表示要创建的匿名共享内存的大小,flags是用来设置这块匿名共享内存的属性的,name是用来标识这个匿名共享内存的名字,mFD表示匿名共享内存的文件描述符,mBase标识匿名共享内存的基地址,mDevice表示匿名共享内存的设备文件。这个构造函数只是简单初始化了各个成员变量。
43MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) 44 : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), 45 mDevice(0), mNeedUnmap(false), mOffset(0) 46{ 47 const size_t pagesize = getpagesize();//获得系统中一页大小的内存 48 size = ((size + pagesize-1) & ~(pagesize-1));//内存页对齐 49 int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);//创建一块匿名共享内存 50 ALOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno)); 51 if (fd >= 0) { 52 if (mapfd(fd, size) == NO_ERROR) {//将创建的匿名共享内存映射到当前进程地址空间中 53 if (flags & READ_ONLY) { 54 ashmem_set_prot_region(fd, PROT_READ);//如果地址映射成功,修改匿名共享内存的访问属性 55 } 56 } 57 } 58} 59
该构造函数通过指定已创建的匿名共享内存的设备文件来打开该共享内存,并映射到当前进程地址空间。device指定已经创建的匿名共享内存的设备文件路径,size指定匿名共享内存的大小
60MemoryHeapBase::MemoryHeapBase(const char* device, size_t size, uint32_t flags) 61 : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), 62 mDevice(0), mNeedUnmap(false), mOffset(0) 63{ 64 int open_flags = O_RDWR; 65 if (flags & NO_CACHING) 66 open_flags |= O_SYNC; 67 68 int fd = open(device, open_flags);//打开匿名共享内存设备文件 69 ALOGE_IF(fd<0, "error opening %s: %s", device, strerror(errno)); 70 if (fd >= 0) { 71 const size_t pagesize = getpagesize();//将指定的匿名共享内存大小按页对齐 72 size = ((size + pagesize-1) & ~(pagesize-1)); 73 if (mapfd(fd, size) == NO_ERROR) {//将匿名共享内存映射到当前进程地址空间 74 mDevice = device; 75 } 76 } 77} 78 79MemoryHeapBase::MemoryHeapBase(int fd, size_t size, uint32_t flags, uint32_t offset) 80 : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), 81 mDevice(0), mNeedUnmap(false), mOffset(0) 82{ 83 const size_t pagesize = getpagesize();//指定匿名共享内存大小按页对齐 84 size = ((size + pagesize-1) & ~(pagesize-1)); 85 mapfd(fcntl(fd, F_DUPFD_CLOEXEC, 0), size, offset); 86}
以上四种构造函数都通过mapfd函数来将匿名共享内存映射到当前进程的虚拟地址空间,以便进程可以直接访问匿名共享内存中的内容
3.5 MemoryHeapBase::mapfd
101status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset) 102{ 103 if (size == 0) { 104 // try to figure out the size automatically 105 struct stat sb; 106 if (fstat(fd, &sb) == 0) 107 size = sb.st_size; 108 // if it didn't work, let mmap() fail. 109 } 110 111 if ((mFlags & DONT_MAP_LOCALLY) == 0) {//通过mmap系统调用进入内核空间的匿名共享内存驱动,并调用ashmem_map函数将匿名共享内存映射到当前进程 112 void* base = (uint8_t*)mmap(0, size, 113 PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset); 114 if (base == MAP_FAILED) { 115 ALOGE("mmap(fd=%d, size=%u) failed (%s)", 116 fd, uint32_t(size), strerror(errno)); 117 close(fd); 118 return -errno; 119 } 120 //ALOGD("mmap(fd=%d, base=%p, size=%lu)", fd, base, size); 121 mBase = base; 122 mNeedUnmap = true; 123 } else { 124 mBase = 0; // not MAP_FAILED 125 mNeedUnmap = false; 126 } 127 mFD = fd; 128 mSize = size; 129 mOffset = offset; 130 return NO_ERROR; 131}
在112行的mmap函数的第一个参数0表示由内核来决定这个匿名共享内存文件在进程地址空间的起始位置,第二个参数size表示要映射的匿名共享内文件的大小,第三个参数PROT_READ|PROT_WRITE表示这个匿名共享内存是可读写的,第四个参数fd指定要映射的匿名共享内存的文件描述符,第五个参数offset表示要从这个文件的哪个偏移位置开始映射。
调用mmap函数返回之后,就得这块匿名共享内存在本进程地址空间中的起始访问地址了,将这个地址保存在成员变量mBase中。还将这个匿名共享内存的文件描述符和以及大小分别保存在成员变量mFD和mSize,并提供了相应接口函数来访问这些变量值。
MemoryHeapBase服务内部的匿名共享内存块的文件描述符mFD、映射地址 mBase、大小mSize、访问保护位mFlags、设备文件mDevice和偏移量mOffset返回给调用者。
152int MemoryHeapBase::getHeapID() const { 153 return mFD; 154} 155 156void* MemoryHeapBase::getBase() const { 157 return mBase; 158} 159 160size_t MemoryHeapBase::getSize() const { 161 return mSize; 162} 163 164uint32_t MemoryHeapBase::getFlags() const { 165 return mFlags; 166} 167 168const char* MemoryHeapBase::getDevice() const { 169 return mDevice; 170} 171 172uint32_t MemoryHeapBase::getOffset() const { 173 return mOffset; 174}
3.6 Client端
MemoryHeapBase类在Client端主要是实现一个类型为BpMemoryHeap的Client组件, 即一个实现了 IMemoryHeap接口的Binder代理对象, 通过它可以获得运行在Server端的MemeroyHeapBase服务内部的匿名共享内存块的文件描述符、 大小和访问保护位。 有了这个匿名共享内存块的信息之后, Client端就可以将它映射到Client进程的地址空间, 从而可以访问在Server端创建的匿名共享内存块。
BpMemoryHeap类用来描述一个MemorγHeapBase服务的代理对象
代码路径:/frameworks/native/libs/binder/IMemory.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/IMemory.cpp)
80class BpMemoryHeap : public BpInterface81{ .................................... 112 113 mutable std::atomic mHeapId; 114 mutable void* mBase; 115 mutable size_t mSize; 116 mutable uint32_t mFlags; 117 mutable uint32_t mOffset; 118 mutable bool mRealHeap; 119 mutable Mutex mLock; 120};
成员变量mHeapId是一个文件描述符, 它是通过请求MemorγHeapBase服务获得的, 用来描述一个匿名共享内存块。
在BpMemoryHeap的构造函数里只是初始化一些成员变量
249BpMemoryHeap::BpMemoryHeap(const sp& impl) 250 : BpInterface (impl), 251 mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mOffset(0), mRealHeap(false) 252{ 253}
BpMemoryHeap提供了和服务端MemoryHeapBase相对应的服务函数
352int BpMemoryHeap::getHeapID() const {
353 assertMapped();
354 // We either stored mHeapId ourselves, or loaded it with acquire semantics.
355 return mHeapId.load(memory_order_relaxed);
356}
357
358void* BpMemoryHeap::getBase() const {
359 assertMapped();
360 return mBase;
361}
362
363size_t BpMemoryHeap::getSize() const {
364 assertMapped();
365 return mSize;
366}
367
368uint32_t BpMemoryHeap::getFlags() const {
369 assertMapped();
370 return mFlags;
371}
372
373uint32_t BpMemoryHeap::getOffset() const {
374 assertMapped();
375 return mOffset;
376}
通过BpMemoryHeap代理对象访问匿名共享内存前都会调用函数assertMapped()来判断是否已经向服务获取到匿名共享内存的信息,如果没有就向服务端发起请求:
280void BpMemoryHeap::assertMapped() const 281{ 282 int32_t heapId = mHeapId.load(memory_order_acquire); 283 if (heapId == -1) {//如果还没有请求服务创建匿名共享内存 284 spbinder(IInterface::asBinder(const_cast (this)));//将当前BpMemoryHeap对象转换为IBinder对象 285 sp heap(static_cast (find_heap(binder).get()));//从成员变量gHeapCache中查找对应的BpMemoryHeap对象 286 heap->assertReallyMapped();//向服务端请求获取匿名共享内存信息 287 if (heap->mBase != MAP_FAILED) {//判断该匿名共享内存是否映射成功 288 Mutex::Autolock _l(mLock); 289 if (mHeapId.load(memory_order_relaxed) == -1) {//保存服务端返回回来的匿名共享内存信息 290 mBase = heap->mBase; 291 mSize = heap->mSize; 292 mOffset = heap->mOffset; 293 int fd = fcntl(heap->mHeapId.load(memory_order_relaxed), F_DUPFD_CLOEXEC, 0); 294 ALOGE_IF(fd==-1, "cannot dup fd=%d", 295 heap->mHeapId.load(memory_order_relaxed)); 296 mHeapId.store(fd, memory_order_release); 297 } 298 } else { 299 // something went wrong 300 free_heap(binder); 301 } 302 } 303}
mHeapId等于-1,表示匿名共享内存还为准备就绪,因此请求服务端MemoryHeapBase创建匿名共享内存,否则该函数不作任何处理。只有第一次使用匿名共享时才会请求服务端创建匿名共享内存。由于在客户端进程中使用同一个BpBinder代理对象可以创建多个与匿名共享内存业务相关的BpMemoryHeap对象,因此定义了类型为HeapCache的全局变量gHeapCache用来保存创建的所有BpMemoryHeap对象,assertMapped函数首先将当前BpMemoryHeap对象强制转换为IBinder类型对象,然后调用find_heap()函数从全局变量gHeapCache中查找出对应的BpMemoryHeap对象,并调用assertReallyMapped()函数向服务进程的BnemoryHeap请求创建匿名共享内存。
305void BpMemoryHeap::assertReallyMapped() const 306{ 307 int32_t heapId = mHeapId.load(memory_order_acquire); 308 if (heapId == -1) {//再次判断是否已经请求创建过匿名共享内存 309 310 // remote call without mLock held, worse case scenario, we end up 311 // calling transact() from multiple threads, but that's not a problem, 312 // only mmap below must be in the critical section. 313 314 Parcel data, reply; 315 data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor()); 316 status_t err = remote()->transact(HEAP_ID, data, &reply);//向服务端BnMemoryHeap发起请求 317 int parcel_fd = reply.readFileDescriptor(); 318 ssize_t size = reply.readInt32(); 319 uint32_t flags = reply.readInt32(); 320 uint32_t offset = reply.readInt32(); 321 322 ALOGE_IF(err, "binder=%p transaction failed fd=%d, size=%zd, err=%d (%s)", 323 IInterface::asBinder(this).get(), 324 parcel_fd, size, err, strerror(-err)); 325 326 Mutex::Autolock _l(mLock); 327 if (mHeapId.load(memory_order_relaxed) == -1) { 328 int fd = fcntl(parcel_fd, F_DUPFD_CLOEXEC, 0);//保存服务进程创建的匿名共享内存的文件描述符 329 ALOGE_IF(fd==-1, "cannot dup fd=%d, size=%zd, err=%d (%s)", 330 parcel_fd, size, err, strerror(errno)); 331 332 int access = PROT_READ; 333 if (!(flags & READ_ONLY)) { 334 access |= PROT_WRITE; 335 } 336 mRealHeap = true;//将服务进程创建的匿名共享内存映射到当前客户进程的地址空间中 337 mBase = mmap(0, size, access, MAP_SHARED, fd, offset); 338 if (mBase == MAP_FAILED) { 339 ALOGE("cannot map BpMemoryHeap (binder=%p), size=%zd, fd=%d (%s)", 340 IInterface::asBinder(this).get(), size, fd, strerror(errno)); 341 close(fd); 342 } else {//映射成功后,将匿名共享内存信息保存到BpMemoryHeap的成员变量中,供其他接口函数访问 343 mSize = size; 344 mFlags = flags; 345 mOffset = offset; 346 mHeapId.store(fd, memory_order_release); 347 } 348 } 349 } 350}
该函数首先通过Binder通信方式向服务进程请求创建匿名共享内存,当服务端BnMemoryHeap对象创建完匿名共享内存后,并将共享内存信息返回到客户进程后,客户进程通过系统调用mmap函数将匿名共享内存映射到当前进程的地址空间,这样客户进程就可以访问服务进程创建的匿名共享内存了。当了解Binder通信机制,就知道BpMemoryHeap对象通过transact函数向服务端发起请求后,服务端的BnMemoryHeap的onTransact函数会被调用。
388status_t BnMemoryHeap::onTransact( 389 uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) 390{ 391 switch(code) { 392 case HEAP_ID: { 393 CHECK_INTERFACE(IMemoryHeap, data, reply); 394 reply->writeFileDescriptor(getHeapID()); 395 reply->writeInt32(getSize()); 396 reply->writeInt32(getFlags()); 397 reply->writeInt32(getOffset()); 398 return NO_ERROR; 399 } break; 400 default: 401 return BBinder::onTransact(code, data, reply, flags); 402 } 403}
服务端的BnMemoryHeap对象将调用服务端的匿名共享内存访问接口得到创建的匿名共享内存信息,并返回给客户端进程,MemoryHeapBase是BnMemoryHeap的子类,实现了服务端的匿名共享内存访问接口。全局变量gHeapCache用来保存客户端创建的所有BpMemoryHeap对象,它的类型为HeapCache。
BpMemoryHeap通过find_heap()函数从全局变量gHeapCache中查找当前的BpMemoryHeap对象,查找过程其实是调用HeapCache类的find_heap()函数从向量mHeapCache中查找,该向量以键值对的形式保存了客户端创建的所有BpMemoryHeap对象。为什么要保存所有创建的BpMemoryHeap对象呢?虽然客户端可以创建多个不同的BpMemoryHeap对象,但他们请求的匿名共享内存在服务端确实同一块。当第一个BpMemoryHeap对象从服务端获取匿名共享内存的信息并在本进程中映射好这块匿名共享内存之后,其他的BpMemoryHeap对象就可以直接使用了,不需要再映射了。下图显示了匿名共享内存在客户端和服务端分别提供的访问接口:
通过对Android匿名共享内存C++层的数据结构分析及Binder通信的服务端和客户端分析,总结一下匿名共享内存的C++访问方式:
1)服务端构造MemoryHeapBase对象时,创建匿名共享内存,并映射到服务进程的地址空间中,同时提供获取该匿名共享内存的接口函数;2)客户端通过BpMemoryHeap对象请求服务端返回创建的匿名共享内存信息,并且将服务端创建的匿名共享内存映射到客户进程的地址空间中,在客户端也提供对应的接口函数来获取匿名共享内存的信息;
3.7 MemoryBase
MemoryBase接口是建立在MemoryHeapBase接口的基础上的,它们都可以作为一个Binder对象来在进程间进行数据共享。
MemoryBase类内部有一个类型为IMemoryHeap的强指针mHeap,它指向一个个MemoryHeapBase服务,Memory类就是通过它来描述一个匿名共享内存服务的。 MemoryBase类所维护的匿名共享内存其实只是其内部的MemoryHeapBase服务所维护的匿名共享内存的其中的一小块。
MemoryBase类的实现也分为Server端和Client端两个部分。
代码路径:/frameworks/native/libs/binder/include/binder/IMemory.h
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/include/binder/IMemory.h)
70class IMemory : public IInterface 71{ 72public: 73 DECLARE_META_INTERFACE(Memory) 74 75 virtual spgetMemory(ssize_t* offset=0, size_t* size=0) const = 0; 76 77 // helpers 78 void* fastPointer(const sp & heap, ssize_t offset) const; 79 void* pointer() const; 80 size_t size() const; 81 ssize_t offset() const; 82};
152void* IMemory::pointer() const { 153 ssize_t offset; 154 spheap = getMemory(&offset); 155 void* const base = heap!=0 ? heap->base() : MAP_FAILED; 156 if (base == MAP_FAILED) 157 return 0; 158 return static_cast<char*>(base) + offset; 159}
161size_t IMemory::size() const { 162 size_t size; 163 getMemory(NULL, &size); 164 return size; 165}
167ssize_t IMemory::offset() const { 168 ssize_t offset; 169 getMemory(&offset); 170 return offset; 171}
184spBpMemory::getMemory(ssize_t* offset, size_t* size) const 185{ 186 if (mHeap == 0) {//指向的匿名共享内存MemoryHeapBase为空 187 Parcel data, reply; 188 data.writeInterfaceToken(IMemory::getInterfaceDescriptor()); 189 if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) {//向服务端MemoryBase发起RPC请求 190 sp heap = reply.readStrongBinder();//读取匿名共享内存MemoryHeapBase的IBinder对象 191 ssize_t o = reply.readInt32();//读取匿名共享内存中的偏移量 192 size_t s = reply.readInt32();//读取匿名共享内存的大小 193 if (heap != 0) {//如果服务端返回来的用于描述整块匿名共享内存的MemoryHeapBase不为空 194 mHeap = interface_cast (heap); 195 if (mHeap != 0) {//将匿名共享内存的偏移和大小保存到成员变量中 196 size_t heapSize = mHeap->getSize(); 197 if (s <= heapSize 198 && o >= 0 199 && (static_cast (o) <= heapSize - s)) { 200 mOffset = o; 201 mSize = s; 202 } else { 203 // Hm. 204 android_errorWriteWithInfoLog(0x534e4554, 205 "26877992", -1, NULL, 0); 206 mOffset = 0; 207 mSize = 0; 208 } 209 } 210 } 211 } 212 }//将成员变量赋值给传进来的参数,从而修改参数值 213 if (offset) *offset = mOffset; 214 if (size) *size = mSize; 215 return (mSize > 0) ? mHeap : 0; 216}
228status_t BnMemory::onTransact( 229 uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) 230{ 231 switch(code) { 232 case GET_MEMORY: { 233 CHECK_INTERFACE(IMemory, data, reply);//根据客户端发送过来的接口描述进行检查确认 234 ssize_t offset; 235 size_t size;
//调用服务端的getMemory函数获取匿名共享内存对象MemoryHeapBase及匿名共享内存大小,偏移,并返回给客户端 236 reply->writeStrongBinder( IInterface::asBinder(getMemory(&offset, &size)) ); 237 reply->writeInt32(offset);//将偏移量返回给客户端 238 reply->writeInt32(size);//将匿名共享内存大小返回给客户端 239 return NO_ERROR; 240 } break; 241 default: 242 return BBinder::onTransact(code, data, reply, flags); 243 } 244}
34spMemoryBase::getMemory(ssize_t* offset, size_t* size) const 35{ 36 if (offset) *offset = mOffset; 37 if (size) *size = mSize; 38 return mHeap; 39}
四、匿名共享内存的Java访问接口
Android系统在应用程序框架层中提供了Java类MemoryFile来创建和管理匿名共享内存。使用Java类MemoryFile创建的匿名共享内存可以在不同的Android应用程序之间进行共享。
MemoryFile类提供一个构造函数来创建一块匿名共享内存。
代码路径:/frameworks/base/core/java/android/os/MemoryFile.java
(http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/java/android/os/MemoryFile.java)
69 public MemoryFile(String name, int length) throws IOException { 70 mLength = length; 71 if (length >= 0) { 72 mFD = native_open(name, length);//打开设备文件/dev/ashmem 73 } else { 74 throw new IOException("Invalid length: " + length); 75 } 76 77 if (length > 0) { 78 mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);//将匿名共享内存映射到进程的地址空间 ,并且将得到的地址保存在成员变量mAddress中 79 } else { 80 mAddress = 0; 81 } 82 }
构造函数有两个参数name和length ,分别用来描述要创建的匿名共享内存的名称和大小 。
72行首先调用JNI方法native_open 打开设备文件/dev/ashmem ,即请求Ashmem 驱动程序创建一块匿名共享内存 。
78行调用JNI方法native_mmap 将这块匿名共享内存映射到进程的地址空间 ,并且将得到的地址保存在成员变量mAddress中。
一般来说 ,Server端应用程序首先使用构造函数来创建 一块匿名共享内存 ,接着再将这块匿名共享内存的文件描述符 、大小以及访问保护位传递给Client端应用程序 。这样Server端应用程序和Client 端应用程序就可以共享同一块匿名共享内存了
MemoryFile类的成员函数getSize的实现:
257 public static int getSize(FileDescriptor fd) throws IOException { 258 return native_get_size(fd); 259 }
258行调用了一个JNI方法native_get_size来获得文件描述符fd所指向的一块匿名共享内存的大小。 如果获得的大小不是一个负数 ,那么就说明文件描述符fd指向的是一块匿名共享内存 ,这时候函数的返回值就等于true。
MemoryFile类的成员函数readBytes和writeBytes的实现:
分别用来读取和写人一块匿名共享内存的内容
187 /** 188 * Reads bytes from the memory file. 189 * Will throw an IOException if the file has been purged. 190 * 191 * @param buffer byte array to read bytes into. 192 * @param srcOffset offset into the memory file to read from. 193 * @param destOffset offset into the byte array buffer to read into. 194 * @param count number of bytes to read. 195 * @return number of bytes read. 196 * @throws IOException if the memory file has been purged or deactivated. 197 */ 198 public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count) 199 throws IOException { 200 if (isDeactivated()) { 201 throw new IOException("Can't read from deactivated memory file."); 202 } 203 if (destOffset < 0 || destOffset > buffer.length || count < 0 204 || count > buffer.length - destOffset 205 || srcOffset < 0 || srcOffset > mLength 206 || count > mLength - srcOffset) { 207 throw new IndexOutOfBoundsException(); 208 } 209 return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); 210 } 211 212 /** 213 * Write bytes to the memory file. 214 * Will throw an IOException if the file has been purged. 215 * 216 * @param buffer byte array to write bytes from. 217 * @param srcOffset offset into the byte array buffer to write from. 218 * @param destOffset offset into the memory file to write to. 219 * @param count number of bytes to write. 220 * @throws IOException if the memory file has been purged or deactivated. 221 */ 222 public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) 223 throws IOException { 224 if (isDeactivated()) { 225 throw new IOException("Can't write to deactivated memory file."); 226 } 227 if (srcOffset < 0 || srcOffset > buffer.length || count < 0 228 || count > buffer.length - srcOffset 229 || destOffset < 0 || destOffset > mLength 230 || count > mLength - destOffset) { 231 throw new IndexOutOfBoundsException(); 232 } 233 native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); 234 }
在MemorγFile类中还使用了其他jni的方法,例如native_close,native_pin等,来完成匿名共享内存的关闭,锁定和解锁等工作。
JNI方法所在文件:
代码路径:/frameworks/base/core/jni/android_os_MemoryFile.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/jni/android_os_MemoryFile.cpp)
29static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length) 30{ 31 const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL); 32 33 int result = ashmem_create_region(namestr, length);//创建一块匿名共享内存,创建成功之后 ,就会得到一个C++层的文件描述符result 34 35 if (name) 36 env->ReleaseStringUTFChars(name, namestr); 37 38 if (result < 0) { 39 jniThrowException(env, "java/io/IOException", "ashmem_create_region failed"); 40 return NULL; 41 } 42 43 return jniCreateFileDescriptor(env, result);//将result转换成一个Java层的文件描述符,并且返回给调用者。 44} 45 46static jlong android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, 47 jint length, jint prot) 48{ 49 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);//将Java层的文件描述符fileDescriptor转换成一个C++层的文件描述符fd 50 void* result = mmap(NULL, length, prot, MAP_SHARED, fd, 0);//将fd所指向的一块匿名共享内存映射到进程的地址空间 51 if (result == MAP_FAILED) { 52 jniThrowException(env, "java/io/IOException", "mmap failed"); 53 } 54 return reinterpret_cast(result);//得到的地址result返回给调用者 55} 56 57static void android_os_MemoryFile_munmap(JNIEnv* env, jobject clazz, jlong addr, jint length) 58{ 59 int result = munmap(reinterpret_cast<void *>(addr), length); 60 if (result < 0) 61 jniThrowException(env, "java/io/IOException", "munmap failed"); 62} 63 64static void android_os_MemoryFile_close(JNIEnv* env, jobject clazz, jobject fileDescriptor) 65{ 66 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); 67 if (fd >= 0) { 68 jniSetFileDescriptorOfFD(env, fileDescriptor, -1); 69 close(fd); 70 } 71} 72 73static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz, 74 jobject fileDescriptor, jlong address, jbyteArray buffer, jint srcOffset, jint destOffset, 75 jint count, jboolean unpinned) 76{ 77 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);//将Java层的文件描述符fileDescriptor转换成一个C++层的文件描述符fd 78 if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {//判断匿名共享内存已经被内存管理系统回收 79 ashmem_unpin_region(fd, 0, 0); 80 jniThrowException(env, "java/io/IOException", "ashmem region was purged"); 81 return -1; 82 } 83 84 env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);//访问匿名共享内存的内容 85 86 if (unpinned) { 87 ashmem_unpin_region(fd, 0, 0);//解锁匿名共享内存,以便在系统内存不足时,内存管理系统可以将它回收 88 } 89 return count; 90} 91 92static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz, 93 jobject fileDescriptor, jlong address, jbyteArray buffer, jint srcOffset, jint destOffset, 94 jint count, jboolean unpinned) 95{ 96 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);//将Java层的文件描述符fileDescriptor转换成一个C++层的文件描述符fd 97 if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {//判断匿名共享内存已经被内存管理系统回收 98 ashmem_unpin_region(fd, 0, 0); 99 jniThrowException(env, "java/io/IOException", "ashmem region was purged"); 100 return -1; 101 } 102 103 env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);//访问匿名共享内存的内容 104 105 if (unpinned) { 106 ashmem_unpin_region(fd, 0, 0);//解锁匿名共享内存,以便在系统内存不足时,内存管理系统可以将它回收 107 } 108 return count; 109} 110 111static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor, jboolean pin) 112{ 113 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); 114 int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0)); 115 if (result < 0) { 116 jniThrowException(env, "java/io/IOException", NULL); 117 } 118} 119
120static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz, 121 jobject fileDescriptor) { 122 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);//将Java层的文件描述符fileDescriptor转换成一个C++层的文件描述符fd 123 // Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region. 124 // ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel 125 // should return ENOTTY for all other valid file descriptors 126 int result = ashmem_get_size_region(fd);//向Ashmem驱动程序发出一个IO控制命令ASHMEM_GET_SIZE 127 if (result < 0) { 128 if (errno == ENOTTY) { 129 // ENOTTY means that the ioctl does not apply to this object, 130 // i.e., it is not an ashmem region. 131 return (jint) -1; 132 } 133 // Some other error, throw exception 134 jniThrowIOException(env, errno); 135 return (jint) -1; 136 } 137 return (jint) result; 138} 139 140static const JNINativeMethod methods[] = { 141 {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, 142 {"native_mmap", "(Ljava/io/FileDescriptor;II)J", (void*)android_os_MemoryFile_mmap}, 143 {"native_munmap", "(JI)V", (void*)android_os_MemoryFile_munmap}, 144 {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, 145 {"native_read", "(Ljava/io/FileDescriptor;J[BIIIZ)I", (void*)android_os_MemoryFile_read}, 146 {"native_write", "(Ljava/io/FileDescriptor;J[BIIIZ)V", (void*)android_os_MemoryFile_write}, 147 {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, 148 {"native_get_size", "(Ljava/io/FileDescriptor;)I", 149 (void*)android_os_MemoryFile_get_size} 150}; 151 152int register_android_os_MemoryFile(JNIEnv* env) 153{ 154 return RegisterMethodsOrDie(env, "android/os/MemoryFile", methods, NELEM(methods)); 155} 156 157}