mmap() with POSIX Shared Memory

mmap() with POSIX Shared Memory
Chapter 54 β€” Part 4 of 5 | The Linux Programming Interface
Step 2
of 2-Step Process
MAP_SHARED
Required Flag
PROT_*
Protection Flags

πŸ“š Chapter 54 Series
Part 1: Introduction & Overview Part 2: shm_open() Part 3: ftruncate() & fstat() Part 4: mmap() with POSIX shm Part 5: shm_unlink() & Examples

πŸ”‘ Key Concepts in This Part
mmap() MAP_SHARED PROT_READ PROT_WRITE MAP_FAILED munmap() Virtual Address Space Page Cache Shared Pages Zero-copy IPC

mmap() β€” The Heart of Shared Memory IPC

mmap() maps a file (or shared memory object) into the calling process’s virtual address space. With MAP_SHARED, all processes that map the same object see each other’s writes immediately β€” because they all share the same underlying physical memory pages.

This is the fastest form of IPC: writing to shared memory is as fast as writing to a normal variable. No system calls are involved after the initial mmap().

πŸ“‹ mmap() β€” Function Signature
#include <sys/mman.h>

void *mmap(void   *addr,    /* Suggested address (usually NULL) */
           size_t  length,  /* Number of bytes to map           */
           int     prot,    /* Protection flags: PROT_READ etc  */
           int     flags,   /* MAP_SHARED or MAP_PRIVATE etc    */
           int     fd,      /* File descriptor from shm_open()  */
           off_t   offset); /* Offset into object (usually 0)   */

/* Returns: mapped address on success, MAP_FAILED on error */

Argument Typical Value for POSIX shm Notes
addr NULL Let the kernel choose a suitable address
length Same as ftruncate() size How many bytes to map from the object
prot PROT_READ | PROT_WRITE Must match how you opened with shm_open()
flags MAP_SHARED Writes are visible to all processes sharing the object
fd From shm_open() Can close this fd after mmap() returns
offset 0 Map from the beginning of the object

πŸ›‘οΈ Protection Flags (prot) β€” What You Can Do

The prot argument controls what operations are allowed on the mapped region. The protection you request must be compatible with the access mode used in shm_open().

PROT_NONE
No access at all. Any access causes a segfault.
PROT_READ
Pages can be read. Required for reading the shared data.
PROT_WRITE
Pages can be written. Use with O_RDWR in shm_open().
PROT_EXEC
Pages can be executed (code). Rarely used with shm.
⚠️ Rule: If you opened the object with O_RDONLY, you cannot mmap with PROT_WRITE. The kernel will return EACCES. The write permission in prot must not exceed what was granted at shm_open() time.

πŸ”€ MAP_SHARED vs MAP_PRIVATE β€” Why MAP_SHARED is Required for IPC
MAP_SHARED βœ… for IPC
  • Writes are immediately visible to all other processes sharing the same object
  • All processes see the same physical pages
  • Changes persist in the shared memory object
  • This is what you want for inter-process communication
MAP_PRIVATE ❌ for IPC
  • Creates a copy-on-write mapping private to this process
  • Writes are NOT visible to other processes
  • Changes do not propagate back to the underlying object
  • Useful for loading read-only data (like code) β€” not for IPC

πŸ–₯️ How Two Processes Share the Same Physical Pages

When two processes call mmap() with MAP_SHARED on the same object, the kernel arranges their page tables to point at the same physical pages:

Process A
Virtual Addr
0x7f00000000
β†’ Physical Page X
β†˜
page table
β†—

Physical RAM
Shared Pages
/dev/shm/ep_demo
[ data written here ]
β†—
page table
β†˜

Process B
Virtual Addr
0x7e00000000
β†’ Physical Page X
Both virtual addresses point to the same physical page β€” writes by A are instantly visible to B

🧹 munmap() β€” Unmapping the Region

When you are done with the shared memory, call munmap() to release the mapping from the process’s virtual address space.

#include <sys/mman.h>

int munmap(void *addr, size_t length);
/* Returns: 0 on success, -1 on error */
What munmap() does
  • Removes the mapping from the virtual address space
  • Flushes any pending writes to the shared object
  • Does NOT delete the shared memory object (use shm_unlink() for that)
Important notes
  • addr must be the exact address returned by mmap()
  • length must match what you passed to mmap()
  • Mappings are also automatically removed when the process exits

πŸ’» Code Example: Sharing a Struct via mmap() + POSIX shm

A common real-world pattern is to cast the mapped address to a pointer to a struct that defines the shared data layout. Both processes use the same struct definition.

/*
 * shared_struct.h β€” Shared data layout used by writer and reader
 * Both processes include this header.
 */
#ifndef SHARED_STRUCT_H
#define SHARED_STRUCT_H

#include <stdint.h>

#define SHM_NAME  "/ep_struct_demo"
#define SHM_SIZE  sizeof(struct SharedData)

