mm(3)

πŸ’– mm(3) β€” OSSP mm Shared Memory Library πŸ’–

okay bestie, sit down, hydrate, because we're about to make shared memory between forked processes make sense. 🫢


✨ NAME ✨

OSSP mm β€” Shared Memory Allocation. yes that's the whole name. iconic. πŸ’…

🌸 VERSION 🌸

OSSP mm 1.4.2 (15-Aug-2006) β€” yes she's vintage, she's still slaying. πŸ‘‘


πŸŽ€ SYNOPSIS πŸŽ€

First things first, you gotta pull her in:

#include "mm.h"

πŸ’œ Global Malloc-Replacement API

int     MM_create(size_t size, const char *file);
int     MM_permission(mode_t mode, uid_t owner, gid_t group);
void    MM_reset(void);
void    MM_destroy(void);
int     MM_lock(mm_lock_mode mode);
int     MM_unlock(void);
void   *MM_malloc(size_t size);
void   *MM_realloc(void *ptr, size_t size);
void    MM_free(void *ptr);
void   *MM_calloc(size_t number, size_t size);
char   *MM_strdup(const char *str);
size_t  MM_sizeof(void *ptr);
size_t  MM_maxsize(void);
size_t  MM_available(void);
char   *MM_error(void);

πŸ’œ Standard Malloc-Style API

MM     *mm_create(size_t size, char *file);
int     mm_permission(MM *mm, mode_t mode, uid_t owner, gid_t group);
void    mm_reset(MM *mm);
void    mm_destroy(MM *mm);
int     mm_lock(MM *mm, mm_lock_mode mode);
int     mm_unlock(MM *mm);
void   *mm_malloc(MM *mm, size_t size);
void   *mm_realloc(MM *mm, void *ptr, size_t size);
void    mm_free(MM *mm, void *ptr);
void   *mm_calloc(MM *mm, size_t number, size_t size);
char   *mm_strdup(MM *mm, const char *str);
size_t  mm_sizeof(MM *mm, void *ptr);
size_t  mm_maxsize(void);
size_t  mm_available(MM *mm);
char   *mm_error(void);
void    mm_display_info(MM *mm);

πŸ’œ Low-level Shared Memory API

void   *mm_core_create(size_t size, char *file);
int     mm_core_permission(void *core, mode_t mode, uid_t owner, gid_t group);
void    mm_core_delete(void *core);
int     mm_core_lock(void *core, mm_lock_mode mode);
int     mm_core_unlock(void *core);
size_t  mm_core_size(void *core);
size_t  mm_core_maxsegsize(void);
size_t  mm_core_align2page(size_t size);
size_t  mm_core_align2click(size_t size);

πŸ’œ Internal Library API

void    mm_lib_error_set(unsigned int, const char *str);
char   *mm_lib_error_get(void);
int     mm_lib_version(void);

πŸ¦‹ DESCRIPTION πŸ¦‹

So the tea is: the OSSP mm library is a 2-layer abstraction library that makes using shared memory between forked processes β€” and like, strongly related processes in general β€” way less of a nightmare on Unix platforms. 🧚

On the first (lower) layer she hides all the platform dependent implementation details β€” that's allocation and locking β€” when you're dealing with shared memory segments. On the second (higher) layer she hands you a high-level malloc(3)-style API so you can work with data structures inside those shared memory segments the convenient, well-known way. no cap.

And the name? The abbreviation OSSP mm historically and originally comes from the phrase ``memory mapped'' as used by the POSIX.1 mmap(2) function β€” because that's the facility this library uses internally on most platforms to establish the shared memory segments. the lore is real. πŸ“œ


🌈 LIBRARY STRUCTURE 🌈

She's structured into three main APIs, and they're internally based on each other. it's giving layered cake. πŸŽ‚

