Android IMemory原理及使用

1. Android内存共享-Ashmem

Ashmem(anonymous shared memory)是Android中的匿名共享内存机制,
可以从内核级别提供两个进程之间的数据共享。

1. Ashmem API

声明: /system/core/include/cutils/ashmem.h
定义实现: /system/core/libcutils/ashmem-dev.c

1. 创建共享区域- ashmem_create_region

原型:

/*
	ashmem属于misc设备的一个伪文件,虽然是匿名共享内存, 但是也可以指定一个名字
返回值:
	返回代表匿名共享内存的文件描述符, 如果返回0则代表出错了。
	
*/
int ashmem_create_region(
	const char *name, //共享内存的名字(可选, 传NULL也可以)
	size_t size // 要申请的共享内存的大小
)

2. 设置匿名共享内存的保护位- ashmem_set_prot_region

比如: 设置访问(读写)权限等

原型

/*
参数说明:
	prot : 要设置的权限保护位
		- PROT_EXEC: 可执行权限
		- PROT_READ :可读权限
		- PROT_WRITE :可写权限
		- PROT_SEM: 可用于原子操作
		- PROT_GROWSDOWN ??
		- PROT_GROWSUP: ??
		- PROT_NONE : 不给权限
*/
int ashmem_set_prot_region(
	int fd, // ashmem的代表fd(ashmem_create_region的返回值)
	int prot // 要设置的权限保护位
)

3. 获取创建的匿名共享内存大小- ashmem_get_size_region

原型:

/*
	返回值:
		<0: 异常, 并设置errno
		>0: 返回内存大小, 同时可以确定该fd确实引用到了该ashmem区域.
*/
int ashmem_get_size_region(
	int fd //代表ashmem内存的文件描述符(ashmem_create_region的返回值)
)

4. 锁定匿名共享内存块 - ashmem_pin_region

原型:

/*
	标记那些内存正在使用, 共享内存在创建之初都是pinned状态的.
	并且只有unpinned(调用unpin)状态的内存段才可以被pin.
*/
int ashmem_pin_region(
	int fd, //代表ashmem内存的文件描述符(ashmem_create_region的返回值)
	size_t offset, //锁定开始位置的偏移量
	size_t len //要锁定的的区域长度(从offset开始)
)

在这里插入图片描述

5. 解锁指定匿名共享内存块- ashmem_unpin_region

原型:

/*
	标记那些内存不再使用了, 只有pinned状态的内存段才可以被unpin
*/
int ashmem_unpin_region(
	int fd, //代表ashmem内存的文件描述符(ashmem_create_region的返回值)
	size_t offset, //需要解锁的内存段的开始位置的偏移量
	size_t len //要解锁的的区域长度(从offset开始)
)

在这里插入图片描述
Ashmem只是用来开辟一块共享内存空间, 如果要实现进程间共享, 还需要使用借助mmap来将申请的ashmem区域映射到进程中。

注:图片来自文章 Android匿名共享内存(Ashmem)原理


2. MemoryHeapBase 和 MemoryBase

继承关系概览:
在这里插入图片描述
概述:

  1. MemoryBase:算是MemoryHeapBase的代理类, 提供基于binder机制的进程间通信。
  2. MemoryHeapBase: 是基于Ashmem机制封装的内存共享接口。
  3. 服务端(Bp)通过MemoryHeapBase创建共享内存,使用MemoryBase包装, 通过Binder传递到客户端(Bp)。
  4. 客户端使用IMemory接收,在Bp侧使用BpMemoryHeap接收,并使用BpMemory持有。
  5. 在Bp和Bn侧都通过IMemoryHeap访问内存。

1. MemoryHeapBase

基于Ashmem机制封装的内存共享接口。
用于封装对ashmem的内存申请和映射功能。

/frameworks/native/include/binder/MemoryHeapBase.h

//MemoryHeapBase 是继承自BnMemoryHeap的, 所以可以说是属于binder通信的服务端角色。
class MemoryHeapBase : public virtual BnMemoryHeap
{
public:
    enum {
        READ_ONLY = IMemoryHeap::READ_ONLY,
        // memory won't be mapped locally, but will be mapped in the remote
        // process.
        DONT_MAP_LOCALLY = 0x00000100,
        NO_CACHING = 0x00000200
    };