/* Data layout in shared memory */
struct SharedData {
    int32_t  counter;          /* Incremented by writer     */
    int32_t  ready;            /* 1 = new data available    */
    char     message[128];     /* Text message from writer  */
};

#endif
/*
 * shm_writer.c β€” Writes to shared memory via mmap()
 * Compile: gcc shm_writer.c -o shm_writer -lrt
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "shared_struct.h"

int main(void)
{
    int fd;
    struct SharedData *shdata;

    /* Create shared memory object */
    fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0660);
    if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }

    if (ftruncate(fd, SHM_SIZE) == -1) { perror("ftruncate"); exit(EXIT_FAILURE); }

    /* Map and cast to struct pointer */
    shdata = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shdata == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
    close(fd);  /* fd not needed after mmap */

    /* Initialize */
    shdata->counter = 0;
    shdata->ready   = 0;
    memset(shdata->message, 0, sizeof(shdata->message));

    printf("[Writer] Shared memory ready. Writing 5 messages...\n");

    for (int i = 1; i <= 5; i++) {
        shdata->counter = i;
        snprintf(shdata->message, sizeof(shdata->message),
                 "Message #%d from writer (pid=%d)", i, (int)getpid());
        shdata->ready = 1;    /* Signal reader */

        printf("[Writer] counter=%d  message='%s'\n",
               shdata->counter, shdata->message);
        sleep(1);
        shdata->ready = 0;    /* Reset */
    }

    printf("[Writer] Done. Press Enter to cleanup and exit...\n");
    getchar();

    munmap(shdata, SHM_SIZE);
    shm_unlink(SHM_NAME);
    return 0;
}
/*
 * shm_reader.c β€” Reads from shared memory via mmap()
 * Compile: gcc shm_reader.c -o shm_reader -lrt
 * Run in a second terminal while writer is running.
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "shared_struct.h"

int main(void)
{
    int fd;
    struct SharedData *shdata;

    /* Open existing object β€” reader doesn't create */
    fd = shm_open(SHM_NAME, O_RDONLY, 0);
    if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }

    /* Map read-only */
    shdata = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
    if (shdata == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
    close(fd);

    printf("[Reader] Watching for messages (Ctrl+C to stop)...\n");

    int last_counter = 0;
    while (1) {
        /* Spin-wait for new data (simple polling β€” in production use semaphore) */
        if (shdata->ready == 1 && shdata->counter != last_counter) {
            printf("[Reader] counter=%d  message='%s'\n",
                   shdata->counter, shdata->message);
            last_counter = shdata->counter;
        }
        usleep(10000);  /* 10ms poll interval */
    }

    munmap(shdata, SHM_SIZE);
    return 0;
}
Note on synchronization: The above example uses simple polling. In production code, combine POSIX shared memory with a POSIX semaphore or mutex (stored in the shared memory itself with PTHREAD_PROCESS_SHARED) for proper synchronization without busy-waiting.

🎯 Interview Questions β€” mmap() with POSIX shm
Q1. Why is MAP_SHARED required for IPC using POSIX shared memory?
Answer: MAP_SHARED causes writes by one process to be immediately visible to all other processes mapping the same object, because they all share the same physical memory pages. MAP_PRIVATE creates a copy-on-write mapping β€” writes stay private to the process and never propagate to the object or other processes.
Q2. Is it safe to close the file descriptor returned by shm_open() after mmap()?
Answer: Yes, completely safe. The memory mapping is maintained independently of the file descriptor. Closing the fd does not affect the existing mapping. However, keep the fd open if you plan to call ftruncate() or fstat() later.
Q3. What does mmap() return on error, and how do you check for it?
Answer: mmap() returns the special constant MAP_FAILED (which is (void *) -1) on error. You must check addr == MAP_FAILED, not addr == NULL. A return of NULL would be a valid mapping at address zero in some architectures, which is why MAP_FAILED is a distinct sentinel.
Q4. What happens if prot includes PROT_WRITE but the fd was opened with O_RDONLY?
Answer: mmap() returns MAP_FAILED and sets errno to EACCES (permission denied). The write protection in prot cannot exceed the access rights granted when the object was opened with shm_open().
Q5. Why is shared memory the fastest IPC mechanism?
Answer: Because once the mapping is set up, there are no system calls for data transfer. A process writes data by storing to a memory address. Another process reads it by loading from a memory address. No copying, no kernel involvement, no buffers β€” it is as fast as normal memory access.
Q6. What arguments must you pass to munmap()?
Answer: The exact address returned by mmap() and the same length that was passed to mmap(). If you try to unmap a partial range or a wrong address, munmap() will fail with EINVAL.
Q7. Does munmap() delete the shared memory object?
Answer: No. munmap() only removes the mapping from the calling process’s virtual address space. The shared memory object in /dev/shm continues to exist and can be opened by other processes. To delete the object, you must call shm_unlink().

Continue the Series
Next: shm_unlink() and a complete producer–consumer example

← Part 3: ftruncate() & fstat() Part 5: shm_unlink() & Examples β†’

Leave a Reply

Your email address will not be published. Required fields are marked *