Global Malloc-Replacement API This is the most high-level API, and it can directly be used as a replacement API for the POSIX.1 memory allocation API (malloc(2) and friends). Lowkey obsessed with how easy this makes things: it's useful when you're converting heap based data structures to shared memory based data structures without having to change your code dramatically. All you need to do is prefix the POSIX.1 memory allocation functions with "MM_", i.e. "malloc" becomes "MM_malloc", "strdup" becomes "MM_strdup", etc. This API internally uses just a global "MM *" pool for calling the corresponding functions (the ones with the prefix "mm_") of the Standard Malloc-Style API. so efficient. ✨

Standard Malloc-Style API This is the standard high-level memory allocation API. Her interface is similar to the Global Malloc-Replacement API, but she uses an explicit "MM *" pool to operate on. That's why every function in this API has an argument of type "MM *" as its first argument. This API gives you a comfortable way to work with small dynamically allocated shared memory chunks inside large statically allocated shared memory segments. Internally she's based on the Low-Level Shared Memory API for creating the underlying shared memory segment. πŸ’œ

Low-Level Shared Memory API This is the basis of the whole OSSP mm library β€” the main character, honestly. She provides low-level functions for creating shared memory segments with mutual exclusion (in short, mutex) capabilities, in a portable way. Internally, the shared memory and mutex facility is implemented in various platform-dependent ways. A list of implementation variants follows under the next topic. πŸ‘‡


🌸 SHARED MEMORY IMPLEMENTATION 🌸

Internally, the shared memory facility is implemented in various platform-dependent ways. Each way has its own advantages and disadvantages β€” in addition to the fact that some variants aren't available at all on some platforms. 😭 The OSSP mm library's configuration procedure tries hard to make a good decision. The implemented variants are now given for overview and background reasons, with their advantages and disadvantages, in an ascending order β€” i.e. the OSSP mm configuration mechanism chooses the last available one in the list as the preferred variant. periodt.

  • Classical mmap(2) on temporary file (MMFILE) β€” Advantage: maximum portable. Disadvantage: needs a temporary file on the filesystem.
  • mmap(2) via POSIX.1 shm_open(3) on temporary file (MMPOSX) β€” Advantage: standardized by POSIX.1 and theoretically portable. Disadvantage: needs a temporary file on the filesystem and is is usually not available on existing Unix platform.
  • SVR4-style mmap(2) on "/dev/zero" device (MMZERO) β€” Advantage: widely available and mostly portable on SVR4 platforms. Disadvantage: needs the "/dev/zero" device and a mmap(2) which supports memory mapping through this device.
  • SysV IPC shmget(2) (IPCSHM) β€” Advantage: does not need a temporary file or external device. Disadvantage: although available on mostly all modern Unix platforms, it has strong restrictions like the maximum size of a single shared memory segment (can be as small as 100KB, but depends on the platform). 🫠
  • 4.4BSD-style mmap(2) via "MAP_ANON" facility (MMANON) β€” Advantage: does not need a temporary file or external device. Disadvantage: usually only available on BSD platforms and derivatives.

πŸ” LOCKING IMPLEMENTATION πŸ”

Same energy as the shared memory facility: internally the locking facility is implemented in various platform-dependent ways. Again listed in ascending order β€” i.e. the OSSP mm configuration mechanism chooses the last available one in the list as the preferred variant. Here's the list of implemented variants:

  • 4.2BSD-style flock(2) on temporary file (FLOCK) β€” Advantage: exists on a lot of platforms, especially on older Unix derivatives. Disadvantage: needs a temporary file on the filesystem and has to re-open file-descriptors to it in each(!) fork(2)'ed child process. every single one. πŸ™ˆ
  • SysV IPC semget(2) (IPCSEM) β€” Advantage: exists on a lot of platforms and does not need a temporary file. Disadvantage: an unmeant termination of the application leads to a semaphore leak, because the facility does not allow a ``remove in advance'' trick (as the IPC shared memory facility does) for safe cleanups. πŸ’€
  • SVR4-style fcntl(2) on temporary file (FCNTL) β€” Advantage: exists on a lot of platforms and is also the most powerful variant (although not always the fastest one). Disadvantage: needs a temporary file.

