精华内容
下载资源
问答
  • 详细描述了数据采集接口中使用共享内存的方法,以及共享内存的结构模型
  • Android 匿名共享内存C++接口分析

    千次阅读 2013-09-17 22:52:58
    l在上一篇Android 匿名共享内存C接口分析中介绍了Android系统的匿名共享内存C语言访问接口,本文在前文的基础上继续介绍Android系统的匿名共享内存提供的C++访问接口。在C++层通过引入Binder进程间通信机制可以实现...

    在上一篇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\native\libs\binder\MemoryBase.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\native\libs\binder\IMemory.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\native\libs\binder\IMemory.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来完成的。

    展开全文
  • Android 匿名共享内存C接口分析

    万次阅读 2013-09-17 09:21:09
    匿名共享内存驱动源码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口。Android系统在用户空间,C++应用程序框架层...

    Android 匿名共享内存驱动源码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口。Android系统在用户空间,C++应用程序框架层,Java层分别提供了访问接口


    本文首先介绍匿名共享内存在用户空间提供的C语言接口,在后续文章中在介绍Android匿名共享内存的C++及Java接口,从而全面理解并掌握Android匿名共享内存的使用。

    1)匿名共享内存的创建

    system\core\libcutils\ashmem-dev.c
    int ashmem_create_region(const char *name, size_t size)
    {
    	int fd, ret;
        //打开"/dev/ashmem"设备文件
    	fd = open(ASHMEM_DEVICE, O_RDWR);
    	if (fd < 0)
    		return fd;
        //根据Java空间传过来的名称修改设备文件名
    	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;
    }
    ASHMEM_DEVICE的宏定义如下:
    #define ASHMEM_DEVICE	"/dev/ashmem"
    函数ashmem_create_region首先通过open函数进入匿名共享内存驱动打开/dev/ashmem设备文件,打开过程在 Android 匿名共享内存驱动源码分析中已经详细分析了,就是在匿名共享内存初始化过程创建的slab缓冲区ashmem_area_cachep中创建并初始化一个ashmem_area结构体了,接着通过IO命令来修改该ashmem_area结构体的成员name和size,具体设置过程请查看 Android 匿名共享内存驱动源码分析。匿名共享内存的创建过程可以归纳为以下三个步骤:
    1. 打开/dev/ashmem设备文件;
    2. 修改匿名共享内存名称
    3. 修改匿名共享内存大小

    2)设置匿名共享内存属性

    通过Ioctl命令控制系统调用进入内核空间的匿名共享内存驱动来设置匿名共享内存块的属性值,比如设置匿名共享内存块的锁定与解锁,设置匿名共享内存块的大小,名称,保护位等属性信息。Android对匿名共享内存的这些属性访问也提供了相应的C语言接口:

    1. 设置匿名共享内存的保护位

    int ashmem_set_prot_region(int fd, int prot)
    {
    	return ioctl(fd, ASHMEM_SET_PROT_MASK, prot);
    }

    2.锁定匿名共享内存块
    int ashmem_pin_region(int fd, size_t offset, size_t len)
    {
    	struct ashmem_pin pin = { offset, len };
    	return ioctl(fd, ASHMEM_PIN, &pin);
    }

    3.解锁指定匿名共享内存块
    int ashmem_unpin_region(int fd, size_t offset, size_t len)
    {
    	struct ashmem_pin pin = { offset, len };
    	return ioctl(fd, ASHMEM_UNPIN, &pin);
    }

    4.获取创建的匿名共享内存大小
    int ashmem_get_size_region(int fd)
    {
      return ioctl(fd, ASHMEM_GET_SIZE, NULL);
    }
    
    无论是匿名共享内存的属性设置还是获取,都是直接使用ioctl系统调用进入匿名共享内存驱动中实现的,关于匿名共享内存驱动是如何实现这些功能的,在 Android 匿名共享内存驱动源码分析中有详细的介绍,这里就不重复介绍了。这里我们知道Android提供的匿名共享内存C语言接口比较简单。了解了匿名共享内存的C语言接口之后也为以后学习匿名共享内存的C++接口提供基础。
    展开全文
  • linux共享内存

    2013-11-18 19:54:06
    共享内存接口函数轻解1共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区。在/proc/sys/kernel/目录下,记录着共享内存的一些限制,如一 个共享内存区的最大字节数shmmax,系统范围内最大共享内存区...
  • Android 匿名共享内存Java接口分析

    千次阅读 2013-09-18 15:52:51
    在Android匿名共享内存驱动源码分析中介绍了匿名共享内存的驱动实现过程,本文在Android匿名共享内存驱动基础上,介绍Android匿名共享内存对外Android系统的匿名共享内存子系统的主体是以驱动程序的形式实现在内核...

    Android 匿名共享内存驱动源码分析中介绍了匿名共享内存的驱动实现过程,本文在Android匿名共享内存驱动基础上,介绍Android匿名共享内存对外Android系统的匿名共享内存子系统的主体是以驱动程序的形式实现在内核空间的,同时在应用程序框架层提供了Java调用接口。在Android应用程序框架层,提供了一个MemoryFile接口来封装了匿名共享内存文件的创建和使用,它实现在frameworks/base/core/java/android/os/MemoryFile.java

    public MemoryFile(String name, int length) throws IOException {
    	mLength = length;
    	//打开"/dev/ashmem"设备文件
    	mFD = native_open(name, length);
    	if (length > 0) {
    		//将打开的"/dev/ashmem"设备文件映射到进程虚拟地址空间中
    		mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
    	} else {
    		mAddress = 0;
    	}
    }
    native_open函数是一个本地函数,通过JNI实现在C++层,代码位于frameworks\base\core\jni\android_os_MemoryFile.cpp 
    static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)
    {
    	//字符串转换
        const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
        //打开设备文件"/dev/ashmem",并修改设备文件名称及共享内存大小
        int result = ashmem_create_region(namestr, length);
    
        if (name)
            env->ReleaseStringUTFChars(name, namestr);
    
        if (result < 0) {
            jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");
            return NULL;
        }
    	//设备文件句柄转换
        return jniCreateFileDescriptor(env, result);
    }

    函数首先将Java层传过来的你们共享内存名称转换为C++层的字符串,然后调用ashmem_create_region函数创建一个名为dev/ashmem/的匿名共享内存,并且修改该共享内存的名称及大小,然后将创建的匿名共享内存设备文件句柄值返回到Java空间中。函数ashmem_create_region在Android 匿名共享内存C接口分析中有详细分析,该接口函数就是用于创建一块匿名共享内存。

    在Java空间构造MemoryFile对象时,首先打开/dev/ashmem设备文件并在内核空间创建一个ashmem_area,接着需要将内核空间分配的共享内存地址映射到进程虚拟地址空间中来,映射过程是通过native_mmap函数来完成的。
    static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
            jint length, jint prot)
    {
        int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
        jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);
        if (!result)
            jniThrowException(env, "java/io/IOException", "mmap failed");
        return result;
    }
    该函数直接调用mmap来实现地址空间映射,注意标志位MAP_SHARED,表示该缓冲区以共享方式映射。映射过程是由Ashmem驱动来完成, Android 匿名共享内存驱动源码分析详细分析了Android匿名共享内存的实现过程。在构造MemoryFile对象时完成了匿名共享内存的创建及地址空间的映射过程,将创建的匿名共享内存的大小保存到MemoryFile的成员变量mLength中,成员变量mFD保存创建的匿名共享内存的文件描述符,成员变量mAddress保存匿名共享内存映射到进程地址空间的起始地址。有了这些信息后,就可以直接使用该匿名共享内存了。

    匿名共享内存读

    对匿名共享内存的读取操作,在Java空间被封装成MemoryInputStream来完成,该类继承于输入流InputStream,并对外提供了read方法,定义如下:
    @Override
    public int read() throws IOException {
    	if (mSingleByte == null) {
    		mSingleByte = new byte[1];
    	}
    	int result = read(mSingleByte, 0, 1);
    	if (result != 1) {
    		return -1;
    	}
    	return mSingleByte[0];
    }
    
    @Override
    public int read(byte buffer[], int offset, int count) throws IOException {
    	if (offset < 0 || count < 0 || offset + count > buffer.length) {
    		// readBytes() also does this check, but we need to do it before
    		// changing count.
    		throw new IndexOutOfBoundsException();
    	}
    	count = Math.min(count, available());
    	if (count < 1) {
    		return -1;
    	}
    	int result = readBytes(buffer, mOffset, offset, count);
    	if (result > 0) {
    		mOffset += result;
    	}
    	return result;
    }
    MemoryInputStream类提供了两个read重载方法,第一个无参read方法调用有参read方法来读取1字节的数据,而有参read方法的数据读取过程是调用MemoryInputStream的外部类MemoryFile的readBytes方法来实现匿名共享内存数据的读取过程。
    public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
    		throws IOException {
    	if (isDeactivated()) {
    		throw new IOException("Can't read from deactivated memory file.");
    	}
    	if (destOffset < 0 || destOffset > buffer.length || count < 0
    			|| count > buffer.length - destOffset
    			|| srcOffset < 0 || srcOffset > mLength
    			|| count > mLength - srcOffset) {
    		throw new IndexOutOfBoundsException();
    	}
    	return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
    }
    该函数也仅仅作了一些判断,然后直接调用本地方法native_read在C++空间完成数据读取,在构造MemoryFile对象时,已经打开并映射了dev/ashmem设备文件,因此在这里直接将打开该设备文件得到的文件句柄值传到C++空间,以正确读取指定的匿名共享内存中的内容,mAddress为匿名共享内存映射到进程地址空间中的起始地址。

    static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz,
            jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
            jint count, jboolean unpinned)
    {
        int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
        if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
            ashmem_unpin_region(fd, 0, 0);
            jniThrowException(env, "java/io/IOException", "ashmem region was purged");
            return -1;
        }
    
        env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);
    
        if (unpinned) {
            ashmem_unpin_region(fd, 0, 0);
        }
        return count;
    }


    匿名共享内存写

    将指定数据写入到匿名共享内存中,对匿名共享内存的写操作使用MemoryOutputStream来封装,该类提供了两个重载的write方法,一个用于向匿名共享内存写入多字节数据,另一个则只写入一个字节数据。这里简单介绍多字节数据写入过程:
    public void write(byte buffer[], int offset, int count) throws IOException {
    	writeBytes(buffer, offset, mOffset, count);
    	mOffset += count;
    }
    

    参数buffer是指写入匿名共享内存中的字节数组,offset指定数据buffer开始写的偏移量,参数count指定写入匿名共享内存的字节长度,函数调用MemoryFile的writeBytes函数来完成数据写入。

    public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
    		throws IOException {
    	if (isDeactivated()) {
    		throw new IOException("Can't write to deactivated memory file.");
    	}
    	if (srcOffset < 0 || srcOffset > buffer.length || count < 0
    			|| count > buffer.length - srcOffset
    			|| destOffset < 0 || destOffset > mLength
    			|| count > mLength - destOffset) {
    		throw new IndexOutOfBoundsException();
    	}
    	native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
    }
    
    该函数首先检验参数的正确性,然后调用native方法native_write通过JNI转入C++完成数据写入,第一个参数是匿名共享内存的文件描述符,第二个参数是匿名共享内存映射到进程地址空间的基地值,后面三个参数上面已经介绍了,最后一个参数mAllowPurging表示是否允许内存回收

    上图描述了匿名共享内存的写入过程,本质上就是将buffer中指定位置开始的数据拷贝到匿名共享内存指定的偏移位置

    frameworks\base\core\jni\android_os_MemoryFile.cpp

    static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz,
            jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
            jint count, jboolean unpinned)
    {
        int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
        if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
            ashmem_unpin_region(fd, 0, 0);
            jniThrowException(env, "java/io/IOException", "ashmem region was purged");
            return -1;
        }
        env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);
        if (unpinned) {
            ashmem_unpin_region(fd, 0, 0);
        }
        return count;
    }
    
    数据写入过程是通过JNI函数GetByteArrayRegion完成数据的拷贝操作。


    展开全文
  • Linux共享内存共享内存API

    千次阅读 2018-01-23 16:31:45
    共享内存区是最快的IPC(进程间通信)形式。 用共享内存从服务器拷贝文件数据到客户端: 共享内存基本API: #include #include 1. int shmget(key_t key,size_t size,int shmflg); 功能:用来创建共享...

    共享内存区是最快的IPC(进程间通信)形式。

    用共享内存从服务器拷贝文件数据到客户端:


    共享内存基本API:

    #include<sys/ipc.h>

    #include<sys/shm.h>

    1.     int shmget(key_t key,size_t size,int shmflg);

    功能:用来创建共享内存

    key:是这个共享内存段的名字
    size:共享内存的大小
    shmflg:相当于权限位(如0666)
    返回值是共享内存段的标识码shmid,
    例如:shmid = shmget(0x1111, 128, 0666);
         //创建共享内存 , 相当于打开打开文件 
     //若共享内存存在 则使用   fopen()
     //若共享内存 不存在 则报错 -1

    shmid = shmget(0x1111, 128, 0666 | IPC_CREAT);
          //创建共享内存 , 相当于打开打开文件 
       //若共享内存存在 则使用   fopen()
      //若共享内存 不存在 则创建 


    shmid = shmget(0x1111, 128, 0666 | IPC_CREAT | IPC_EXCL);
     //创建共享内存 , 相当于打开文件 
    //若共享内存存在 则报错
    //若共享内存 不存在 则创建 
    //作用 IPC_EXCL判断存在不存在的标志  避免已经存在的文件 被覆盖

    2.  void *shmat(int shmid, const void *shmaddr, int shmflg);0xaa11

    功能:将共享内存段连接到进程地址空间

    shmaddr:指定连接的地址,因为内存地址是段页式管理,所以有可能传入的地址并不就是那一页的开头位置,所以传入一个地址,传出的仍然是一个地址,传出的是具体开始存储的地址。所以我们通常传入NULL,让编译器直接分配个合适的位置给我们。

    shmflg:它的两个取值可能是SHM_RND和SHM_RDONLY.

    例: void *p = shmat(shmid, NULL, 0);

    返回值:成功返回一个指针,指向共享内存第一个节,失败返回-1;


    3, int shmdt(const void *shmaddr);

    功能:将共享内存段与当前进程脱离,但并不等于删除共享内存段


    4,  int shmctl(int shmid,int cmd,struct shmid_ds *buf);

    功能:用于控制共享内存

    cmd:将要采取的动作

    1,IPC_STAT  把shmid_ds结构中的数据设置为共享内存的当前关联值

    2,IPC_SET    在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值

    3,IPC_RMID  删除共享内存段

    buf: 指向一个保存着共享内存的模式状态和访问权限的数据结构

       例: shmctl(shmid, IPC_RMID, NULL);

       //删除共享内存

    若想要把旧的共享内存里面的内容保存下来,则传入一个地址,用来完成保存的功能


    为什么链接共享内存时要设计shmid,创建时要传入key:

    共享内存私有:





    Linux内核通过引用计数技术来管理共享内存生命周期







    展开全文
  • 对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不...
  • 共享内存使用的基本思路和接口

    千次阅读 2013-12-02 09:33:31
    共享内存的基本使用方法和接口介绍
  • 共享内存sample

    2014-11-23 23:54:08
    一个简单的实现多线程共享内存接口,任意进程调用该接口都可以往主进程写值
  • 共享内存

    千次阅读 2017-06-17 20:21:55
    什么是共享内存共享内存顾名思义就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程...
  • android8.1和之前的版本有一个很大的不同,那就是8.1以后的版本将hal层和framework... 之前我这边有个模块,在android6.0上时,需要在hal层通过ashmem_create_region来创建一块共享内存。然后将创建的共享内存句柄,...
  • linux内核支持多种共享内存方式,如mmap内存映射,Posix共享内存,以system V共享内存。当内核空间和用户空间存在大量数据交互时,共享内存映射就成了这种情况下的不二选择。它能够最大限度的降低内核空间和用户空间...
  • nginx共享内存共享内存的实现

    千次阅读 2014-07-15 23:56:30
    nginx中, 作者为我们提供了方便共享内存的使用的接口,关于共享内存的使用在我之前的文章中有介绍。这次我们来研究一下nginx是如何实现的。 我们知道,如果我们的模块中要使用一个共享内存,需要调用ngx_shared_...
  • 在Android系统中,针对移动...为了方便使用匿名共享内存机制,系统还提供了Java调用接口(MemoryFile)和C++调用接口(MemoryHeapBase、MemoryBase),Java接口在前面也已经分析过了,本文中将继续分析它的C++接口
  • 共享内存及其用mmap实现共享内存

    千次阅读 2016-08-01 00:32:49
    一、什么是共享内存顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。...
  • 【需求描述】 1、共享内存保存信息 2、提供接口写入共享内存 3、提供接口获取共享内存 【编写语言:C】 【环境:linux】 1、写入内存一千万条数据 耗时:5.356秒 2、读取内存一千万条数据 耗时:1.449秒
  • 共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU...
  • 共享内存实现原理

    万次阅读 2018-09-28 18:02:39
    共享内存的使用实现原理(必考必问,然后共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?) nmap函数要求内核创建一个新额虚拟存储器区域,最好是从地质start开始的一个...
  • 本资源,利用hidl接口规范,写了一个共享内存的服务,简单明了,用到了IAllocator、native_handle_t等等等标准方法
  • 实现内存共享,提供创建和获得的共享内存接口,简单实用
  • 进程间通信——共享内存(Shared Memory)

    万次阅读 多人点赞 2018-04-16 16:19:17
    共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存...
  • Linux进程间通信——使用共享内存

    万次阅读 多人点赞 2013-08-24 10:26:39
    下面将讲解进程间通信的另一种方式,使用共享内存。 一、什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式...
  • 共享单车、共享充电宝、共享雨伞,世间的共享有千万种,而我独爱共享内存。 早期的共享内存,着重于强调把同一片内存,map到多个进程的虚拟地址空间(在相应进程找到一个VMA区域),以便于CPU可以在各个进程访问到...
  • Linux 修改共享内存限制

    千次阅读 2019-03-23 20:13:42
    最大的共享内存段设置可以用sysctl接口设置。 比如,要允许 128 MB,并且最大的总共享内存数为 2097152 页(缺省): sysctl -w kernel.shmmax=134217728 sysctl -w kernel.shmall=2097152 你...
  • linux c 共享内存实现读写操作

    千次阅读 2018-05-17 16:55:26
    linux c 共享内存实现读写操作【需求描述】1、共享内存保存信息2、提供接口写入共享内存3、提供接口获取共享内存【编写语言:C】【环境:linux】1、写入内存一千万条数据 耗时:5.356秒2、读取内存一千万条数据 耗时...
  • 物理内存的划分有两种方法:一种就是不关心物理内存的地址,直接调用shmget接口创建共享内存,shmat接口映射共享内存到进程的虚拟地址空间。另一种就是通过mmap将/dev/mem映射出来。 IPC共享内存 共享内存机制含义:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 387,180
精华内容 154,872
关键字:

共享内存接口