    /*
     * maps the memory referenced by fd. but DOESN'T take ownership
     * of the filedescriptor (it makes a copy with dup()
     */
    MemoryHeapBase(int fd, size_t size, uint32_t flags = 0, uint32_t offset = 0);

    /*
     * maps memory from the given device(设备是指文件路径)
     */
    MemoryHeapBase(const char* device, size_t size = 0, uint32_t flags = 0);

    /*
     * 从ashmem来映射内存, 可以给定一个名字,用来debug
     * maps memory from ashmem, with the given name for debugging
     */
    MemoryHeapBase(size_t size, uint32_t flags = 0, char const* name = NULL);

    virtual ~MemoryHeapBase();

    /* implement IMemoryHeap interface */
    virtual int         getHeapID() const; //内存块的引用id, 就是文件描述符mFD

    /* virtual address of the heap. returns MAP_FAILED in case of error */
    virtual void*       getBase() const;

    virtual size_t      getSize() const;
    virtual uint32_t    getFlags() const;
    virtual uint32_t    getOffset() const;

    const char*         getDevice() const;

    /* this closes this heap -- use carefully */
    void dispose();

    /* this is only needed as a workaround, use only if you know
     * what you are doing */
    status_t setDevice(const char* device) {
        if (mDevice == 0)
            mDevice = device;
        return mDevice ? NO_ERROR : ALREADY_EXISTS;
    }

protected:
            MemoryHeapBase();
    // init() takes ownership of fd
    status_t init(int fd, void *base, int size,
            int flags = 0, const char* device = NULL);

private:
	//使用mmap将创建的内存通过fd映射到进程中。
    status_t mapfd(int fd, size_t size, uint32_t offset = 0);

    int         mFD; //内存Id
    size_t      mSize; //内存大小
    void*       mBase; //内存首地址
    uint32_t    mFlags;
    const char* mDevice;
    bool        mNeedUnmap;
    uint32_t    mOffset; //偏移量
};

主要的构造:

//使用Ashmem创建匿名共享内存, 并获取内存代表的文件描述符FD。
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)); //计算申请的内存大小
    //使用ashmem_create_region申请内存
    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);
            }
        }
    }
}

//通过获取的文件描述符,使用mmap将FD代表的内存信息,真正的映射物理内存到本进程。
status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset)
{
    if (size == 0) {
        // try to figure out the size automatically
        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创建的内存映射到本地进程中
        void* base = (uint8_t*)mmap(0, size,
                PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
        if (base == MAP_FAILED) {
            ALOGE("mmap(fd=%d, size=%u) failed (%s)",
                    fd, uint32_t(size), strerror(errno));
            close(fd);
            return -errno;
        }
        //ALOGD("mmap(fd=%d, base=%p, size=%lu)", fd, base, size);
        mBase = base;
        mNeedUnmap = true;
    } else  {
        mBase = 0; // not MAP_FAILED
        mNeedUnmap = false;
    }
    //记录映射所需要的信息,在其他进程中作为映射参数。
    mFD = fd; //记录fd(HeapId)
    mSize = size; //记录内存大小
    mOffset = offset; //记录偏移量
    return NO_ERROR;
}

/*获取内存的代表fd*/
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;
}

2. MemoryBase

MemoryHeapBase是作为申请内存的代表,而要将申请的内存用于多个进程来进行共享, 一般使用MemoryBase来携带MemoryHeapBase, 而进程间进行通信的代表, 是MemoryBase的父类 - IMemory

真正的操作接口其实就是在IMemory中, MemoryBase只是作为数据记录。

///frameworks/native/include/binder/MemoryBase.h
//同样的MemoryBase,也扮演这个服务端(申请内存的一端)的角色。
class MemoryBase : public BnMemory
{
public:
    MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
    virtual ~MemoryBase();
    virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const;

protected:
    size_t getSize() const { return mSize; }
    ssize_t getOffset() const { return mOffset; }
    const sp<IMemoryHeap>& getHeap() const { return mHeap; }

private:
    size_t          mSize;
    ssize_t         mOffset;
    sp<IMemoryHeap> mHeap;
};

