• Android 匿名共享内存C++接口分析


    在上一篇Android 匿名共享内存C接口分析中介绍了Android系统的匿名共享内存C语言访问接口,本文在前文的基础上继续介绍Android系统的匿名共享内存提供的C++访问接口。在C++层通过引入Binder进程间通信机制可以实现跨进程访问匿名共享内存。我们知道Android匿名共享内存的设计本身就是为了实现进程间共享大量数据,当源进程开辟一块匿名共享内存并把这块匿名共享内存映射到当前进程的虚拟地址空间从而使当前进程可以直接访问这块匿名共享内存后,如何让目标进程共享访问这块匿名共享内存呢?这就需要利用Binder进程间通信方式将匿名共享内存的文件描述符传输给目标进程,目标进程才能将得到的匿名共享内存文件描述符映射到本进程地址空间中,只有这样目标进程才能访问源进程创建的这块匿名共享内存。将Binder进程间通信方式与匿名共享内存相结合实现小数据量传输换来大数据量共享。


    由于这里用到了Binder进程间通信机制,这里再次贴上Android系统的Binder通信设计框架,关于Binder通信的具体分析,请查看Binder通信模块中的一系列文章。


    MemoryHeapBase

    Android使用MemoryHeapBase接口来实现进程间共享一个完整的匿名共享内存块,通过MemoryBase接口来实现进程间共享一个匿名共享内存块中的其中一部分,MemoryBase接口是建立在MemoryHeapBase接口的基础上面的,都可以作为一个Binder对象来在进程间传输。首先介绍MemoryHeapBase如何实现一个完整匿名共享内存块的共享,下面是MemoryHeapBase在Binder通信框架中的类关系图:


    既然Android的匿名共享内存涉及到了Binder通信机制,那就必定包括客户端和服务端两种实现方式。图中黄色标识的类及MemoryHeapBase是匿名共享内存在服务进程中的实现类,而粉红色标识的类则是匿名共享内存在Client进程中的引用类。IMemoryHeapding接口定义了匿名共享内存访问接口;BpMemoryHeap定义了匿名共享内存在客户进程中的业务逻辑,BpInterface,BpRefBase,BpBinder则是为业务逻辑层的BpMemoryHeap提供进程间通信支持,ProcessState和IPCThreadState是Binder进程和线程的抽象操作类,为BpBinder和Binder驱动交互提供辅助接口。黄色标识类BBinder,BnInterface是服务进程中用于支持Binder通信实现类,BnMemoryHeap用于分发关于匿名共享内存请求命令,而MemoryHeapBase实现了匿名共享内存在服务进程的所有业务逻辑。了解了整个匿名共享内存的框架设计后,接下来根据代码具体分析Android是如何在进程间共享匿名共享内存的。在IMemoryBase类中定义了匿名共享内存的访问接口:

    class IMemoryHeap : public IInterface
    {
    public:
        DECLARE_META_INTERFACE(MemoryHeap);
    	...
    	//用来获得匿名共享内存块的打开文件描述符
        virtual int         getHeapID() const = 0;
    	//用来获得匿名共享内存块的基地址
        virtual void*       getBase() const = 0;
    	//用来获得匿名共享内存块的大小
        virtual size_t      getSize() const = 0;
    	//用来获得匿名共享内存块的保护标识位
        virtual uint32_t    getFlags() const = 0;
    	//用来获得匿名共享内存块中的偏移量
        virtual uint32_t    getOffset() const = 0;
    	...
    };

    Server端

    MemoryHeapBase类实现了IMemoryBase提供的接口函数,位于frameworks ativelibsinderMemoryBase.cpp,MemoryHeapBase提供了四种构造函数:

    MemoryHeapBase::MemoryHeapBase()
        : mFD(-1), mSize(0), mBase(MAP_FAILED),
          mDevice(NULL), mNeedUnmap(false), mOffset(0)
    {
    }


    size表示要创建的匿名共享内存的大小,flags是用来设置这块匿名共享内存的属性的,name是用来标识这个匿名共享内存的名字,mFD表示匿名共享内存的文件描述符,mBase标识匿名共享内存的基地址,mDevice表示匿名共享内存的设备文件。这个构造函数只是简单初始化了各个成员变量。

    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);
                }
            }
        }
    }

    在以上构造函数中根据参数,利用匿名共享内存提供的C语言接口创建一块匿名共享内存,并映射到当前进程的虚拟地址空间,参数size是指定匿名共享内存的大小,flags指定匿名共享内存的访问属性,name指定匿名共享内存的名称,如果没有指定名称,默认命名为MemoryHeapBase。

    MemoryHeapBase::MemoryHeapBase(const char* device, size_t size, uint32_t flags)
        : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
          mDevice(0), mNeedUnmap(false), mOffset(0)
    {
        int open_flags = O_RDWR;
        if (flags & NO_CACHING)
            open_flags |= O_SYNC;
    	//打开匿名共享内存设备文件
        int fd = open(device, open_flags);
        ALOGE_IF(fd<0, "error opening %s: %s", device, strerror(errno));
        if (fd >= 0) {
    		//将指定的匿名共享内存大小按页对齐
            const size_t pagesize = getpagesize();
            size = ((size + pagesize-1) & ~(pagesize-1));
    		//将匿名共享内存映射到当前进程地址空间
            if (mapfd(fd, size) == NO_ERROR) {
                mDevice = device;
            }
        }
    }

    该构造函数通过指定已创建的匿名共享内存的设备文件来打开该共享内存,并映射到当前进程地址空间。device指定已经创建的匿名共享内存的设备文件路径,size指定匿名共享内存的大小。

    MemoryHeapBase::MemoryHeapBase(int fd, size_t size, uint32_t flags, uint32_t offset)
        : 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));
        mapfd(dup(fd), size, offset);
    }


    以上四种构造函数都通过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) {
    		//通过mmap系统调用进入内核空间的匿名共享内存驱动,并调用ashmem_map函数将匿名共享内存映射到当前进程
            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;
            }
            mBase = base;
            mNeedUnmap = true;
        } else  {
            mBase = 0; // not MAP_FAILED
            mNeedUnmap = false;
        }
        mFD = fd;
        mSize = size;
        mOffset = offset;
        return NO_ERROR;
    }

    mmap函数的第一个参数0表示由内核来决定这个匿名共享内存文件在进程地址空间的起始位置,第二个参数size表示要映射的匿名共享内文件的大小,第三个参数PROT_READ|PROT_WRITE表示这个匿名共享内存是可读写的,第四个参数fd指定要映射的匿名共享内存的文件描述符,第五个参数offset表示要从这个文件的哪个偏移位置开始映射。调用了这个函数之后,最后会进入到内核空间的ashmem驱动程序模块中去执行ashmem_map函数,调用mmap函数返回之后,就得这块匿名共享内存在本进程地址空间中的起始访问地址了,将这个地址保存在成员变量mBase中,最后,还将这个匿名共享内存的文件描述符和以及大小分别保存在成员变量mFD和mSize,并提供了相应接口函数来访问这些变量值。通过构造MemoryHeapBase对象就可以创建一块匿名共享内存,或者映射一块已经创建的匿名共享内存到当前进程的地址空间。

    int MemoryHeapBase::getHeapID() const {
        return mFD;
    }
    
    void* MemoryHeapBase::getBase() const {
        return mBase;
    }
    
    size_t MemoryHeapBase::getSize() const {
        return mSize;
    }
    
    uint32_t MemoryHeapBase::getFlags() const {
        return mFlags;
    }
    
    const char* MemoryHeapBase::getDevice() const {
        return mDevice;
    }
    
    uint32_t MemoryHeapBase::getOffset() const {
        return mOffset;
    }

    Client端

    前面介绍了匿名共享内存服务端的实现,那客户端如果通过Binder通信中的RPC方式访问匿名共享内存的服务端,从而得到相应的服务呢?Android提供了BpMemoryHeap类来请求服务,位于frameworks ativelibsinderIMemory.cpp
    BpMemoryHeap::BpMemoryHeap(const sp<IBinder>& impl)
        : BpInterface<IMemoryHeap>(impl),
            mHeapId(-1), mBase(MAP_FAILED), mSize(0), mFlags(0), mOffset(0), mRealHeap(false)
    {
    }
    在BpMemoryHeap的构造函数里只是初始化一些成员变量,BpMemoryHeap提供了和服务端MemoryHeapBase相对应的服务函数:
    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;
    }
    通过BpMemoryHeap代理对象访问匿名共享内存前都会调用函数assertMapped()来判断是否已经向服务获取到匿名共享内存的信息,如果没有就向服务端发起请求:
    void BpMemoryHeap::assertMapped() const
    {
        if (mHeapId == -1) {//如果还没有请求服务创建匿名共享内存
    		//将当前BpMemoryHeap对象转换为IBinder对象
            sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());
    		//从成员变量gHeapCache中查找对应的BpMemoryHeap对象
            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,表示匿名共享内存还为准备就绪,因此请求服务端MemoryHeapBase创建匿名共享内存,否则该函数不作任何处理。只有第一次使用匿名共享时才会请求服务端创建匿名共享内存。由于在客户端进程中使用同一个BpBinder代理对象可以创建多个与匿名共享内存业务相关的BpMemoryHeap对象,因此定义了类型为HeapCache的全局变量gHeapCache用来保存创建的所有BpMemoryHeap对象,assertMapped函数首先将当前BpMemoryHeap对象强制转换为IBinder类型对象,然后调用find_heap()函数从全局变量gHeapCache中查找出对应的BpMemoryHeap对象,并调用assertReallyMapped()函数向服务进程的BnemoryHeap请求创建匿名共享内存。
    void BpMemoryHeap::assertReallyMapped() const
    {
        if (mHeapId == -1) {//再次判断是否已经请求创建过匿名共享内存
            Parcel data, reply;
            data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());
    		//向服务端BnMemoryHeap发起请求
            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();
    		//保存服务进程创建的匿名共享内存的文件描述符
            int fd = dup( parcel_fd );
            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 {//映射成功后,将匿名共享内存信息保存到BpMemoryHeap的成员变量中,供其他接口函数访问
                    mSize = size;
                    mFlags = flags;
                    mOffset = offset;
                    android_atomic_write(fd, &mHeapId);
                }
            }
        }
    }
    该函数首先通过Binder通信方式向服务进程请求创建匿名共享内存,当服务端BnMemoryHeap对象创建完匿名共享内存后,并将共享内存信息返回到客户进程后,客户进程通过系统调用mmap函数将匿名共享内存映射到当前进程的地址空间,这样客户进程就可以访问服务进程创建的匿名共享内存了。当了解Binder通信机制,就知道BpMemoryHeap对象通过transact函数向服务端发起请求后,服务端的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);
        }
    }
    服务端的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对象请求服务端返回创建的匿名共享内存信息,并且将服务端创建的匿名共享内存映射到客户进程的地址空间中,在客户端也提供对应的接口函数来获取匿名共享内存的信息;

    MemoryBase

    MemoryBase接口是建立在MemoryHeapBase接口的基础上的,它们都可以作为一个Binder对象来在进程间进行数据共享

    和MemoryHeapBase类型,首先定义了IMemory,同时定义了客户端的BpMemory代理类,服务端的BnMemory及其子类MemoryBase,熟悉Binder进程间通信框架就应该很请求各个类之间的关系,这里不在介绍Binder通信层的相关类,而是直接介绍在通信层上面的业务逻辑层的相关类。IMemory类定义了MemoryBase类所需要实现的接口,这个类定义在frameworks/base/include/binder/IMemory.h
    class IMemory : public IInterface
    {
    public:
        DECLARE_META_INTERFACE(Memory);
        virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const = 0;
        // helpers
        void* fastPointer(const sp<IBinder>& heap, ssize_t offset) const;
        void* pointer() const;
        size_t size() const;
        ssize_t offset() const;
    };
    在IMemory类中实现了大部分接口函数,只有getMemory函数是在IMemory的子类MemoryBase中实现的。成员函数getMemory用来获取内部的MemoryHeapBase对象的IMemoryHeap接口。
    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()用来获取内部所维护的匿名共享内存的基地址
    size_t IMemory::size() const {
        size_t size;
        getMemory(NULL, &size);
        return size;
    }
    函数size()用来获取内部所维护的匿名共享内存的大小
    ssize_t IMemory::offset() const {
        ssize_t offset;
        getMemory(&offset);
        return offset;
    }
    函数offset()用来获取内部所维护的这部分匿名共享内存在整个匿名共享内存中的偏移量
    由于这三个接口函数都在IMemory类中实现了,因此无论是在Binder进程间通信的客户端的BpMemory还是服务端的MemoryBase类中都不在实现这三个接口函数,从以上三个接口函数的实现可以看出,它们都是调用getMemory函数来获取匿名共享内存的基地址,大小及偏移量,那getMemory函数在客户端和服务端的实现有何区别呢,下面分别介绍客户端BpMemory和服务端MemoryBase中getMemory函数的实现过程。

    客户端

    在客户端的BpMemory类中,通过Binder进程间通信机制向服务端MemoryBase发起请求:
    frameworks ativelibsinderIMemory.cpp
    sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const
    {
        if (mHeap == 0) {//指向的匿名共享内存MemoryHeapBase为空
            Parcel data, reply;
            data.writeInterfaceToken(IMemory::getInterfaceDescriptor());
    		//向服务端MemoryBase发起RPC请求
            if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) {
    			//读取服务端返回来的结果
                sp<IBinder> heap = reply.readStrongBinder();//读取匿名共享内存MemoryHeapBase的IBinder对象
                ssize_t o = reply.readInt32();//读取匿名共享内存中的偏移量
                size_t s = reply.readInt32();//读取匿名共享内存的大小
    			//如果服务端返回来的用于描述整块匿名共享内存的MemoryHeapBase不为空
                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;
    }

    服务端

    当客户端的BpMemory向服务端MemoryBase发起RPC请求后,服务端的BnMemory对象的onTransact函数被调用
    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;
    	    //调用服务端的getMemory函数获取匿名共享内存对象MemoryHeapBase及匿名共享内存大小,偏移,并返回给客户端
                reply->writeStrongBinder(getMemory(&offset, &size)->asBinder() );
    	    //将偏移量返回给客户端
                reply->writeInt32(offset);
    	    //将匿名共享内存大小返回给客户端
                reply->writeInt32(size);
                return NO_ERROR;
            } break;
            default:
                return BBinder::onTransact(code, data, reply, flags);
        }
    }
    服务端的getMemory函数由BnMemory的子类MemoryBase实现
    sp<IMemoryHeap> MemoryBase::getMemory(ssize_t* offset, size_t* size) const
    {
        if (offset) *offset = mOffset;
        if (size)   *size = mSize;
        return mHeap;
    }
    这里只是将成员变量的值赋给传进来的参数,从而修改参数值,由于参数类型为指针类型,因此形参的改变必然改变实参。那服务端MemoryBase的成员变量mSize,mOffset,mHeap是在什么时候赋值的呢?MemoryBase的构造函数如下:
    MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap,
            ssize_t offset, size_t size)
        : mSize(size), mOffset(offset), mHeap(heap)
    {
    }
    从MemoryBase的构造函数可以看出,它的成员变量值都是在构造MemoryBase对象是被初始化的。参数heap指向的是一个MemoryHeapBase对象,真正的匿名共享内存就是由它来维护的,参数offset表示这个MemoryBase对象所要维护的这部分匿名共享内存在整个匿名共享内存块中的起始位置,参数size表示这个MemoryBase对象所要维护的这部分匿名共享内存的大小。MemoryBase用于共享匿名共享内存中的部分内存,在构造MemoryBase对象是指定共享的整块匿名共享内存为mHeap,大小为mSize,被共享的内存在整块匿名共享内存中的偏移量为mOffset。客户端通过Binder进程间通信方式获取用于描述部分共享内存的MemoryBase对象信息,MemoryBase只是用来维护部分匿名共享内存,而匿名共享内存的创建依然是通过MemoryHeapBase来完成的。

  • 相关阅读:
    CF982E Billiard
    CF385E Bear in the Field
    CF1322C Instant Noodles
    CF359C Prime Number
    CF1117E Decypher the String
    模板整合计划 写了一点qwq慢慢补
    BZOJ 4320
    acwing91:最短哈密顿路径
    P3469 [POI2008]BLO-Blockade
    简易平衡树
  • 原文地址:https://www.cnblogs.com/suncoolcat/p/3329082.html
Copyright © 2020-2023  润新知