Android8.0.0-r4的内存共享机制

一、匿名共享内存的共享原理

      在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 BpInterface<IMemoryHeap>
81{
....................................
112
113    mutable std::atomic<int32_t> 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<IBinder>& impl)
250    : BpInterface<IMemoryHeap>(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        sp<IBinder> binder(IInterface::asBinder(const_cast<BpMemoryHeap*>(this)));//将当前BpMemoryHeap对象转换为IBinder对象
285        sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(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端两个部分。

和MemoryHeapBase类型,首先定义了IMemory,同时定义了客户端的BpMemory代理类,服务端的BnMemory及其子类MemoryBase,熟悉Binder进程间通信框架就应该很请求各个类之间的关系,这里不在介绍Binder通信层的相关类,而是直接介绍在通信层上面的业务逻辑层的相关类。IMemory类定义了MemoryBase类所需要实现的接口。

代码路径:/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 sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const = 0;
76
77    // helpers
78    void* fastPointer(const sp<IBinder>& heap, ssize_t offset) const;
79    void* pointer() const;
80    size_t size() const;
81    ssize_t offset() const;
82};

在IMemory类中实现了大部分接口函数,只有getMemory函数是在IMemory的子类MemoryBase中实现的。成员函数getMemory用来获取内部的MemoryHeapBase对象的IMemoryHeap接口

函数pointer()用来获取内部所维护的匿名共享内存的基地址
 代码路径:/frameworks/native/libs/binder/IMemory.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/IMemory.cpp)
152void* IMemory::pointer() const {
153    ssize_t offset;
154    sp<IMemoryHeap> heap = 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}

函数size()用来获取内部所维护的匿名共享内存的大小 
代码路径:/frameworks/native/libs/binder/IMemory.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/IMemory.cpp)
函数offset()用来获取内部所维护的这部分匿名共享内存在整个匿名共享内存中的偏移量
代码路径: /frameworks/native/libs/binder/IMemory.cpp
(http://androidxref.com/8.0.0_r4/xref /frameworks/native/libs/binder/IMemory.cpp )
167ssize_t IMemory::offset() const {
168    ssize_t offset;
169    getMemory(&offset);
170    return offset;
171}
由于这三个接口函数都在IMemory类中实现了,因此无论是在Binder进程间通信的客户端的BpMemory还是服务端的MemoryBase类中都不在实现这三个接口函数,从以上三个接口函数的实现可以看出,它们都是调用getMemory函数来获取匿名共享内存的基地址,大小及偏移量,那getMemory函数在客户端和服务端的实现有何区别呢,下面分别介绍客户端BpMemory和服务端MemoryBase中getMemory函数的实现过程。


3.7.1 客户端

在客户端的BpMemory类中,通过Binder进程间通信机制向服务端MemoryBase发起请求:
代码路径:/frameworks/native/libs/binder/IMemory.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/IMemory.cpp)
184sp<IMemoryHeap> BpMemory::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<IBinder> 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<IMemoryHeap>(heap);
195                if (mHeap != 0) {//将匿名共享内存的偏移和大小保存到成员变量中
196                    size_t heapSize = mHeap->getSize();
197                    if (s <= heapSize
198                            && o >= 0
199                            && (static_cast<size_t>(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}

3.7.2 服务端
当客户端的BpMemory向服务端MemoryBase发起RPC请求后,服务端的BnMemory对象的onTransact函数被调用
代码路径:/frameworks/native/libs/binder/IMemory.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/IMemory.cpp)
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}

服务端的getMemory函数由BnMemory的子类MemoryBase实现
代码路径:/frameworks/native/libs/binder/MemoryBase.cpp
(http://androidxref.com/8.0.0_r4/xref/frameworks/native/libs/binder/MemoryBase.cpp)
成员变量的值赋给传进来的参数,从而修改参数值,由于参数类型为指针类型,因此形参的改变必然改变实参。

从MemoryBase的构造函数可以看出,它的成员变量值都是在构造MemoryBase对象是被初始化的。参数heap指向的是一个MemoryHeapBase对象,真正的匿名共享内存就是由它来维护的,参数offset表示这个MemoryBase对象所要维护的这部分匿名共享内存在整个匿名共享内存块中的起始位置,参数size表示这个MemoryBase对象所要维护的这部分匿名共享内存的大小。MemoryBase用于共享匿名共享内存中的部分内存,在构造MemoryBase对象是指定共享的整块匿名共享内存为mHeap,大小为mSize,被共享的内存在整块匿名共享内存中的偏移量为mOffset。客户端通过Binder进程间通信方式获取用于描述部分共享内存的MemoryBase对象信息,MemoryBase只是用来维护部分匿名共享内存,而匿名共享内存的创建依然是通过MemoryHeapBase来完成的。


四、匿名共享内存的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<jlong>(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}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值