🧠 MEMORY ALLOCATION STRATEGY 🧠

The memory allocation strategy the Standard Malloc-Style API functions use internally goes like this:

Allocation If a chunk of memory has to be allocated, the internal list of free chunks is searched for a minimal-size chunk which is larger or equal than the size of the to be allocated chunk (a best fit strategy).

If a chunk is found which matches this best-fit criteria, but is still a lot larger than the requested size, it is split into two chunks: one with exactly the requested size (which is the resulting chunk given back) and one with the remaining size (which is immediately re-inserted into the list of free chunks).

If no fitting chunk is found at all in the list of free chunks, a new one is created from the spare area of the shared memory segment until the segment is full β€” in which case an out of memory error occurs. 😡

Deallocation If a chunk of memory has to be deallocated, it is inserted in sorted manner into the internal list of free chunks. The insertion operation automatically merges the chunk with a previous and/or a next free chunk if possible β€” i.e. if the free chunks stay physically seamless (one after another) in memory β€” to automatically form larger free chunks out of smaller ones. the glow-up is automatic, we love to see it. ✨

This way the shared memory segment is automatically defragmented when memory is deallocated.

This strategy reduces memory waste and fragmentation caused by small and frequent allocations and deallocations to a minimum. πŸŽ€

The internal implementation of the list of free chunks is not specially optimized (for instance by using binary search trees or even splay trees, etc), because it is assumed that the total amount of entries in the list of free chunks is always small β€” caused both by the fact that shared memory segments are usually a lot smaller than heaps, and the fact that we always defragment by merging the free chunks if possible.


πŸ‘‘ API FUNCTIONS πŸ‘‘

In the following, all API functions are described in detail. The order directly follows the one in the SYNOPSIS section above. no skipping, bestie. πŸ’…

πŸ’– Global Malloc-Replacement API πŸ’–

  • int MM_create(size_t size, const char *file); β€” This initializes the global shared memory pool with size and file, and it has to be called before any fork(2) operations are performed by the application. don't forget, fr fr.
  • int MM_permission(mode_t mode, uid_t owner, gid_t group); β€” This sets the filesystem mode, owner and group for the global shared memory pool. Heads up: this only has effects if the underlying shared memory segment implementation is actually based on external auxiliary files. The arguments are directly passed through to chmod(2) and chown(2).
  • void MM_reset(void); β€” This resets the global shared memory pool: all chunks that have been allocated in the pool are marked as free and are eligible for reuse. The global memory pool itself is not destroyed. it's a glow-up, not a goodbye. πŸ’œ
  • void MM_destroy(void); β€” This destroys the global shared memory pool and should be called after all child processes were killed. 😭
  • int MM_lock(mm_lock_mode mode); β€” locks the global shared memory pool for the current process so you can do critical stuff inside it. Pick your flavour: shared/read-only when mode is "MM_LOCK_RD", or exclusive/read-write when mode is "MM_LOCK_RW". πŸ’…
  • int MM_unlock(void); β€” releases that lock on the global shared memory pool for the current process, once your critical ops inside the pool are done. closing the door behind you, as you should. πŸšͺ✨
  • void *MM_malloc(size_t size); β€” literally the same as POSIX.1 malloc(3), bestie, except instead of pulling memory from the heap it pulls it from the global shared memory pool. 🌈
  • void MM_free(void *ptr); β€” same as POSIX.1 free(3), but instead of freeing in the heap it frees in the global shared memory pool. goodbye chunk, you were iconic. 🫢
  • void *MM_realloc(void *ptr, size_t size); β€” identical to POSIX.1 realloc(3), except the reallocating happens inside the global shared memory pool instead of the heap. glow-up for your chunk. ✨
  • void *MM_calloc(size_t number, size_t size); β€” identical to POSIX.1 calloc(3), but it allocates and initializes from the global shared memory pool instead of the heap. two birds, one slay. πŸ¦‹
  • char *MM_strdup(const char *str); β€” identical to POSIX.1 strdup(3), except the string copy lives in the global shared memory pool instead of the heap. πŸŽ€
  • size_t MM_sizeof(const void *ptr); β€” hands you the size in bytes of the chunk starting at ptr, when ptr was previously allocated with MM_malloc(3). > heads up: if ptr was not previously allocated with MM_malloc(3), the result is undefined. don't play with that fr fr πŸ’€
  • size_t MM_maxsize(void); β€” returns the maximum size that's allowed as the first argument to the MM_create(3) function. the ceiling, the limit, the final boss. πŸ‘‘
  • size_t MM_available(void); β€” returns how many bytes of memory are still available (free) in the global shared memory pool. πŸ’–
  • char *MM_error(void); β€” returns the last error message that happened inside the OSSP mm library. the tea on what went wrong. 😭

