carb::extras::SharedMemory

Defined in carb/extras/SharedMemory.h

Classes

class SharedMemory

A utility helper class to provide shared memory access to one or more processes.

The shared memory area is named so that it can be opened by another process or component using the same name. Once created, views into the shared memory region can be created. Each successfully created view will unmap the mapped region once the view object is deleted. This object and any created view objects exist independently from each other - the ordering of destruction of each does not matter. The shared memory region will exist in the system until the last reference to it is released through destruction.

A shared memory region must be mapped into a view before it can be accessed in memory. A new view can be created with the createView() function as needed. As long as one SharedMemory object still references the region, it can still be reopened by another call to open(). Each view object maps the region to a different location in memory. The view’s mapped address will remain valid as long as the view object exists. Successfully creating a new view object requires that the mapping was successful and that the mapped memory is valid. The view object can be wrapped in a smart pointer such as std::unique_ptr<> if needed to manage its lifetime. A view object should never be copied (byte-wise or otherwise).

A view can also be created starting at non-zero offsets into the shared memory region. This is useful for only mapping a small view of a very large shared memory region into the memory space. Multiple views of the same shared memory region may be created simultaneously.

When opening a shared memory region, an ‘open token’ must be acquired first. This token can be retrieved from any other shared memory region object that either successfully created or opened the region using getOpenToken(). This token can then be transmitted to any other client through some means to be used to open the same region. The open token data can be retrieved as a base64 encoded string for transmission to other processes. The target process can then create its own local open token from the base64 string using the constructor for SharedMemory::OpenToken. Open tokens can also be passed around within the same process or through an existing shared memory region as needed by copying, moving, or assigning it to another object.

Note

On Linux, this requires that the host app be linked with the “rt” and “pthread” system libraries. This can be done by adding ‘links { “rt”, “pthread” }’ to the premake script or adding the “-lrt -lpthread” option to the build command line.

Public Types

enum class AccessMode

Names for the different ways a mapping region can be created and accessed.

Values:

enumerator eDefault

Use the default memory access mode for the mapping.

When this is specified to create a view, the same access permissions as were used when creating the shared memory region will be used. For example, if the SHM region is created as read-only, creating a view with the default memory access will also be read-only. The actual access mode that was granted can be retrieved from the View::getAccessMode() function after the view is created.

enumerator eReadOnly

Open or access the shared memory area as read-only.

This access mode cannot be used to create a new SHM area. This can be used to create a view of any shared memory region however.

enumerator eReadWrite

Create, open, or access the shared memory area as read-write.

This access mode must be used when creating a new shared memory region. It may also be used when opening a shared memory region, or creating a view of a shared memory region that was opened as read-write. If this is used to create a view on a SHM region that was opened as read-only, the creation will fail.

enum Result

Result from createOrOpen().

Values:

enumerator eError

An error occurred when attempting to create or open shared memory.

enumerator eCreated

The call to createOrOpen() created the shared memory by name.

enumerator eOpened

The call to createOrOpen() opened an existing shared memory by name.

Public Functions

inline SharedMemory()

Constructor: initializes a new shared memory manager object.

This will not point to any block of memory.

inline ~SharedMemory()
inline bool create(const char *name, size_t size, uint32_t flags = fCreateMakeUnique)

Creates a new shared memory region.

Remark

This creates a new shared memory region in the system. If successful, this new region will be guaranteed to be different from all other shared memory regions in the system. The new region will always be opened for read and write access. New views into this shared memory region may be created upon successful creation using createView(). Any created view object will remain valid even if this object is closed or destroyed. Note however that other clients will only be able to open this same shared memory region if are least one SharedMemory object still has the region open in any process in the system. If all references to the region are closed, all existing views to it will remain valid, but no new clients will be able to open the same region by name. This is a useful way to ‘lock down’ a shared memory region once all expected client(s) have successfully opened it.

Note

On Linux it is possible to have region creation fail if the fCreateMakeUnique flag is not used because the region objects were leaked on the filesystem by another potentially crashed process (or the region was never closed). This is also possible on Windows, however instead of failing to create the region, an existing region will be opened instead. In both cases, this will be detected as a failure since a new unique region could not be created as expected. It is best practice to always use the fCreateMakeUnique flag here. Similarly, it is always best practice to close all references to a region after all necessary views have been created if it is intended to persist for the lifetime of the process. This will guarantee that all references to the region get released when the process exits (regardless of method).