///frameworks/native/libs/binder/MemoryBase.cpp
//记录MemoryHeapBase的参数,即在使用MemoryHeapBase申请匿名内存的时候的参数:
	// - 内存信息(包括描述符fd(heapId),首地址等 )
	// - 偏移量信息
	// - 内存大小信息
MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap,
        ssize_t offset, size_t size)
    : mSize(size), mOffset(offset), mHeap(heap)
{
}

//获取IMemoryHeap(MemoryHeapBase的binder通信接口), offset 和 size参数。
sp<IMemoryHeap> MemoryBase::getMemory(ssize_t* offset, size_t* size) const
{
    if (offset) *offset = mOffset;
    if (size)   *size = mSize;
    return mHeap;
}

MemoryBase::~MemoryBase()
{
}

3. IMemory

在这里插入图片描述
说明:
1. 服务端通过MemoryHeapBase申请到共享内存;
2. 通过MemoryBase包装MemoryHeapBase对象,保存共享内存的信息数据;
3. 通过Binder通信, 将IMemory携带到客户端一侧;
4. 客户端获取服务端的共享内存数据并映射到自己进程中(pionter() 函数);
5. 客户端通过调用IMemory的pointer函数, 完成映射并获取到共享内存的首地址;
6. pointer通过调用 geMemory, 向服务端的MemoryHeapBase请求共享内存的数据参数(内存代表的文件描述符、内存大小、访问权限和内存偏移量, 都是内存映射必不可少的参数)。
7. 通过mmap映射内存到客户端, 最后返回映射的共享内存首地址,这样, 两个进程之间就可以共享申请的匿名共享内存了。

注意:
由于虚拟内存的存在,文件描述符号是不能在两个进程之间传递的, 传递过去是无效的。这里可以传递,是因为Android的Binder在驱动侧做了适配, 在传递到另外一个进程的时候,binder驱动会自动查找并分配最小未使用的FD给另一个进程, 所以两个进程使用的FD可能是不一样的。

3.1. IMemory在客户端的内存映射流程:

从pointer开始:

// 1. pointer
void* IMemory::pointer() const {
    ssize_t offset;
    //获取服务端的MemoryHeapBase的代理IMemoryHeap对象;
    sp<IMemoryHeap> heap = getMemory(&offset); 
    //通过IMemoryHeap的base()函数调用getBase() 向服务端请求内存映射所需要的参数并映射到该进程, 最后返回映射的内存首地址;
    void* const base = heap!=0 ? heap->base() : MAP_FAILED;
    if (base == MAP_FAILED)
        return 0;
    //返回的是首地址+偏移量, 一般没有偏移的话,那就是0了(参考mmap的offset参数);
    return static_cast<char*>(base) + offset;
}

//2. 调用getMemory获取IMemoryHeap对象
sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const
{
    if (mHeap == 0) {
        Parcel data, reply;
        data.writeInterfaceToken(IMemory::getInterfaceDescriptor());
        //获取MemoryHeapBase代理
        if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) {
            sp<IBinder> heap = reply.readStrongBinder(); //获取fd
            ssize_t o = reply.readInt32(); //获取offset
            size_t s = reply.readInt32(); //获取内存大小
            if (heap != 0) {
                mHeap = interface_cast<IMemoryHeap>(heap);
                if (mHeap != 0) {
                    size_t heapSize = mHeap->getSize();
                    if (s <= heapSize
                            && o >= 0
                            && (static_cast<size_t>(o) <= heapSize - s)) {
                        mOffset = o;
                        mSize = s;
                    } else {
                        // Hm.
                        android_errorWriteWithInfoLog(0x534e4554,
                            "26877992", -1, NULL, 0);
                        mOffset = 0;
                        mSize = 0;
                    }
                }
            }
        }
    }
    
    //存储获取的数据
    if (offset) *offset = mOffset; //偏移量
    if (size) *size = mSize; //内存大小
    return (mSize > 0) ? mHeap /*MemoryHeap*/: 0;
}