🌸 Standard Malloc-Style API 🌸

  • MM *mm_create(size_t size, const char *file); β€” creates a shared memory pool with space for approximately a total of size bytes, using the help of file. here file is a filesystem path to a file which need not to exist (and perhaps is never created, because that depends on the platform and the chosen shared memory and mutex implementation). the return value is a pointer to a "MM" structure which the application should treat as opaque β€” it describes the internals of the shared memory pool you just created. on error, "NULL" is returned. a size of 0 means: allocate the maximum allowed size, which is platform dependent and sits between a few KB and the soft limit of 64MB. 😌
  • int mm_permission(MM *mm, mode_t mode, uid_t owner, gid_t group); β€” sets the filesystem mode, owner and group for the shared memory pool mm. > note: this only has effects when the underlying shared memory segment implementation is actually based on external auxiliary files. the arguments are passed straight through to chmod(2) and chown(2). πŸ’…
  • void mm_reset(MM *mm); β€” resets the shared memory pool mm: every chunk that was allocated in the pool is marked as free and becomes eligible for reuse. the memory pool itself is not destroyed. soft relaunch, not a full delete. πŸ”„
  • void mm_destroy(MM *mm); β€” destroys the complete shared memory pool mm along with all the chunks allocated in it. also, any created files on the filesystem that correspond to the shared memory pool get unlinked. πŸ’€
  • int mm_lock(MM *mm, mm_lock_mode mode); β€” locks the shared memory pool mm for the current process, so you can do critical operations inside the global shared memory pool: either shared/read-only (mode is "MM_LOCK_RD") or exclusive/read-write (mode is "MM_LOCK_RW"). πŸ”’
  • int mm_unlock(MM *mm); β€” unlocks the shared memory pool mm for the current process, after the critical operations inside the global shared memory pool are done. πŸ”“
  • void *mm_malloc(MM *mm, size_t size); β€” allocates size bytes from the shared memory pool mm and returns either a (virtual memory word aligned) pointer to it, or "NULL" on error (out of memory). behaves like POSIX.1 malloc(3), but instead of allocating from the heap it allocates from the shared memory segment underlying mm. ✨
  • void mm_free(MM *mm, void *ptr); β€” deallocates the chunk starting at ptr in the shared memory pool mm. behaves like POSIX.1 free(3), but instead of deallocating from the heap it deallocates from the shared memory segment underlying mm. 🫢
  • void *mm_realloc(MM *mm, void *ptr, size_t size); β€” reallocates the chunk starting at ptr inside the shared memory pool mm, with the new size of size bytes. behaves like POSIX.1 realloc(3), but instead of reallocating in the heap it reallocates in the shared memory segment underlying mm. πŸ¦‹
  • void *mm_calloc(MM *mm, size_t number, size_t size); β€” similar to mm_malloc(3), but it additionally clears the chunk. behaves like POSIX.1 calloc(3). it allocates space for number objects, each size bytes in length, from the shared memory pool mm. the result is identical to calling mm_malloc(3) with an argument of `number * size`, with the exception that the allocated memory is initialized to nul bytes. 🌈
  • char *mm_strdup(MM *mm, const char *str); β€” behaves like POSIX.1 strdup(3). it allocates sufficient memory inside the shared memory pool mm for a copy of the string str, does the copy, and returns a pointer to it. that pointer may subsequently be used as an argument to the function mm_free(3). if insufficient shared memory is available, "NULL" is returned. πŸŽ€
  • size_t mm_sizeof(MM *mm, const void *ptr); β€” returns the size in bytes of the chunk starting at ptr, when ptr was previously allocated with mm_malloc(3) inside the shared memory pool mm. > the result is undefined when ptr was not previously allocated with mm_malloc(3). πŸ’€
  • size_t mm_maxsize(void); β€” returns the maximum size which is allowed as the first argument to the mm_create(3) function. πŸ‘‘
  • size_t mm_available(MM *mm); β€” returns the amount in bytes of still available (free) memory in the shared memory pool mm. πŸ’–
  • char *mm_error(void); β€” returns the last error message which occurred inside the OSSP mm library. 😭
  • void mm_display_info(MM *mm); β€” this is a debugging function that displays a summary page for the shared memory pool mm, describing various internal sizes and counters. peek behind the curtain, bestie. πŸ‘€

