GamepadSharedBuffer 的基本原理

368 阅读3分钟

前言

GamepadSharedBuffer 是 chromium 用来在 blink 进程和 browser 进程进行手柄数据传递的一个对象。他的基本原理是共享内存。安卓使用的是 ASharedMemory_xxx 方法来实现。

代码分析

我们先来看 GamepadSharedBuffer 。他的构造函数中包含了创建共享内存的逻辑:

// device/gamepad/gamepad_shared_buffer.cc
GamepadSharedBuffer::GamepadSharedBuffer() {
  // 可以看到是通过 MappedReadOnlyRegion 来创建的共享内存。
  base::MappedReadOnlyRegion mapped_region =
      base::ReadOnlySharedMemoryRegion::Create(sizeof(GamepadHardwareBuffer));
  CHECK(mapped_region.IsValid());
  // region 相当于一个内存管理器,包含一些平台相关的内存处理逻辑,比如内存创建,复制,校验等等操作。
  shared_memory_region_ = std::move(mapped_region.region);
  // 存储了生成的共享内存的指针,代表一块共享内存。
  shared_memory_mapping_ = std::move(mapped_region.mapping);
  // 获取共享内存
  void* mem = shared_memory_mapping_.memory();
  DCHECK(mem);
  // 在 mem 的内存位置上构建 GamepadHardwareBuffer 对象。
  hardware_buffer_ = new (mem) GamepadHardwareBuffer();
  // 初始化 hardware_buffer_ 中的 data 字段,data字段是一个 Gamepads 结构。
  memset(&(hardware_buffer_->data), 0, sizeof(Gamepads));
}

可以看到这里通过 ReadOnlySharedMemoryRegion::Create 创建了一个共享内存管理器和一块共享内存。hardware_buffer_ 生成在了这个共享内存的地址上,后续对 hardware_buffer_ 的修改,就是在写共享内存了。

再来深入 ReadOnlySharedMemoryRegion::Create 方法来看看。

// base/memory/read_only_shared_memory_region.cc
MappedReadOnlyRegion ReadOnlySharedMemoryRegion::Create(size_t size) {
  .....
  // 创建可以读写的共享内存
  subtle::PlatformSharedMemoryRegion handle =
      subtle::PlatformSharedMemoryRegion::CreateWritable(size);
  .....
  // 调用 mmap 来将共享内存文件映射到一块共享内存,避免使用文件相关的逻辑去操作内存。
  void* memory_ptr = nullptr;
  size_t mapped_size = 0;
  if (!handle.MapAt(0, handle.GetSize(), &memory_ptr, &mapped_size))
    return {};
  .....
  return {std::move(region), std::move(mapping)};
}

可以看到其中比较重要的有两部分,第一,创建共享内存得到一个 fd,然后通过 mmap 将共享内存的虚拟文件映射到内存中,方便操作,提高效率。

先来看看怎么创建的共享内存文件:


// base/memory/platform_shared_memory_region_android.cc
// mode = Mode::kWritable
PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
                                                              size_t size) {
  .....
  // Align size as required by ashmem_create_region() API documentation. This
  // operation may overflow so check that the result doesn't decrease.
  // ashmem_create_region() API 的文档中提到,需要使用已经对齐了的内存,此操作可能会溢出,如果溢出则视为失败,终止创建。
  size_t rounded_size = bits::AlignUp(size, GetPageSize());
  if (rounded_size < size ||
      rounded_size > static_cast<size_t>(std::numeric_limits<int>::max())) {
    return {};
  }
  // 创建一个共享内存文件的唯一标识
  UnguessableToken guid = UnguessableToken::Create();
  // 调用 安卓 native 的 api,创建共享内存区域。
  int fd = ashmem_create_region(
      SharedMemoryTracker::GetDumpNameForTracing(guid).c_str(), rounded_size);
  ......
  ScopedFD scoped_fd(fd);
  // 限制读写权限
  int err = ashmem_set_prot_region(scoped_fd.get(), PROT_READ | PROT_WRITE);
  ......
  return PlatformSharedMemoryRegion(std::move(scoped_fd), mode, size, guid);
}

其中 ashmem_create_region 方法在安卓上面是通过 dlopen("libandroid.so", RTLD_NOW) 打开了安卓系统的so,然后通过 dlsym(lib, "ASharedMemory_create") 获取到了函数指针。其他几个 ashmem_ 方法都是通过此种方式生成的。为什么这样调用,应该是由于安卓系统版本比较多,低版本还未暴露这个方法,只能这样调用。

然后再来看看 mmap 映射:

// base/memory/platform_shared_memory_region_android.cc 
bool PlatformSharedMemoryRegion::MapAtInternal(off_t offset,
                                               size_t size,
                                               void** memory,
                                               size_t* mapped_size) const {
  // IMPORTANT: Even if the mapping is readonly and the mapped data is not
  // changing, the region must ALWAYS be mapped with MAP_SHARED, otherwise with
  // ashmem the mapping is equivalent to a private anonymous mapping.
  bool write_allowed = mode_ != Mode::kReadOnly;
  // 调用了安卓 native 的方法 mmap。
  *memory = mmap(nullptr, size, PROT_READ | (write_allowed ? PROT_WRITE : 0),
                 MAP_SHARED, handle_.get(), offset);
  ......
}

mmap 是做什么,这里引用一段话:mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

到这里,这块共享内存就创建好了,可以拿去使用了。

结语

可以看到 chromium 在安卓上面只是对共享内存的创建过程做了一个简单的封装,核心还是 create 和 mmap 的两个操作。而 chromium 里面包含了比较多的异常处理逻辑,值得参考。

参考文章

developer.android.com/ndk/referen…

Ashmem(Android共享内存)使用方法和原理 - 掘金

mmap + native 日志优化 - 掘金