//3. ############BnMemory即MemoryHeapBase, MemoryHeapBase是继承自BnMemory的服务端代表
status_t BnMemory::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        case GET_MEMORY: {
            CHECK_INTERFACE(IMemory, data, reply);
            ssize_t offset;
            size_t size;
            //调用MemoryHeapBase的getMemory(参考上面MemoryHeapBase::getMemory的说明)
            //并将获取的IMemoryBase和offset, size写入到客户端
            reply->writeStrongBinder( IInterface::asBinder(getMemory(&offset, &size)) ); 
            reply->writeInt32(offset);
            reply->writeInt32(size);
            return NO_ERROR;
        } break;
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

// 4. 获取映射首地址
void* BpMemoryHeap::getBase() const {
    assertMapped(); //如果已经映射,返回首地址,还没有映射,则先映射再返回映射的首地址。
    return mBase; //返回映射的首地址。
}

// 5. 断定使得否已经映射过了
void BpMemoryHeap::assertMapped() const
{
    if (mHeapId == -1) {
        sp<IBinder> binder(IInterface::asBinder(const_cast<BpMemoryHeap*>(this)));
        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);
        }
    }
}

// 6. 真正的开始映射
void BpMemoryHeap::assertReallyMapped() const
{
    if (mHeapId == -1) {

        // remote call without mLock held, worse case scenario, we end up
        // calling transact() from multiple threads, but that's not a problem,
        // only mmap below must be in the critical section.

        Parcel data, reply;
        //从服务端获取映射所需要的参数;
        data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());
        status_t err = remote()->transact(HEAP_ID, data, &reply);
        int parcel_fd = reply.readFileDescriptor(); //文件描述符
        ssize_t size = reply.readInt32(); // 内存大小
        uint32_t flags = reply.readInt32(); // 访问权限标志
        uint32_t offset = reply.readInt32(); // 偏移量

        ALOGE_IF(err, "binder=%p transaction failed fd=%d, size=%zd, err=%d (%s)",
                IInterface::asBinder(this).get(),
                parcel_fd, size, err, strerror(-err));

        Mutex::Autolock _l(mLock);
        if (mHeapId == -1) {
            int fd = dup( parcel_fd );
            ALOGE_IF(fd==-1, "cannot dup fd=%d, size=%zd, err=%d (%s)",
                    parcel_fd, size, err, strerror(errno));

            int access = PROT_READ;
            if (!(flags & READ_ONLY)) {
                access |= PROT_WRITE;
            }

            mRealHeap = true;
            //调用mmap开始映射
            mBase = mmap(0, size, access, MAP_SHARED, fd, offset);
            if (mBase == MAP_FAILED) {
                ALOGE("cannot map BpMemoryHeap (binder=%p), size=%zd, fd=%d (%s)",
                        IInterface::asBinder(this).get(), size, fd, strerror(errno));
                close(fd);
            } else {
            	//记录映射完成的数据。
                mSize = size;
                mFlags = flags;
                mOffset = offset;
                android_atomic_write(fd, &mHeapId);
            }
        }
    }
}

3. 代码样例

定义公共的数据结构和枚举变量:

typedef struct DataSt {
	int id;
	int flag;
	int tagIndex;
	int dataSize;
	int type;
	char data[0]; //可扩展数据段
}DataSt_t;

enum {
 CMD_START = IBinder::FIRST_CALL_TRANSACTION,
 CMD_BINDER_OBJ = CMD_START + 1,
};

发送端:

//发送端------------------------------------------------------------------------------

sp<IMemory> Client::allocateMem(size_t size, const char *name) {
    //申请用于扩展字段,头申请自己的空间
    sp<MemoryHeapBase> heap = new MemoryHeapBase(sizeof(DataSt_t) + size, 0, name);
    sp<IMemory> mem = new MemoryBase(heap, 0, size);
    return mem;
}