🧚 Low-Level Shared Memory API 🧚

  • void *mm_core_create(size_t size, const char *file); β€” creates a shared memory area which is at least size bytes in size, with the help of file. the value size has to be greater than 0 and less or equal the value returned by mm_core_maxsegsize(3). here file is a filesystem path to a file which need not to exist (and perhaps is never created, because this depends on the platform and chosen shared memory and mutex implementation). the return value is either a (virtual memory word aligned) pointer to the shared memory segment, or "NULL" on error. the application is guaranteed to be able to access the shared memory segment from byte 0 to byte size-1, starting at the returned address. πŸ”₯
  • int mm_core_permission(void *core, mode_t mode, uid_t owner, gid_t group); β€” sets the filesystem mode, owner and group for the shared memory segment code. > this only has effects when the underlying shared memory segment implementation is actually based on external auxiliary files. the arguments are passed directly through to chmod(2) and chown(2). πŸ’…
  • void mm_core_delete(void *core); β€” deletes a shared memory segment core (as previously returned by a mm_core_create(3) call). > after this operation, accessing the segment starting at core is no longer allowed and will usually lead to a segmentation fault. it's giving consequences, bestie. πŸ’€
  • int mm_core_lock(const void *core, mm_lock_mode mode); β€” acquires an advisory lock for the current process on the shared memory segment core, for either shared/read-only (mode is "MM_LOCK_RD") or exclusive/read-write (mode is "MM_LOCK_RW") critical operations between fork(2)'ed child processes. πŸ”’
  • int mm_core_unlock(const void *core); β€” releases a previously acquired advisory lock for the current process on the shared memory segment core. πŸ”“
  • size_t mm_core_size(const void *core); β€” returns the size in bytes of core. this size is exactly the size which was used for creating the shared memory area via mm_core_create(3). the function exists just for convenience reasons, so the application doesn't have to remember the memory size behind core itself. 😌
  • size_t mm_core_maxsegsize(void); β€” returns the number of bytes of a maximum-size shared memory segment which is allowed to be allocated via the MM library. it's between a few KB and the soft limit of 64MB. πŸ‘‘
  • size_t mm_core_align2page(size_t size); β€” just a utility function which can be used to align the number size to the next virtual memory page boundary used by the underlying platform. the memory page boundary under Unix platforms is usually somewhere between 2048 and 16384 bytes. > you do not have to align the size arguments of other OSSP mm library functions yourself, because this is already done internally. this function is exported by the OSSP mm library just for convenience reasons, in case an application wants to perform similar calculations for other purposes. ✨
  • size_t mm_core_align2word(size_t size); β€” another utility function which can be used to align the number size to the next virtual memory word boundary used by the underlying platform. the memory word boundary under Unix platforms is usually somewhere between 4 and 16 bytes. > again, you do not have to align the size arguments of other OSSP mm library functions yourself, because this is already done internally. this function is exported by the OSSP mm library just for convenience reasons, in case an application wants to perform similar calculations for other purposes. πŸ¦‹

