2.43 Android匿名共享内存

2 minute read

Android的匿名共享内存

  • Android可以使用Linux的一切IPC通信方式,包括共享内存,不过Android主要使用的方式是匿名共享内存Ashmem(Anonymous Shared Memory), MemoryFile是Android为匿名共享内存而封装的一个对象,这里通过使用MemoryFile来分析,Android中如何利用共享内存来实现大数据传递,同时MemoryFile也是进程间大数据传递的一个手段
  // IMemoryAidlInterface.aidl
  interface IMemoryAidlInterface {
    ParcelFileDescriptor getParcelFileDescriptor();
  }

  // MemoryFetchService

  public class MemoryFetchService extends Service {
    public IBinder onBind(Intent intent) {
        return new MemoryFetchStub();
    }
    static class MemoryFetchStub extends IMemoryAidlInterface.Stub {
        public ParcelFileDescriptor getParcelFileDescriptor() throws RemoteException {
            MemoryFile memoryFile = null;
            ...
            memoryFile = new MemoryFile("test_memory", 1024);
            memoryFile.getOutputStream().write(new byte[]{1, 2, 3, 4, 5});
            Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
            FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);
            return ParcelFileDescriptor.dup(des);
            ...
     }}}

  // TestActivity.java
  Intent intent = new Intent(MainActivity.this, MemoryFetchService.class);
  bindService(intent, new ServiceConnection() {
      public void onServiceConnected(ComponentName name, IBinder service) {
          byte[] content = new byte[10];
          IMemoryAidlInterface iMemoryAidlInterface = IMemoryAidlInterface.Stub.asInterface(service);
          ParcelFileDescriptor parcelFileDescriptor = iMemoryAidlInterface.getParcelFileDescriptor();
          FileDescriptor descriptor = parcelFileDescriptor.getFileDescriptor();
          FileInputStream fileInputStream = new FileInputStream(descriptor);
          fileInputStream.read(content);
      ...
  }, Service.BIND_AUTO_CREATE);

MemoryFile共享内存的分配与传递

  • native_open()
static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)
{
  const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
  // ashmem_create_region创建共享内存
  int result = ashmem_create_region(namestr, length);
  ...
  return jniCreateFileDescriptor(env, result);
}

int ashmem_create_region(const char *name, size_t size)
{
    int fd, ret;
    // 调用这个open函数最终会进入到Ashmem驱动程序中的ashmem_open函数中去
    fd = open(ASHMEM_DEVICE, O_RDWR);
    if (fd < 0)
        ...
        // ASHMEM_DEVICE其实就是抽象的共享内存设备,它是一个杂项设备(字符设备的一种),在驱动加载之后,就会在/dev下ashem文件,之后用户就能够访问该设备文件,同一般的设备文件不同,它仅仅是通过内存抽象的,同普通的磁盘设备文件、串行端口字段设备文件不一样
        ret = ioctl(fd, ASHMEM_SET_NAME, buf);
        ...
    }
    ...
}
  • 匿名文件操作
  struct file_operations ashmem_fops = {
  .owner = THIS_MODULE,
  .open = ashmem_open,
  .release = ashmem_release,
  .mmap = ashmem_mmap,
  .unlocked_ioctl = ashmem_ioctl,
  .compat_ioctl = ashmem_ioctl,
  };
  • ashmem_open()
static int ashmem_open(struct inode *inode, struct file *file)
{
  struct ashmem_area *asma;
  int ret;

  ret = nonseekable_open(inode, file);
  ...

  asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
  ...

  return 0;
}
  • ashmem_ioctl()
long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
  struct ashmem_area *asma = file->private_data;
  long ret = -ENOTTY;

  switch (cmd) {
  ......
  case ASHMEM_SET_SIZE:
    ret = -EINVAL;
    if (!asma->file) {
      ret = 0;
      asma->size = (size_t) arg;
    }
    break;
  ......
  }

  return ret;
}
  • native_mmap()
// 文件描述符fd是在前面open匿名设备文件/dev/ashmem获得的,有个这个文件描述符后,就可以直接通过mmap来执行内存映射操作了
static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
      jint length, jint prot)
{
  int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
  // 系统调用mmap,将共享内存映射到当前进程空间
  jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);
  ...
  return result;
}     
  • ashmem_mmap()
int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
  struct ashmem_area *asma = file->private_data;
  int ret = 0;
  ...
  if (!asma->file) {
    char *name = ASHMEM_NAME_DEF;
    struct file *vmfile;
    /* ... and allocate the backing shmem file */
    // 调用了Linux内核提供的shmem_file_setup函数来在临时文件系统tmpfs中创建一个临时文件,这个临时文件与Ashmem驱动程序创建的匿名共享内存对应
    vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
    ...
    // 表示当前进程空间中一块连续的虚拟地址空间
    // 临时文件vmfile也会保存asma->file域中,这样,Ashmem驱动程序后面就可以通过在asma->file来操作这个匿名内存共享文件了。
    asma->file = vmfile;
  }
  get_file(asma->file);

  if (vma->vm_flags & VM_SHARED)
    shmem_set_file(vma, asma->file);
  else {
    if (vma->vm_file)
      fput(vma->vm_file);
    //这个临时文件vmfile也会保存asma->file域中,这样,Ashmem驱动程序后面就可以通过在asma->file来操作这个匿名内存共享文件了。
    vma->vm_file = asma->file;
  }
  ...

}

匿名共享内存的优缺点

  • 优点: 匿名共享内存不会占用Dalvik Heap与Native Heap,不会导致OOM
  • 缺点: 过度使用会导致系统资源不足
  • 共享存占用空间的计算,只会计算到第一个创建它的进程中,其他进程不将ashmem计算在内

参考