void Client::fillData(sp<IMemory> mem) {
    ssize_t offset;
    size_t size;
    sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);

    auto dataSt = static_cast<DataSt_t*>(heap->base() + offset);
    dataSt->id = 0x11223344;
    dataSt->flag = 0x44332211;
    dataSt->tagIndex = mSessionId;
    dataSt->type = 'TYPE';
    dataSt->dataSize = sizeof(int);

    //扩展数据段
    int data = 5;
    memcpy(dataSt->data, &data, sizeof(int));
}

int Client::deleteMem() {
    mMem = nullptr;
    return Result::OK;
}

void Client::sendBinder(sp<IMemory> mem) {
    if (mServer != nullptr) {
        Parcel data, reply;
        携带Bindr对象,需要借助IInterface::asBinder接口.
        data.writeStrongBinder(IInterface::asBinder(mem)); 
        if (mServer->transact(CMD_BINDER_OBJ, data, &reply) != 0) {
            //....
        } else {
            //....
        }
    }
}

int main() {
    //模拟的执行方法,这里是客户端执行段
	sp<IMemory> mem = allocateMem(1920 * 1080, "IMemory_test");
	fillData(mem);
	sendBinder(mem);
}

接收端

//接收端:-----------------------------------------------------------------------------
status_t Server::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        case CMD_BINDER_OBJ: {
            ALOGD("Memory callback.");
            //接收Binder对象, 使用interface_cast<IMemory>(data.readStrongBinder());
            //或者IMemory::asInterface(data.readStrongBinder())转换为对应的Binder接口
            sp<IMemory> mem = interface_cast<IMemory>(data.readStrongBinder());

            ssize_t offset = 0;
            size_t size = 0;
            sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
            auto dataSt = (DataSt_t *)(heap->base() + offset);

            if (dataSt == nullptr) {
                ALOGE("dataSt == nullptr");
                return -1;
            }

            ALOGD("offset= %d, size= %d", offset, size);

            ALOGD("typedef struct DataSt {\n"
                 "    int id = %x;\n"
                 "    int flag = %x;\n"
                 "    int tagIndex = %d;\n"
                 "    int dataSize = %d;\n"
                 "    int type = %d;\n"
                 "    char data[0] = %d;\n"
                 "}DataSt_t;", dataSt->id, dataSt->flag, dataSt->tagIndex,
                 dataSt->dataSize, dataSt->type,
                 ((uint32_t*)(dataSt->data))[0]);
			
            return NO_ERROR;
        }
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

使用后,客户端和服务端的addr 是指向同一块地址内存的相同偏移量的, 也就是共享内存的首地址。


IMemoryTest Demo:
https://gitee.com/bossagit11/android-imemory-test

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这是一个简单的binder示例demo,包含一个服务端和一个客户端: 服务端代码(ServiceDemo.cpp): ``` #include <binder/IServiceManager.h> #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IMemory.h> #include "ServiceDemo.h" using namespace android; int main(int argc, char **argv) { sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); sm->addService(String16("demo.service"), new ServiceDemo()); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); return 0; } ServiceDemo::ServiceDemo() { ALOGD("ServiceDemo"); } ServiceDemo::~ServiceDemo() { ALOGD("~ServiceDemo"); } int32_t ServiceDemo::add(int32_t a, int32_t b) { ALOGD("add: %d + %d = %d", a, b, a + b); return a + b; } ``` 客户端代码(ClientDemo.cpp): ``` #include <binder/IServiceManager.h> #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IMemory.h> #include "ServiceDemo.h" using namespace android; int main(int argc, char **argv) { sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder = sm->getService(String16("demo.service")); sp<ServiceDemo> serviceDemo = interface_cast<ServiceDemo>(binder); ALOGD("add result: %d", serviceDemo->add(1, 2)); return 0; } ``` 需要注意的是,在服务端代码中,需要实现aidl文件中定义的接口方法。在本例中,接口方法只有一个add(),返回两个int类型数据的和。 在客户端代码中,首先通过defaultServiceManager()获取IServiceManager对象,然后通过getService()获取服务端的IBinder对象,最后将IBinder对象转换为服务端接口ServiceDemo对象,并调用其中的方法。 本例中,将服务名设为"demo.service",可以根据实际需求修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值