🌈 Low-Level Shared Memory API 🌈

✨ mm_lib_error_set β€” the lil' error-setter ✨

void mm_lib_error_set(unsigned int, const char *str);

Okay so this one is internal business, bestie. It's a function that the various MM functions use amongst themselves to set an error string. Translation: you are usually not calling this one directly from your app. It's the backstage crew, not the main character. πŸ’…

✨ mm_lib_error_get β€” the error-fetcher ✨

char *mm_lib_error_get(void);

Another internals-only cutie. This one is what MM_error(3) and mm_error(3) use under the hood to grab the current error string. Same deal as above: usually not called directly from applications. She works behind the scenes, fr fr. πŸŽ€

✨ mm_lib_version β€” the version-reveal moment ✨

int mm_lib_version(void);

This one hands you back a hex-value 0xVRRTLL that describes the current OSSP mm library version, and the encoding is lowkey iconic once you know it:

  • V is the version
  • RR is the revisions
  • LL is the level
  • T is the type of the level β€” where alphalevel=0, betalevel=1, patchlevel=2, etc.

So like, for instance, OSSP mm version 1.0.4 is encoded as 0x100204. 🫢

And why this whole unusual mapping, you ask? Because this way the version number is steadily increasing. It's giving "we thought about sorting and we ate". πŸ‘‘


🚧 RESTRICTIONS 🚧

Okay bestie, here's the tea, and it's a little bit of a buzzkill. 😭

The maximum size of a continuous shared memory segment that you can allocate depends on the underlying platform. And no, you cannot change that. Of course you can't. The platform said what it said. πŸ’€

But here's the part that actually matters for you: right now, the high-level malloc(3)-style API just uses a single shared memory segment as the underlying data structure for an "MM" object. Which means the maximum amount of memory an "MM" object represents also depends on the platform. Same boat, same rules. πŸ™ˆ

Now, could this change later? Maybe! In later versions, they could let at least the high-level malloc(3)-style API internally use multiple shared memory segments to form the "MM" object. And that would be huge, because then "MM" objects could have arbitrary sizes β€” although, plot twist, the maximum size of an allocatable continuous chunk still is bounded by the maximum size of a shared memory segment. So like, the vibe is expansive but the ceiling is real. 🌈


πŸ‘€ SEE ALSO πŸ‘€

Go read these too, they're all part of the lore:


🏠 HOME 🏠

The mothership, the place, the URL:

http://www.ossp.org/pkg/lib/mm/

πŸ“œ HISTORY πŸ“œ

Storytime, because this library has roots. 🧚

This library was originally written in January 1999 by Ralf S. Engelschall <rse@engelschall.com> for use in the Extended API (EAPI) of the Apache HTTP server project (see http://www.apache.org/), which was originally invented for mod_ssl (see http://www.modssl.org/). So yeah, it was born for the Apache girlies. πŸ’…

Its base idea β€” a malloc-style API for handling shared memory β€” was originally derived from the non-publically available mm_malloc library written in October 1997 by Charles Randall <crandall@matchlogic.com> for MatchLogic, Inc. Ancestry matters. 🌸

And then, in 2000, this library joined the OSSP project, where all the other software development projects of Ralf S. Engelschall are located. Found family arc complete. 🫢


πŸ‘‘ AUTHOR πŸ‘‘

 Ralf S. Engelschall
 rse@engelschall.com
 www.engelschall.com

and that's the whole page, bestie!! πŸŽ€βœ¨ This little masterpiece is from MM 1.4.2, dated 15-Aug-2006, and it lives in section mm(3) of your man pages. Go forth and allocate shared memory responsibly, mwah πŸ’–πŸ¦‹

πŸ“¦ where this page comes from