Note

On Linux, some malware scanners such as rkhunter check for large shared memory regions and flag them as potential root kits. It is best practice to use a descriptive name for a shared memory region so that they can be easily dismissed as not a problem in a malware report log.

Parameters
  • name[in] The name to give to the new shared memory region. This may not be nullptr or an empty string. This must not contain a slash (‘/’) or backslash (’') character and must generally consist of filename safe ASCII characters. This will be used as the base name for the shared memory region that is created. This should be shorter than 250 characters for the most portable behavior. Longer names are allowed but will be silently truncated to a platform specific limit. This truncation may lead to unintentional failures if two region names differ only by truncated characters.

  • size[in] The requested size of the shared memory region in bytes. This may be any size. This will be silently rounded up to the next system supported region size. Upon creation, getSize() may be used to retrieve the actual size of the newly created region.

  • flags[in] Flags to control behavior of creating the new shared memory region. This may be 0 for default behavior, or may be fCreateMakeUnique to indicate that the name specified in name should be made into a more unique string so that creating a new region with the same base name as another region is more likely to succeed (note that it may still fail for a host of other reasons). This defaults to fCreateMakeUnique.

Returns

true if the new shared memory region is successfully created. At this point, new views to the region may be created with createView(). When this region is no longer needed, it should be closed by either calling close() or by destroying this object. This same region may be opened by another client by retrieving its open token and passing that to the other client.

Returns

false if the new shared memory region could not be created as a new region. This can include failing because another shared memory region with the same name already exists or that the name was invalid (ie: contained invalid characters).

Returns

false if another shared memory region is currently open on this object. In this case, close() must be called before attempting to create a new region.

inline Result createOrOpen(const char *name, size_t size, uint32_t flags = 0)

Attempts to create a shared memory region, or if it could not be created, open an existing one by the same name.

See also

create() for more information.

See also

create() for more information.

See also

create() for more information. If the region is opened, this parameter has different behavior between Windows and Linux. See note above.

See also

create() for more information. If the region was originally created by passing fCreateMakeUnique to create(), this function will not be able to open the region as the name was decorated with a unique identifier. Instead use open() to open the shared memory region using the OpenToken.

Note

On Windows, the attempt to create or open is performed atomically within the operating system. On Linux, however, this is not possible. Instead a first attempt is made to create the shared memory region, and if that fails, an attempt is made to open the shared memory region.

Note

On Windows, if a mapping already exists under the given name and the requested size is larger than the size of the existing mapping, the process to open the mapping is aborted and Result::eError is returned. On Linux, if the requested size is larger than the size of the existing mapping, the mapping grows to accommodate the requested size. It is not possible to grow an existing mapping on Windows.

Parameters
  • name – The name of the shared memory region.

  • size – The size of the shared memory region in bytes.

  • flags – The flags provided to direct creation/opening of the shared memory region.

Returns

A result for the operation.

inline bool open(const char *name, size_t size, uint32_t flags = 0)

Opens a shared memory region by name.

See also

create() for more information.

Note

On Windows, if a mapping already exists under the given name and the requested size is larger than the size of the existing mapping, the process to open the mapping is aborted and Result::eError is returned. On Linux, if the requested size is larger than the size of the existing mapping, the mapping grows to accommodate the requested size. It is not possible to grow an existing mapping on Windows.

Parameters
  • name – The name of the shared memory region.

  • size – The required size of the shared memory region. This parameter has different behavior on Windows and Linux. See note above.

  • flags – The flags provided to direct opening the shared memory region.

Returns

true if the region was found by name and opened successfully; false otherwise.

inline bool open(const OpenToken &openToken, AccessMode access = AccessMode::eDefault)

Opens a shared memory region by token.

Parameters
  • openToken[in] The open token to use when opening the shared memory region. This token contains all the information required to correctly open and map the same shared memory region. This open token is retrieved from another SharedMemory object that has the region open. This is retrieved with getOpenToken(). The token may be encoded into a base64 string for transmission to another process (ie: over a socket, pipe, environment variable, command line, etc). The specific transmission method is left as an exercise for the caller. Once received, a new open token object may be created in the target process by passing the base64 string to the OpenToken() constructor.

  • access[in] The access mode to open the shared memory region in. This will default to AccessMode::eReadWrite.

Returns

true if the shared memory region is successfully opened with the requested access permissions. Once successfully opened, new views may be created and the open token may also be retrieved from here to pass on to yet another client. Note that at least one SharedMemory object must still have the region open in order for the region to be opened in another client with the same open token. This object must either be closed with close() or destroyed in order to ensure the region is destroyed properly once all views are unmapped.

Returns

false if the region could not not be opened. This could include the open token being invalid or corrupt, the given region no longer being present in the system, or there being insufficient permission to access it from the calling process.

inline View *createView(size_t offset = 0, size_t size = 0, AccessMode access = AccessMode::eDefault) const

Creates a new view into this shared memory region.

Remark

This creates a new view into this shared memory region. The default behavior (with no parameters) is to map the entire shared memory region into the new view. Multiple views into the same shared memory region can be created simultaneously. It is safe to close the shared memory region or delete the object after a new view is successfully created. In this case, the view object and the mapped memory will still remain valid as long as the view object still exists. This shared memory object can similarly also safely close and open a new region while views on the previous region still exist. The previous region will only be completely invalidated once all views are deleted and all other open references to the region are closed.

Note

On Windows, the offset parameter should be additionally aligned to the system’s allocation granularity (ie: getSystemAllocationGranularity()). Under this object however, this additional alignment requirement is handled internally so that page alignment is provided to match Linux behavior. However, this does mean that additional pages may be mapped into memory that are just transparently skipped by the view object. When on Windows, it would be a best practice to align the view offsets to a multiple of the system allocation granularity (usually 64KB) instead of just the page size (usually 4KB). This larger offset granularity behavior will also work properly on Linux.

Parameters
  • offset[in] The offset in bytes into the shared memory region where this view should start. This value should be aligned to the system page size. If this is not aligned to the system page size, it will be aligned to the start of the page that the requested offset is in. The actual mapped offset can be retrieved from the new view object with getOffset() if the view is successfully mapped. This defaults to 0 bytes into the shared memory region (ie: the start of the region).

  • size[in] The number of bytes of the shared memory region starting at the byte offset specified by offset to map into the new view. This should be a multiple of the system page size. If it is not a multiple of the system page size, it will be rounded up to the next page size during the mapping. This size will also be clamped to the size of the shared memory region (less the offset). This may be 0 to indicate that the remainder of the region starting at the given offset should be mapped into the new view. This defaults to 0.

  • access[in] The access mode to use for the new view. This can be set to AccessMode::eDefault to use the same permissions as were originally used to open the shared memory region. This will fail if the requested mode attempts to grant greater permissions to the shared memory region than were used to open it (ie: cannot create a read-write view of a read-only shared memory region). This defaults to AccessMode::eDefault.

Returns

A new view object representing the mapped view of the shared memory region. This must be deleted when the mapped region is no longer needed.

Returns

nullptr if the new view cannot be created or if an invalid set of parameters is passed in.

inline void close(bool forceUnlink = false)

Closes this shared memory region.

Remark

This closes the currently open shared memory region on this object. This will put the object back into a state where a new region can be opened. This call will be ignored if no region is currently open. This region can be closed even if views still exist on the current region. The existing views will not be closed, invalidated, or unmapped by closing this shared memory region.

Note

The current region will be automatically closed when this object is deleted.

Parameters

forceUnlinkLinux: If true, the shared memory region name is disassociated with the currently opened shared memory. If the shared memory is referenced by other processes (or other SharedMemory objects) it remains open and valid, but no additional SharedMemory instances will be able to open the same shared memory region by name. Attempts to open the shared memory by name will fail, and attempts to create the shared memory by name will create a new shared memory region. If false, the shared memory region will be unlinked when the final SharedMemory object using it has closed. Windows: this parameter is ignored.

inline bool isOpen() const

Indicates whether the SharedMemory object is currently open or not.

Returns

true if the SharedMemory object has a region open; false otherwise.

inline OpenToken getOpenToken()

Retrieves the token used to open this same SHM region elsewhere.

Returns

The open token object. This token is valid as long as the SharedMemory object that returned it has not been closed. If this object needs to persist, it should be copied to a caller owned buffer.

Returns

nullptr if no SHM region is currently open on this object.

inline size_t getSize() const

Retrieves the total size of the current shared memory region in bytes.

Returns

The size in bytes of the currently open shared memory region. Note that this will be aligned to the system page size if the original value passed into the open() call was not aligned.

inline AccessMode getAccessMode() const

The maximum access mode allowed for the current shared memory region.

Return values

AccessMode::eDefault – if no SHM region is currently open on this object.

Returns

The memory access mode that the current shared memory region was created with. This will never be AccessMode::eDefault when this SHM region is valid.

inline size_t getSystemPageSize() const

Retrieves the system memory page size.

Returns

The size of the system’s memory page size in bytes.

inline size_t getSystemAllocationGranularity() const

Retrieves the system allocation granularity.

Returns

The system’s allocation granularity in bytes.

Public Static Attributes

static constexpr uint32_t fCreateMakeUnique = 0x00000001

Flag to indicate that a unique region name should be generated from the given base name in create().

This will allow the call to be more likely to succeed even if a region with the same base name already exists in the system. Note that using this flag will lead to a slightly longer open token being generated.

static constexpr uint32_t fQuiet = 0x00000002

Flag to indicate that failure should not be reported as an error log.

static constexpr uint32_t fNoMutexLock = 0x00000004

Flag to indicate that no mutexes should be locked during this operation.

Note

Currently only Linux is affected by this flag.

Warning

Use of this flag could have interprocess and thread safety issues! Use with utmost caution!

class OpenToken

An opaque token object used to open an existing SHM region or to retrieve from a newly created SHM region to pass to another client to open it.

A token is only valid if it contains data for a shared memory region that is currently open. It will cause open() to fail if it is not valid however.

Public Functions

inline OpenToken()

Constructor: initializes an empty token.

inline OpenToken(const char *base64)

Constructor: creates a new open token from a base64 encoded string.

Remark

This will create a new open token from a base64 encoded data blob received from another open token. Both the base64 and decoded representations will be stored. Only the base64 representation will be able to be retrieved later. No validation will be done on the token data until it is passed to SharedMemory::open().

Parameters

base64[in] A base64 encoded data blob containing the open token information. This may be received through any means from another token. This string must be null terminated. This may not be nullptr or an empty string.

inline OpenToken(const OpenToken &token)

Copy constructor: copies an open token from another one.

Parameters

token[in] The open token to be copied. This does not necessarily need to be a valid token.

inline OpenToken(OpenToken &&token)

Move constructor: moves an open token from another one to this one.

Parameters

token[inout] The open token to be moved from. The data in this token will be stolen into this object and the source token will be cleared out.

inline ~OpenToken()
inline explicit operator bool() const

Validity check operator.

Returns

true if this object contains token data.

Returns

false if this object was not successfully constructed or does not contain any actual token data.

inline bool operator!() const

Validity check operator.

Returns

true if this object does not contain any token data.

Returns

false if this object contains token data.

inline bool operator==(const OpenToken &token) const

Token equality comparison operator.

Parameters

token[in] The token to compare this one to.

Returns

true if the other token contains the same data this one does.

Returns

false if the other token contains different data from this one.

inline bool operator!=(const OpenToken &token) const

Token inequality comparison operator.

Parameters

token[in] The token to compare this one to.

Returns

true if the other token contains different data from this one.

Returns

false if the other token contains the same data this one does.

inline OpenToken &operator=(const OpenToken &token)

Copy assignment operator.

Parameters

token[in] The token to be copied.

Returns

A reference to this object.

inline OpenToken &operator=(OpenToken &&token)

Move assignment operator.

Parameters

token[inout] The token to be moved from. This source object will have its data token into this object, and the source will be cleared.

Returns

A reference to this object.

inline const char *getBase64Token()

Retrieves the token data in base64 encoding.

Returns

The token data encoded as a base64 string. This will always be null terminated. This token data string will be valid as long as this object still exists. If the caller needs the data to persist, the returned string must be copied.

Returns

nullptr if this token doesn’t contain any data or memory could not be allocated to hold the base64 encoded string.

Protected Functions

inline OpenToken(const void *tokenData, size_t size)

Constructor: initializes a new token with a specific data block.

Parameters
  • tokenData[in] The data to be copied into the new token. This may not be nullptr.

  • size[in] The size of the tokenData block in bytes.

Returns

No return value.

inline bool isValid() const

Tests if this token is valid.

Returns

true if this token contains data. Returns false otherwise.

inline uint8_t *getToken() const

Retrieves the specific token data from this object.

Returns

The token data contained in this object.

inline size_t getSize() const

Retrieves the size in bytes of the token data in this object.

Returns

The size of the token data in bytes. Returns 0 if this object doesn’t contain any token data.

inline void clear()

Clears the contents of this token object.

Remark

This clears out the contents of this object. Upon return, the token will no longer be considered valid and can no longer be used to open an existing shared memory object.

Returns

No return value.

Protected Attributes

uint8_t *m_data

The raw binary data for the token.

This will be nullptr if the token does not contain any data.

char *m_base64

The base64 encoded version of this token’s data.

This will be nullptr if no base64 data has been retrieved from this object and it was created from a source other than a base64 string.

size_t m_size

The size of the binary data blob m_data in bytes.

Friends

friend class SharedMemory
class View

Represents a single mapped view into an open shared memory region.

The region will remain mapped in memory and valid as long as this object exists. When this view object is destroyed, the region will be flushed and unmapped. All view objects are still valid even after the shared memory object that created them have been closed or destroyed.

View objects may neither be copied nor manually constructed. They may however be moved with either a move constructor or move assignment. Note that for safety reasons, the moved view must be move assigned or move constructed immediately at declaration time. This eliminates the possibility of an unmapped from being acquired and prevents the need to check its validity at any given time. Once moved, the original view object will no longer be valid and should be immediately deleted. The moved view will not be unmapped by deleting the original view object.

Public Functions

inline View(View &&view)

Move constructor: moves another view object into this one.

Parameters

view[in] The other object to move into this one.

inline ~View()
inline View &operator=(View &&view)

Move assignment operator: moves another object into this one.

Parameters

view[in] The other object to move into this one.

Returns

A reference to this object.

inline void *getAddress()

Retrieves the mapped address of this view.

Returns

The address of the mapped region. This region will extend from this address through the following getSize() bytes minus 1. This address will always be aligned to the system page size.

inline size_t getSize() const

Retrieves the size of this mapped view.

Returns

The total size in bytes of this mapped view. This region will extend from the mapped address returned from getAddress(). This will always be aligned to the system page size. This size represents the number of bytes that are accessible in this view starting at the given offset (getOffset()) in the original shared memory region.

inline size_t getOffset() const

Retrieves the offset of this view into the original mapping object.

Returns

The offset of this view in bytes into the original shared memory region. This will always be aligned to the system page size. If this is 0, this indicates that this view starts at the beginning of the shared memory region that created it. If this is non-zero, this view only represents a portion of the original shared memory region.

inline AccessMode getAccessMode() const

Retrieves the access mode that was used to create this view.

Returns

The access mode used to create this view. This will always be a valid memory access mode and will never be AccessMode::eDefault. This can be used to determine what the granted permission to the view is after creation if it originally asked for the AccessMode::eDefault mode.

Protected Functions

inline View()

Constructor: protected default constructor.

Remark

This initializes an empty view object. This is protected to prevent new empty local declarations from being default constructed so that we don’t need to worry about constantly checking for invalid views.

View(const View&) = delete
View &operator=(const View&) = delete
View &operator=(const View*) = delete
inline bool map(SharedHandle handle, size_t offset, size_t size, AccessMode access, size_t allocGran)

Maps a view of a shared memory region into this object.

Parameters
  • handle[in] The handle to the open shared memory region. This must be a valid handle to the region.

  • offset[in] The offset in bytes into the shared memory region where the new mapped view should start. This must be aligned to the system page size.

  • size[in] The size in bytes of the portion of the shared memory region that should be mapped into the view. This must not extend past the end of the original shared memory region once added to the offset. This must be aligned to the system page size.

  • access[in] The requested memory access mode for the view. This must not be AccessMode::eDefault. This may not specify greater permissions than the shared memory region itself allows (ie: requesting read-write on a read-only shared memory region).

  • allocGran[in] The system allocation granularity in bytes.

Returns

true if the new view is successfully mapped.

Returns

false if the new view could not be mapped.

inline void unmap()

Unmaps this view from memory.

Remark

This unmaps this view from memory. This should only be called on the destruction of this object.

Returns

No return value.

inline void init()

Initializes this object to an empty state.

Protected Attributes

void *m_address

The mapped address of this view.

size_t m_size

The size of this view in bytes.

size_t m_offset

The offset of this view in bytes into the shared memory region.

size_t m_pageOffset

The page offset in bytes from the start of the mapping.

AccessMode m_access

The granted access permissions for this view.

Friends

friend class SharedMemory