Loading content...
Creating a shared memory segment is merely the first step—the segment exists in kernel space, identified by an ID or name, but your process cannot access it directly. To read or write shared data, you must attach (or map) the segment into your process's virtual address space. This operation configures the page tables so that a range of virtual addresses in your process points to the shared physical memory.
Attachment transforms an abstract resource identifier into a concrete pointer you can dereference. Detachment reverses this process, removing the mapping and potentially triggering cleanup operations. Understanding these operations deeply is essential for writing correct, leak-free, crash-resistant shared memory code.
This page explores the mechanics of attachment and detachment for both System V and POSIX shared memory, along with the critical nuances that separate working code from production-ready code.
By the end of this page, you will understand: (1) System V attachment with shmat() and detachment with shmdt(), (2) POSIX mapping with mmap() and unmapping with munmap(), (3) Address selection strategies and their implications, (4) Reference counting and segment lifecycle, (5) Handling attachment across fork(), and (6) Common pitfalls and debugging techniques.
The shmat() system call attaches a shared memory segment to the calling process's address space:
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
Parameters Explained:
| Parameter | Type | Purpose | Recommended Value |
|---|---|---|---|
| shmid | int | Segment identifier from shmget() | Value returned by shmget() |
| shmaddr | const void* | Requested attachment address | NULL (let kernel decide) |
| shmflg | int | Attachment flags | 0 or SHM_RDONLY for read-only |
Return Value:
On success, shmat() returns the address where the segment is attached—a valid pointer you can immediately use. On failure, it returns (void *)-1 and sets errno. Note the unusual error return: not NULL, but explicitly -1 cast to void pointer.
The shmaddr Parameter:
While you can specify an exact address for attachment, doing so is almost always wrong:
NULL (recommended): The kernel selects an appropriate address, guaranteeing no conflicts with existing mappings.
Specific address: The kernel attempts to attach at that address. May fail if address is already mapped or improperly aligned.
Specific address + SHM_RND: The kernel rounds the address down to SHMLBA boundary before attaching (SHMLBA = "Segment Low Boundary Address").
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/ipc.h>#include <sys/shm.h>#include <errno.h> #define SHM_SIZE 4096 /** * Demonstrates various shmat() patterns and their implications. */int main() { key_t key = ftok("/tmp/shmat_demo", 'A'); if (key == -1) { // Create file if needed FILE *f = fopen("/tmp/shmat_demo", "w"); if (f) fclose(f); key = ftok("/tmp/shmat_demo", 'A'); } int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666); if (shmid == -1) { perror("shmget"); exit(EXIT_FAILURE); } printf("Created/accessed segment: %d\n", shmid); // Pattern 1: Let kernel choose address (RECOMMENDED) void *ptr1 = shmat(shmid, NULL, 0); if (ptr1 == (void *)-1) { perror("shmat (NULL address)"); exit(EXIT_FAILURE); } printf("\nPattern 1 - Kernel-chosen address: %p\n", ptr1); // We can attach the same segment multiple times! void *ptr2 = shmat(shmid, NULL, SHM_RDONLY); if (ptr2 == (void *)-1) { perror("shmat (read-only)"); exit(EXIT_FAILURE); } printf("Pattern 2 - Second attachment (read-only): %p\n", ptr2); // Both pointers access the same underlying memory strcpy(ptr1, "Hello from ptr1"); printf("Read from ptr2: %s\n", (char *)ptr2); // Attempting to write via read-only mapping causes SIGSEGV // Uncomment to test: strcpy(ptr2, "This will crash"); // Pattern 3: Demonstrating address hint (usually not recommended) // This might fail on systems with ASLR or if address is in use void *hint_addr = (void *)0x10000000; // Arbitrary address void *ptr3 = shmat(shmid, hint_addr, SHM_RND); if (ptr3 == (void *)-1) { printf("\nPattern 3 - Hint address %p failed: %s\n", hint_addr, strerror(errno)); printf(" This is expected on many systems - use NULL instead\n"); } else { printf("\nPattern 3 - Attached at hint address: %p\n", ptr3); printf(" Note: May differ from hint due to SHM_RND rounding\n"); shmdt(ptr3); } // Check attachment count struct shmid_ds info; shmctl(shmid, IPC_STAT, &info); printf("\nCurrent attachment count: %lu\n", info.shm_nattch); // Detach both mappings shmdt(ptr1); shmdt(ptr2); shmctl(shmid, IPC_STAT, &info); printf("After detaching both: %lu attachments\n", info.shm_nattch); // Clean up segment shmctl(shmid, IPC_RMID, NULL); printf("Segment removed\n"); return 0;}A process can attach the same segment multiple times, receiving different virtual addresses each time. Each attachment increments the attachment count and requires a separate shmdt() call. This is unusual but valid—typically used when you need both read-only and read-write views of the same data.
The shmdt() system call detaches a shared memory segment from the calling process's address space:
#include <sys/shm.h>
int shmdt(const void *shmaddr);
Critical Points:
Parameter is the address, not the shmid: You pass the pointer returned by shmat(), not the segment identifier.
Only removes the mapping: The segment continues to exist in the kernel. Other processes remain attached. Data persists.
Accessing memory after shmdt() is undefined behavior: The address range may be reused for other allocations, causing corruption.
Returns 0 on success, -1 on error: Common errors include EINVAL (not a valid attached address).
What shmdt() Does Internally:
What shmdt() Does NOT Do:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/ipc.h>#include <sys/shm.h>#include <sys/wait.h>#include <unistd.h> #define SHM_SIZE 4096 /** * Demonstrates shmdt() behavior and segment lifecycle management. */int main() { key_t key = IPC_PRIVATE; // Private segment for this demo int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0600); if (shmid == -1) { perror("shmget"); exit(EXIT_FAILURE); } printf("Created segment %d\n", shmid); void *ptr = shmat(shmid, NULL, 0); if (ptr == (void *)-1) { perror("shmat"); exit(EXIT_FAILURE); } printf("Attached at %p\n", ptr); // Write data strcpy(ptr, "Parent data"); printf("Wrote: %s\n", (char *)ptr); // Mark segment for deletion // Segment won't be deleted until all attachments are gone if (shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl IPC_RMID"); } else { printf("Marked for deletion (deferred until all detach)\n"); } // Fork a child pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } if (pid == 0) { // Child process // Child inherits the attachment! printf("\nChild: inherited attachment at %p\n", ptr); printf("Child reads: %s\n", (char *)ptr); // Modify data strcpy(ptr, "Child modified data"); printf("Child wrote new data\n"); // Child detaches if (shmdt(ptr) == -1) { perror("Child shmdt"); } else { printf("Child detached\n"); } // Demonstrate: accessing ptr now is undefined behavior // printf("After detach: %s\n", (char *)ptr); // DON'T DO THIS exit(EXIT_SUCCESS); } // Parent waits for child wait(NULL); // Parent still has access printf("\nParent after child exit\n"); printf("Parent reads: %s\n", (char *)ptr); // Sees child's changes // Check segment status struct shmid_ds info; int result = shmctl(shmid, IPC_STAT, &info); if (result == -1) { // On some systems, IPC_STAT fails after IPC_RMID printf("Segment marked for deletion (IPC_STAT may fail)\n"); } else { printf("Attachment count: %lu\n", info.shm_nattch); } // Parent detaches if (shmdt(ptr) == -1) { perror("Parent shmdt"); } else { printf("Parent detached\n"); printf("Segment now destroyed (was marked for deletion)\n"); } // Verify segment is gone result = shmctl(shmid, IPC_STAT, &info); if (result == -1 && errno == EINVAL) { printf("Confirmed: segment no longer exists\n"); } return 0;}Calling shmctl(shmid, IPC_RMID, NULL) immediately after creation is a defensive pattern. The segment functions normally but is guaranteed to be deleted when all processes detach—even if processes crash without explicit cleanup. This prevents orphaned segments from accumulating over time.
POSIX shared memory uses the standard mmap() system call for attachment, providing a unified interface that also serves for file mapping, anonymous memory, and device access:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
Parameters for Shared Memory:
| Parameter | Type | Purpose | Typical Value |
|---|---|---|---|
| addr | void* | Suggested address (hint) | NULL (kernel selects) |
| length | size_t | Number of bytes to map | Same as ftruncate size |
| prot | int | Memory protection | PROT_READ | PROT_WRITE |
| flags | int | Mapping type | MAP_SHARED |
| fd | int | File descriptor from shm_open() | Return value of shm_open() |
| offset | off_t | Offset into shared object | 0 (start of object) |
Protection Flags (prot):
PROT_READ: Pages can be readPROT_WRITE: Pages can be writtenPROT_EXEC: Pages can be executed (rarely used for data)PROT_NONE: Pages cannot be accessed at allMapping Flags (flags):
MAP_SHARED: Changes are visible to other processes and written back to the underlying object. Required for IPC.MAP_PRIVATE: Creates a copy-on-write mapping. Changes are private to this process. Not useful for IPC.MAP_FIXED: Map exactly at the specified address. Dangerous—may overwrite existing mappings.MAP_HUGETLB: Use huge pages for this mapping (Linux-specific).123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/mman.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h> #define SHM_NAME "/mmap_demo"#define SHM_SIZE (64 * 1024) // 64KB /** * Demonstrates comprehensive POSIX mmap() patterns for shared memory. */int main() { int shm_fd; void *base_ptr; // Create shared memory object shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); } // Set size if (ftruncate(shm_fd, SHM_SIZE) == -1) { perror("ftruncate"); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } printf("Created shared memory object: %s, size: %d bytes\n", SHM_NAME, SHM_SIZE); // Pattern 1: Full mapping with read-write access base_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); if (base_ptr == MAP_FAILED) { perror("mmap"); close(shm_fd); shm_unlink(SHM_NAME); exit(EXIT_FAILURE); } printf("\nPattern 1 - Full RW mapping at: %p\n", base_ptr); // Pattern 2: Partial mapping (map only part of the object) // Useful for very large shared memory objects size_t partial_size = 4096; // One page off_t partial_offset = 4096; // Start at second page void *partial_ptr = mmap(NULL, partial_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, partial_offset); if (partial_ptr == MAP_FAILED) { perror("mmap (partial)"); } else { printf("Pattern 2 - Partial mapping: %p (offset %ld, size %zu)\n", partial_ptr, (long)partial_offset, partial_size); // Write at the start of partial mapping strcpy(partial_ptr, "Data at page 2"); // This is accessible via base_ptr too! printf("Read via base_ptr+4096: %s\n", (char *)base_ptr + partial_offset); munmap(partial_ptr, partial_size); } // Pattern 3: Read-only mapping // Useful for consumers that should not modify the data void *readonly_ptr = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0); if (readonly_ptr == MAP_FAILED) { perror("mmap (read-only)"); } else { printf("Pattern 3 - Read-only mapping at: %p\n", readonly_ptr); // Write via read-write mapping strcpy(base_ptr, "Written via RW mapping"); // Immediately visible via read-only mapping printf("Read via RO mapping: %s\n", (char *)readonly_ptr); // Attempting to write via RO mapping causes SIGSEGV // strcpy(readonly_ptr, "crash"); // Don't do this! munmap(readonly_ptr, SHM_SIZE); } // Pattern 4: Demonstrating file descriptor closure // After mmap(), the file descriptor can be closed close(shm_fd); printf("\nClosed file descriptor (mapping still valid)\n"); // Mapping is still fully functional strcpy(base_ptr, "Works after fd close"); printf("After fd close, read: %s\n", (char *)base_ptr); // Pattern 5: Memory protection change (mprotect) printf("\nPattern 5 - Changing protection with mprotect()\n"); if (mprotect(base_ptr, 4096, PROT_READ) == 0) { printf("First page now read-only\n"); // strcpy(base_ptr, "crash"); // Would SIGSEGV // Restore write access mprotect(base_ptr, 4096, PROT_READ | PROT_WRITE); printf("Write access restored\n"); } // Cleanup munmap(base_ptr, SHM_SIZE); shm_unlink(SHM_NAME); printf("\nUnmapped and unlinked shared memory\n"); return 0;}A common pattern is to close() the file descriptor immediately after mmap() succeeds. The mapping remains valid—it holds a reference to the underlying object. This prevents file descriptor leaks and is considered good practice. The only reason to keep the fd open is if you need to call ftruncate() to resize the object later.
The munmap() system call removes a memory mapping:
#include <sys/mman.h>
int munmap(void *addr, size_t length);
Parameters:
addr: The starting address of the mapping (must be page-aligned, typically the value returned by mmap())length: The number of bytes to unmap (should match the original mmap() length)Return Value: 0 on success, -1 on error.
Key Differences from shmdt():
| Aspect | shmdt() | munmap() |
|---|---|---|
| Parameters | Just the address | Address and length |
| Partial unmapping | Not supported | Can unmap portions of mapping |
| Alignment | Must be exact shmat() return | Must be page-aligned |
| Multiple mappings | Each needs separate call | Can unmap ranges spanning multiple mappings |
Partial Unmapping:
Unlike System V, POSIX allows you to unmap a portion of a mapping. This is powerful but must be done carefully:
// Original mapping: 64KB starting at ptr
void *ptr = mmap(NULL, 65536, ...);
// Unmap just the first 4KB
munmap(ptr, 4096);
// Now ptr is invalid, but ptr+4096 through ptr+65535 is still valid
// The valid range is now: (ptr + 4096) to (ptr + 65535)
Warning: This creates a "hole" in your address space. The unmapped pages may be reused by subsequent allocations, potentially causing hard-to-debug issues if you accidentally access the wrong address.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/mman.h>#include <unistd.h>#include <signal.h>#include <setjmp.h> #define SHM_NAME "/munmap_demo"#define TOTAL_SIZE (16 * 4096) // 16 pages static sigjmp_buf jump_buffer;static volatile sig_atomic_t got_signal = 0; void segv_handler(int sig) { got_signal = 1; siglongjmp(jump_buffer, 1);} /** * Demonstrates munmap() behavior including partial unmapping. */int main() { int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); } ftruncate(shm_fd, TOTAL_SIZE); void *ptr = mmap(NULL, TOTAL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); if (ptr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); } close(shm_fd); printf("Mapped %d bytes at %p\n", TOTAL_SIZE, ptr); printf("Page size: %ld bytes\n\n", sysconf(_SC_PAGESIZE)); // Initialize all pages with identifiable data for (int i = 0; i < 16; i++) { char *page = (char *)ptr + (i * 4096); sprintf(page, "Page %d data", i); } // Verify initial state printf("Initial state:\n"); printf(" Page 0 at %p: %s\n", ptr, (char *)ptr); printf(" Page 8 at %p: %s\n", (char *)ptr + 8*4096, (char *)ptr + 8*4096); // Partial unmap: remove pages 4-7 (middle section) void *unmap_start = (char *)ptr + 4 * 4096; size_t unmap_size = 4 * 4096; printf("\nUnmapping pages 4-7 (address %p, size %zu)...\n", unmap_start, unmap_size); if (munmap(unmap_start, unmap_size) == -1) { perror("munmap"); exit(EXIT_FAILURE); } // Pages 0-3 and 8-15 are still valid printf("After partial unmap:\n"); printf(" Page 0: %s (still valid)\n", (char *)ptr); printf(" Page 3: %s (still valid)\n", (char *)ptr + 3*4096); printf(" Page 8: %s (still valid)\n", (char *)ptr + 8*4096); printf(" Page 15: %s (still valid)\n", (char *)ptr + 15*4096); // Demonstrate that accessing unmapped pages causes SIGSEGV printf("\nAttempting to access unmapped page 5...\n"); struct sigaction sa, old_sa; sa.sa_handler = segv_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGSEGV, &sa, &old_sa); if (sigsetjmp(jump_buffer, 1) == 0) { volatile char c = *((char *)ptr + 5*4096); // SIGSEGV (void)c; // Suppress unused warning printf(" No fault (unexpected)\n"); } else { printf(" SIGSEGV caught! Page 5 is no longer mapped.\n"); } sigaction(SIGSEGV, &old_sa, NULL); // Restore handler // Unmap remaining portions printf("\nCleaning up remaining mappings...\n"); munmap(ptr, 4 * 4096); // Pages 0-3 munmap((char *)ptr + 8*4096, 8 * 4096); // Pages 8-15 shm_unlink(SHM_NAME); printf("All unmapped and unlinked\n"); return 0;}After munmap(), any pointer into the unmapped region becomes a dangling pointer. Modern systems will SIGSEGV on access, but in complex programs with multiple mappings, you might accidentally hit a valid mapping at the same address (ASLR makes this unlikely but not impossible). Always set pointers to NULL after unmapping.
Understanding how shared memory fits into the process address space is crucial for designing robust systems, especially when dealing with large mappings or multiple processes.
Virtual Address Space Layout:
On a typical 64-bit Linux system, the user-space virtual address space spans from 0 to 0x7fffffffffff (128TB). Within this space, different regions serve different purposes:
Where Shared Memory Gets Mapped:
When you use mmap() or shmat() with a NULL address hint, the kernel selects an address in the mmap region. The exact location depends on:
Implications for Shared Memory Pointers:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>#include <fcntl.h>#include <sys/mman.h>#include <unistd.h> #define SHM_NAME "/offset_demo"#define SHM_SIZE (1024 * 1024) // 1MB /** * Demonstrates offset-based data structures for shared memory. * These structures are safe across different process address spaces. */ // Use offsets instead of pointers for the linked structuretypedef int32_t shm_offset_t; // Offset from shared memory base#define SHM_NULL ((shm_offset_t)-1) // A linked list node using offsetstypedef struct { int data; shm_offset_t next; // Offset to next node, not a pointer!} SharedNode; // Shared memory headertypedef struct { uint32_t magic; uint32_t version; shm_offset_t head; // Offset to first node shm_offset_t free_list; // Simple allocator size_t next_free_offset;} SharedHeader; #define SHM_MAGIC 0x534D454D // "SMEM" // Helper macros for offset conversion#define OFFSET_TO_PTR(base, offset) \ ((offset) == SHM_NULL ? NULL : (void *)((char *)(base) + (offset)))#define PTR_TO_OFFSET(base, ptr) \ ((ptr) == NULL ? SHM_NULL : (shm_offset_t)((char *)(ptr) - (char *)(base))) // Simple bump allocator within shared memoryshm_offset_t shm_alloc(void *base, size_t size) { SharedHeader *hdr = (SharedHeader *)base; // Align to 8 bytes size = (size + 7) & ~7; size_t offset = hdr->next_free_offset; if (offset + size > SHM_SIZE) { return SHM_NULL; // Out of memory } hdr->next_free_offset += size; return (shm_offset_t)offset;} void list_add(void *base, int value) { SharedHeader *hdr = (SharedHeader *)base; // Allocate new node shm_offset_t node_offset = shm_alloc(base, sizeof(SharedNode)); if (node_offset == SHM_NULL) { fprintf(stderr, "Out of shared memory\n"); return; } SharedNode *node = OFFSET_TO_PTR(base, node_offset); node->data = value; node->next = hdr->head; // Point to old head hdr->head = node_offset; // New node is now head} void list_print(void *base, const char *label) { SharedHeader *hdr = (SharedHeader *)base; printf("%s: ", label); shm_offset_t current = hdr->head; while (current != SHM_NULL) { SharedNode *node = OFFSET_TO_PTR(base, current); printf("%d -> ", node->data); current = node->next; } printf("NULL\n");} int main(int argc, char *argv[]) { int is_creator = (argc > 1 && strcmp(argv[1], "reader") != 0); int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); } ftruncate(shm_fd, SHM_SIZE); void *base = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); close(shm_fd); if (base == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); } printf("Mapped at address: %p (this differs per process!)\n", base); SharedHeader *hdr = (SharedHeader *)base; if (is_creator) { printf("Initializing shared memory...\n"); memset(base, 0, SHM_SIZE); hdr->magic = SHM_MAGIC; hdr->version = 1; hdr->head = SHM_NULL; hdr->next_free_offset = sizeof(SharedHeader); // Add some data list_add(base, 10); list_add(base, 20); list_add(base, 30); list_print(base, "Creator"); printf("\nRun with 'reader' argument to read from another process\n"); printf("Press Enter to cleanup...\n"); getchar(); shm_unlink(SHM_NAME); } else { // Validate if (hdr->magic != SHM_MAGIC) { fprintf(stderr, "Invalid shared memory (run creator first)\n"); exit(EXIT_FAILURE); } list_print(base, "Reader"); // Add more data from this process list_add(base, 40); list_print(base, "After reader add"); } munmap(base, SHM_SIZE); return 0;}For complex shared data structures, consider using integer handles or indices instead of pointers. A 'handle' is an index into a shared array. Each process converts handles to local pointers as needed. This approach scales well and is immune to address differences between processes.
When a process calls fork(), the child inherits the parent's memory mappings, including shared memory attachments. This creates a natural communication channel between parent and child.
Inheritance Behavior:
Key Insight: Unlike private (COW) memory, shared memory is truly shared—changes by the child are immediately visible to the parent and vice versa. This is the intended behavior and is what makes shared memory useful for IPC.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/mman.h>#include <sys/wait.h>#include <unistd.h>#include <stdatomic.h> #define SHM_NAME "/fork_demo"#define SHM_SIZE 4096 /** * Demonstrates shared memory behavior across fork(). * Parent and child communicate through the shared region. */ typedef struct { atomic_int turn; // Whose turn to write (0=parent, 1=child) atomic_int done; // Communication complete flag int parent_value; // Data from parent int child_value; // Data from child char message[256]; // Shared message buffer} SharedState; int main() { int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); } ftruncate(shm_fd, SHM_SIZE); SharedState *state = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); close(shm_fd); if (state == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); } // Initialize state before fork memset(state, 0, sizeof(SharedState)); atomic_store(&state->turn, 0); // Parent goes first atomic_store(&state->done, 0); printf("Parent PID: %d, mapped at: %p\n", getpid(), (void *)state); pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } if (pid == 0) { // Child process printf("Child PID: %d, inherited mapping at: %p (SAME address!)\n", getpid(), (void *)state); // Wait for parent's turn while (atomic_load(&state->turn) == 0) { usleep(1000); // Busy wait (use proper sync in production) } // Read parent's data printf("Child sees parent_value: %d\n", state->parent_value); printf("Child sees message: %s\n", state->message); // Write child's response state->child_value = 200; strcat(state->message, " Child was here!"); atomic_store(&state->done, 1); printf("Child: wrote response and signaled done\n"); // Child should unmap but NOT unlink (parent will do that) munmap(state, SHM_SIZE); exit(EXIT_SUCCESS); } // Parent process printf("\nParent: writing data for child\n"); state->parent_value = 100; strcpy(state->message, "Hello from parent!"); // Signal child it's their turn atomic_store(&state->turn, 1); // Wait for child to finish while (atomic_load(&state->done) == 0) { usleep(1000); } printf("\nParent sees child_value: %d\n", state->child_value); printf("Parent sees message: %s\n", state->message); // Wait for child process to exit int status; waitpid(pid, &status, 0); printf("\nChild exited with status: %d\n", WEXITSTATUS(status)); // Parent cleanup munmap(state, SHM_SIZE); shm_unlink(SHM_NAME); printf("Cleaned up shared memory\n"); return 0;}When a process forks, the child inherits the exact same virtual addresses for shared memory mappings. This is because fork() copies the page tables. So parent and child can safely use the same pointer values—a key difference from unrelated processes mapping the same shared memory object.
exec() Behavior:
When a process calls exec(), its entire address space is replaced with the new program. All memory mappings are unmapped, including shared memory:
To preserve shared memory across exec():
Debugging shared memory issues requires specialized tools and techniques. The problems often involve multiple processes, race conditions, and memory corruption that may only manifest under specific timing conditions.
Inspecting System V Shared Memory:
12345678910111213141516171819202122232425262728293031323334353637383940
#!/bin/bash# Commands for debugging shared memory issues echo "=== System V Shared Memory Segments ==="ipcs -m echo ""echo "=== Detailed System V Info ==="ipcs -m -i <shmid> # Replace <shmid> with actual ID echo ""echo "=== POSIX Shared Memory Objects ==="ls -la /dev/shm/ echo ""echo "=== Process Memory Maps ==="# Show all mappings for a processcat /proc/<pid>/maps | grep -E "(shm|deleted)" echo ""echo "=== Find which processes have segment attached ==="# For System V segment with shmidipcs -m -p # Shows creator and last-attach PIDs echo ""echo "=== View shared memory contents (POSIX) ==="xxd /dev/shm/my_shm_name | head -20 echo ""echo "=== strace shared memory calls ==="strace -e trace=shmget,shmat,shmdt,shmctl -p <pid>strace -e trace=mmap,munmap,shm_open,shm_unlink -p <pid> echo ""echo "=== Remove orphaned System V segments ==="# BE CAREFUL - verify segments are not in use first!ipcrm -m <shmid> # Remove all segments owned by current useripcs -m | grep $(whoami) | awk '{print $2}' | xargs -I {} ipcrm -m {}valgrind's memcheck tool can detect invalid accesses to shared memory, but with caveats: (1) Only the process under valgrind is checked. (2) Accesses by other processes are invisible. (3) False positives may occur if data is initialized by another process. Consider running all communicating processes under valgrind for complete coverage.
We've explored the mechanics of making shared memory accessible within a process and properly releasing it when done. Let's consolidate the key takeaways:
What's Next:
With shared memory accessible, we face the critical challenge: synchronization. Multiple processes reading and writing the same memory without coordination leads to race conditions, data corruption, and undefined behavior. The next page explores the synchronization requirements and mechanisms that make shared memory safe for concurrent access.
You now understand how to attach and detach shared memory using both System V and POSIX APIs. You've learned about address space considerations, pointer safety, fork() behavior, and debugging techniques. Next, we'll tackle the essential topic of synchronization.