Android开发经验谈

033 Android多进程-共享内存

2021-01-19  本文已影响0人  凤邪摩羯

1 什么是共享内存?

1.1 什么是共享内存?

1.2 关于共享内存

1.3 Mutex对象

  • 1、获取Mutex对象,锁定共享区域
  • 2、将要通信的数据写入共享区域
  • 3、释放Mutex对象

1.4 内存模型

要使用一块共享内存

  • 进程必须首先分配它
  • 随后需要访问这个共享内存块的每一个进程都必须将这个共享内存绑定到自己的地址空间中。
  • 当完成通信之后,所有进程都脱离共享内存,并且由一个进程释放该共享内存块。

1.5 Linux系统内存模型

2 共享内存的使用和原理

还是先看共享内存的使用方法,我主要介绍两个函数:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); //申请共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg); //把共享内存映射到进程的地址空间

通过shmget()函数申请共享内存,它的入参如下

通过shmat()函数将我们申请到的共享内存映射到自己的用户空间,映射成功会返回地址,有了这个地址,我们就可以随意的读写数据了,我们继续看一下这个函数的入参

共享内存的原理是在内存中单独开辟的一段内存空间,这段内存空间其实就是一个tempfs(临时虚拟文件),tempfs是VFS的一种文件系统,挂载在/dev/shm上,前面提到的管道pipefs也是VFS的一种文件系统。

image

由于共享的内存空间对使用和接收进程来讲,完全无感知,就像是在自己的内存上读写数据一样,所以也是效率最高的一种IPC方式。

上面提到的IPC的方式都是在内核空间中开辟内存来存储数据,写数据时,需要将数据从用户空间拷贝到内核空间,读数据时,需要从内核空间拷贝到自己的用户空间,
共享内存就只需要一次拷贝,而且共享内存不是在内核开辟空间,所以可以传输的数据量大

但是共享内存最大的缺点就是没有并发的控制,我们一般通过信号量配合共享内存使用,进行同步和并发的控制

3 Android中共享内存的使用场景

共享内存在Android系统中主要的使用场景是用来传输大数据,并且Android并没有直接使用Linux原生的共享内存方式,而是设计了Ashmem匿名共享内存

之前说到有名管道和匿名管道的区别在于有名管道可以在vfs目录树中查看到这个管道的文件,但是匿名管道不行,所以匿名共享内存同样也是无法在vfs目录中查看到的,Android之所以要设计匿名共享内存,我觉得主要是为了安全性的考虑吧。

我们来看看共享内存的一个使用场景,在Android中,如果我们想要将当前的界面显示出来,需要将当前界面的图元数据传递Surfaceflinger去做图层混合,图层混合之后的数据会直接送入帧缓存,送入帧缓存后,显卡就会直接取出帧缓存里的图元数据显示了。

那么我们如何将应用的Activity的图元数据传递给SurfaceFlinger呢?想要将图像数据这样比较大的数据跨进程传输,靠binder是不行的,所以这儿便用到匿名共享内存。

image

从谷歌官方提供的架构图可以看到,图元数据是通过BufferQueue传递到SurfaceFlinger去的,当我们想要绘制图像的时候,需要从BufferQueue中申请一个Buffer,Buffer会调用Gralloc模块来分配共享内存当作图元缓冲区存放我们的图元数据。

//文件-->hardware/libhardware/modules/gralloc/gralloc.cpp
static int gralloc_alloc_buffer(alloc_device_t* dev,
        size_t size, int usage, buffer_handle_t* pHandle)
{
    int err = 0;
    int fd = -1;
    size = roundUpToPageSize(size);
    // 创建共享内存,并且设定名字跟size
    fd = ashmem_create_region("gralloc-buffer", size);
    if (err == 0) {
        private_handle_t* hnd = new private_handle_t(fd, size, 0);
        gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
                dev->common.module);
         // 执行mmap,将内存映射到自己的进程
        err = mapBuffer(module, hnd);
        if (err == 0) {
            *pHandle = hnd;
        }
    }
​
    return err;
}
​
int mapBuffer(gralloc_module_t const* module,
            private_handle_t* hnd)
{
        void* vaddr; 
        return gralloc_map(module, hnd, &vaddr);
    }
​
static int gralloc_map(gralloc_module_t const* module,
        buffer_handle_t handle,
        void** vaddr)
{
    private_handle_t* hnd = (private_handle_t*)handle;
    if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
        size_t size = hnd->size;
        //映射创建的匿名共享内存
        void* mappedAddress = mmap(0, size,
                PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
        if (mappedAddress == MAP_FAILED) {
            return -errno;
        }
        hnd->base = intptr_t(mappedAddress) + hnd->offset;
    }
    *vaddr = (void*)hnd->base;
    return 0;
}

可以看到Android的匿名共享内存是通过ashmem_create_region() 函数来申请共享内存的,它会在/dev/ashmem下创建一个虚拟文件,Linux原生共享内存是通过shmget()函数,并会在/dev/shm下创建虚拟文件。

匿名共享内存是通过mmap()函数将申请到的内存映射到自己的进程空间,而Linux是通过*shmat()函数。

虽然函数不一样,但是Android的匿名共享内存和Linux的共享内存在本质上是大同小异的。

上一篇 下一篇

猜你喜欢

热点阅读