Skip to content

Instantly share code, notes, and snippets.

@jimblandy
Last active July 1, 2022 08:17
Show Gist options
  • Save jimblandy/0d4a3ecb25ba9a11098437eee90d7cfd to your computer and use it in GitHub Desktop.
Save jimblandy/0d4a3ecb25ba9a11098437eee90d7cfd to your computer and use it in GitHub Desktop.
Explanation of Firefox's `mozilla::ipc::Shmem`

ipc::Shmem and friends

The mozilla::ipc::Shmem type represents a block of memory that IPDL messages can transfer between processes without having to copy the contents. IPDL messages can pass and Shmem arguments, as in these examples from PWebGPU.ipdl:

async BufferReturnShmem(RawId selfId, Shmem shmem);
async BufferMap(RawId selfId, WGPUHostMap hostMap, uint64_t offset, uint64_t size) returns (Shmem sm);

Given a Shmem s, you can call s.Size<T> to get the length of the shared memory block considered as an array of T values, and s.get<T> to get a T* pointing to the first element.

A Shmem is usually first constructed with no memory allocated to it, using the default constructor. For example, theDevice::CreateBuffer method in Firefox's WebGPU implementation declares a Shmem like this:

ipc::Shmem shmem;

Calling get or Size on a Shmem in this state will assert. To allocate some memory to a Shmem, call IProtocol::AllocShmem:

class IProtocol {
  public:
    bool AllocShmem(size_t aSize, Shmem* aOutMem);
    ...
};

IProtocol is the base class for all IPDL-generated actor classes, so the AllocShmem method is available on any actor object.

The WebGPU code above continues:

if (!mBridge->AllocShmem(size, ipc::Shmem::SharedMemory::TYPE_BASIC,
                         &shmem)) {
  aRv.ThrowAbortError(
      nsPrintfCString("Unable to allocate shmem of size %" PRIuPTR, size));
  return nullptr;
}

// zero out memory
memset(shmem.get<uint8_t>(), 0, size);

This calls AllocShmem on the IPDL child actor mBridge to populate shmem with size bytes, calls shmem.get to obtain a pointer to the shared memory block, and then zeros out its contents. Then it calls an IPDL Send method to transmit the shared buffer to its parent actor:

mBridge->SendBufferReturnShmem(id, std::move(shmem));

The IPDL Recv method receives the shared buffer like this:

ipc::IPCResult WebGPUParent::RecvBufferReturnShmem(RawId aSelfId,
                                                   Shmem&& aShmem) {
    ...
}

Sending a Shmem in an IPDL message causes the sender to lose all access to the Shmem's pages. Receiving a Shmem grants the receiver access to those pages. (On Linux, these are mprotect system calls.)

Even though IPDL generates rvalue references for sending and receiving Shmems, it doesn't actually matter: Shmem doesn't define move constructors or assignment operators, only a defaulted copy constructor and assignment operator.

Preventing simultaneous access to shared memory

When a process calls actor->AllocShmem to allocate memory for a Shmem, that immediately sends a message to the other process carrying a platform-specific handle to the shared memory. The recipient uses this handle to map the shared memory into its own address space, initially prohibiting all access to the pages.

(On Linux, IPDL uses the sendmsg system call's SCM_RIGHTS message type to send a file descriptor created with memfd_create. This file descriptor is closed in both processes once initial setup is complete.)

AllocShmem also assigns the Shmem a unique id, and registers it under that id in IToplevelProtocol::mShmemMap. The other process does the same when it receives notification of the new Shmem. Once the Shmem has been allocated, sending it in IPDL messages actually just sends this id back and forth. The recipient uses the mShmemMap entry to reconstruct the Shmem and permit access to its pages.

Platform abstraction: mozilla::ipc::SharedMemory

TODO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment