Loading learning content...
In the landscape of inter-process communication, one mechanism stands apart in raw performance: shared memory. While pipes, message queues, and sockets require the kernel to copy data between processes—a costly operation measured in microseconds—shared memory eliminates this overhead entirely. When two processes share a memory region, writing to an address in one process makes that data instantaneously visible to another. There is no system call, no context switch, no data copying.
This architectural elegance comes with a price: complexity. Shared memory provides raw, unmediated access to bytes in memory. The kernel offers no built-in ordering, no synchronization, no protection against simultaneous access. These responsibilities fall entirely on the programmer.
This page explores the foundations: how shared memory regions are created, how the operating system manages them, and the critical design decisions you must understand before using this powerful primitive.
By the end of this page, you will understand: (1) The memory architecture that enables shared memory, (2) System V shared memory creation with shmget(), (3) POSIX shared memory creation with shm_open(), (4) Key generation and naming conventions, (5) Permission models and security considerations, and (6) Common pitfalls in shared memory allocation.
To understand shared memory, we must first understand how operating systems manage process memory. Each process operates in its own virtual address space—a contiguous range of addresses (e.g., 0 to 2⁴⁸ on 64-bit systems) that appears private to that process. The hardware Memory Management Unit (MMU) translates these virtual addresses to physical addresses in RAM.
The key insight is that virtual-to-physical mappings are maintained in page tables—data structures controlled by the kernel. Two processes can have different virtual addresses that map to the same physical page. When this happens, both processes are literally reading and writing the same bytes in RAM.
Shared memory exploits this mechanism intentionally. The kernel allocates a region of physical memory and maps it into the virtual address spaces of multiple processes. From the hardware's perspective, there's nothing special about these pages—they're ordinary RAM. The magic is entirely in the page table configuration.
Understanding the page table mechanism clarifies why shared memory is so fast: there is literally no copying. Both processes access the same physical bytes. It also explains why synchronization is mandatory—without it, you have two CPUs potentially racing on the same cache lines, with all the hazards that entails.
Physical vs. Virtual Address Considerations:
When process A maps shared memory at virtual address 0x7000 and process B maps it at 0x9000, the pointers are not interchangeable. A pointer stored in shared memory by process A (pointing to 0x7020) means something entirely different to process B. This is a common source of bugs.
Rule: Never store absolute pointers in shared memory. Use offsets from the base address or position-independent data structures instead.
Page Alignment:
Shared memory regions are always allocated in units of the system page size (typically 4KB on x86, 4KB or 64KB on ARM depending on configuration). Requesting 100 bytes of shared memory actually allocates at least one full page. Understanding this prevents memory waste and explains certain size restrictions in the APIs.
The System V IPC family—originating in Unix System V during the 1980s—provides shared memory through the shmget() system call. Despite its age, System V shared memory remains widely used due to its universal availability and rich feature set.
The shmget() System Call:
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
This call either creates a new shared memory segment or obtains an identifier for an existing one. Let's examine each parameter in depth.
| Parameter | Type | Purpose | Critical Details |
|---|---|---|---|
| key | key_t | Unique identifier for the segment | Processes use the same key to access the same segment. Use ftok() or IPC_PRIVATE. |
| size | size_t | Segment size in bytes | Rounded up to page boundary. When accessing existing segment, can be 0 or ≤ original size. |
| shmflg | int | Flags controlling creation and permissions | Bitwise OR of IPC_CREAT, IPC_EXCL, and permission bits (e.g., 0666). |
Understanding the Return Value:
On success, shmget() returns a shared memory identifier (shmid)—a non-negative integer that uniquely identifies the segment within the system. This identifier is used in subsequent calls to shmat(), shmdt(), and shmctl(). On failure, it returns -1 and sets errno.
Flag Combinations:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
#include <stdio.h>#include <stdlib.h>#include <sys/ipc.h>#include <sys/shm.h>#include <errno.h>#include <string.h> #define SHM_SIZE 4096 // 4KB segment /** * Demonstrates robust System V shared memory creation * with comprehensive error handling and cleanup. */int main() { key_t key; int shmid; // Generate a unique key based on file path and project ID // Both communicating processes must use identical arguments key = ftok("/tmp/myapp", 'S'); if (key == -1) { perror("ftok failed"); fprintf(stderr, "Ensure /tmp/myapp exists and is accessible\n"); exit(EXIT_FAILURE); } printf("Generated key: 0x%x\n", key); // Attempt to create shared memory segment // IPC_CREAT: Create if doesn't exist // IPC_EXCL: Fail if already exists (we want to be the creator) // 0660: Owner and group read/write, others no access shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0660); if (shmid == -1) { if (errno == EEXIST) { // Segment already exists - attach to it instead printf("Segment exists, attaching...\n"); shmid = shmget(key, 0, 0660); if (shmid == -1) { perror("shmget (attach) failed"); exit(EXIT_FAILURE); } } else { perror("shmget failed"); fprintf(stderr, "Error code: %d\n", errno); exit(EXIT_FAILURE); } } else { printf("Created new shared memory segment\n"); } printf("Shared memory ID: %d\n", shmid); // Query segment information struct shmid_ds shm_info; if (shmctl(shmid, IPC_STAT, &shm_info) == -1) { perror("shmctl IPC_STAT failed"); exit(EXIT_FAILURE); } printf("Segment size: %zu bytes\n", shm_info.shm_segsz); printf("Creator PID: %d\n", shm_info.shm_cpid); printf("Last attach PID: %d\n", shm_info.shm_lpid); printf("Current attachments: %lu\n", shm_info.shm_nattch); return 0;}System V shared memory segments persist in the kernel until explicitly removed with shmctl(shmid, IPC_RMID, NULL) or system reboot—even if all processes detach. Use 'ipcs -m' to view existing segments and 'ipcrm -m shmid' to remove orphaned ones. Leaked segments consume kernel memory indefinitely.
For unrelated processes to access the same shared memory segment, they need a common key. The ftok() function generates a key from a pathname and a project identifier:
key_t ftok(const char *pathname, int proj_id);
How ftok() Works:
ftok() combines three pieces of information:
These are combined algorithmically to produce a key_t value (typically 32 bits). The exact algorithm is implementation-defined but guarantees that different (pathname, proj_id) pairs produce different keys.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
#include <stdio.h>#include <stdlib.h>#include <sys/ipc.h>#include <sys/stat.h>#include <errno.h> /** * Demonstrates ftok() internals and potential issues. * Understanding these details prevents subtle production bugs. */int main() { const char *path = "/tmp/myapp_shm_key"; int proj_id = 'M'; // 0x4D - using printable char is common practice // First, let's see what ftok() uses internally struct stat st; if (stat(path, &st) == -1) { perror("stat failed - file must exist for ftok()"); printf("Creating key file: %s\n", path); FILE *f = fopen(path, "w"); if (f) { fclose(f); } else { perror("Could not create key file"); exit(EXIT_FAILURE); } stat(path, &st); } printf("Key file: %s\n", path); printf(" Inode: %lu\n", (unsigned long)st.st_ino); printf(" Device ID: %lu\n", (unsigned long)st.st_dev); printf(" Project ID: %d (0x%02x)\n", proj_id, proj_id); key_t key = ftok(path, proj_id); if (key == -1) { perror("ftok failed"); exit(EXIT_FAILURE); } printf("Generated key: 0x%08x\n", key); // Demonstrate proj_id collision printf("\n--- Demonstrating proj_id collision ---\n"); key_t key1 = ftok(path, 0x01); key_t key2 = ftok(path, 0x101); // Only lower 8 bits used! key_t key3 = ftok(path, 0x201); printf("ftok(path, 0x001) = 0x%08x\n", key1); printf("ftok(path, 0x101) = 0x%08x\n", key2); printf("ftok(path, 0x201) = 0x%08x\n", key3); if (key1 == key2 && key2 == key3) { printf("WARNING: All three keys are identical! Only lower 8 bits matter.\n"); } // Best practice: use different characters for different IPC objects printf("\n--- Recommended pattern ---\n"); printf("Shared Memory: ftok(path, 'S') = 0x%08x\n", ftok(path, 'S')); printf("Message Queue: ftok(path, 'M') = 0x%08x\n", ftok(path, 'M')); printf("Semaphore: ftok(path, 'E') = 0x%08x\n", ftok(path, 'E')); return 0;}Alternative Key Strategies:
Given ftok()'s limitations, production systems often use alternative approaches:
Hardcoded Keys: Define keys as constants in a shared header file. Simple but requires careful management to avoid collisions.
IPC_PRIVATE: Creates a unique, unkeyed segment. The shmid must be communicated out-of-band (e.g., environment variable, file, inherited across fork()). Useful for parent-child communication.
Configuration Files: Store the key or shmid in a well-known configuration file. More flexible than hardcoding but adds file I/O overhead.
Migrate to POSIX: POSIX shared memory uses filenames instead of numeric keys, avoiding ftok() entirely.
POSIX shared memory, introduced in POSIX.1b-1993, provides a cleaner, file-oriented API that avoids many System V pitfalls. Instead of numeric keys, POSIX shared memory uses named objects that appear in a virtual filesystem (typically mounted at /dev/shm on Linux).
The shm_open() System Call:
#include <sys/mman.h>
#include <fcntl.h>
int shm_open(const char *name, int oflag, mode_t mode);
shm_open() behaves similarly to open() for regular files, returning a file descriptor that can be used with ftruncate(), fstat(), and mmap().
| Aspect | System V (shmget) | POSIX (shm_open) |
|---|---|---|
| Identifier | Numeric key (key_t) | String name (e.g., "/my_shm") |
| Return Type | Segment ID (int) | File descriptor (int) |
| Size Control | Set in shmget() | Set via ftruncate() |
| Memory Mapping | shmat() / shmdt() | mmap() / munmap() |
| Persistence | Kernel-managed, survives crash | /dev/shm filesystem |
| Visibility | ipcs -m | ls /dev/shm/ |
| Removal | shmctl(IPC_RMID) | shm_unlink() |
| Portability | All Unix systems | POSIX-compliant systems |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
#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 "/my_app_shared" // Must start with /#define SHM_SIZE 4096 /** * Demonstrates POSIX shared memory creation with robust error handling. * POSIX approach uses familiar file-like semantics. */int main() { int shm_fd; void *shm_ptr; // Create or open shared memory object // O_CREAT: Create if doesn't exist // O_RDWR: Read and write access // Mode 0666: Read/write for owner, group, and others shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666); if (shm_fd == -1) { perror("shm_open failed"); exit(EXIT_FAILURE); } printf("Shared memory object created/opened: %s\n", SHM_NAME); printf("File descriptor: %d\n", shm_fd); // Get current size of the shared memory object struct stat shm_stat; if (fstat(shm_fd, &shm_stat) == -1) { perror("fstat failed"); close(shm_fd); exit(EXIT_FAILURE); } printf("Current size: %ld bytes\n", (long)shm_stat.st_size); // Set size of shared memory object // This allocates the actual memory // Note: Can only grow, shrinking may not release memory immediately if (shm_stat.st_size < SHM_SIZE) { printf("Resizing to %d bytes...\n", SHM_SIZE); if (ftruncate(shm_fd, SHM_SIZE) == -1) { perror("ftruncate failed"); close(shm_fd); exit(EXIT_FAILURE); } } // Map the shared memory into our address space shm_ptr = mmap(NULL, // Let kernel choose address SHM_SIZE, // Map this many bytes PROT_READ | PROT_WRITE, // Read and write access MAP_SHARED, // Changes visible to other processes shm_fd, // From this shared memory object 0); // Starting at offset 0 if (shm_ptr == MAP_FAILED) { perror("mmap failed"); close(shm_fd); exit(EXIT_FAILURE); } printf("Mapped at address: %p\n", shm_ptr); // The file descriptor can be closed after mmap() // The mapping remains valid close(shm_fd); printf("Closed file descriptor (mapping still valid)\n"); // Write some data const char *message = "Hello from POSIX shared memory!"; memcpy(shm_ptr, message, strlen(message) + 1); printf("Wrote message: %s\n", message); // In a real application, we would synchronize access here // For now, just demonstrate the data is there printf("Read back: %s\n", (char *)shm_ptr); // Unmap when done with this process // The shared memory object persists for other processes if (munmap(shm_ptr, SHM_SIZE) == -1) { perror("munmap failed"); } printf("Unmapped shared memory\n"); // To remove the shared memory object entirely: // shm_unlink(SHM_NAME); // This removes the name from /dev/shm // Actual memory freed when all mappings are gone printf("\nShared memory persists at: /dev/shm%s\n", SHM_NAME); return 0;}On Linux, POSIX shared memory objects appear as files in /dev/shm/. This is a tmpfs filesystem stored in RAM (and swap if necessary). You can use standard file commands: 'ls -la /dev/shm/' to list objects, 'rm /dev/shm/my_shm' to delete them, 'cat /dev/shm/my_shm | xxd' to view contents. This visibility makes debugging much easier than System V's opaque segments.
Naming Conventions:
POSIX shared memory names must follow specific rules:
/my_shm)/dir/shm is invalid on many systems)/Shm and /shm are different objects)Best Practice: Use a consistent prefix incorporating your application name:
#define SHM_PREFIX "/myapp_"
#define SHM_BUFFER SHM_PREFIX "buffer"
#define SHM_CONFIG SHM_PREFIX "config"
Shared memory provides direct access to process memory—a powerful capability that demands careful security consideration. Both System V and POSIX shared memory use Unix permission models, but the implications of misconfiguration are severe.
Permission Bits:
Both APIs use the familiar Unix permission model (owner/group/other × read/write/execute). For shared memory, the relevant permissions are:
| Mode | Octal | Use Case | Security Level |
|---|---|---|---|
| Owner RW only | 0600 | Private process communication (same user) | High |
| Owner+Group RW | 0660 | Processes in same group need access | Medium |
| World readable | 0644 | Read-only data shared system-wide | Low |
| World RW | 0666 | Any process can read/write | Dangerous |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
#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> /** * Demonstrates secure shared memory creation patterns. * Key principles: minimal permissions, exclusive creation, * explicit cleanup, and size validation. */ #define SECURE_SHM_NAME "/secure_app_data"#define SECURE_SHM_SIZE (64 * 1024) // 64KB typedef struct { unsigned int magic; // Validation marker unsigned int version; // Protocol version size_t data_size; // Actual data length char data[]; // Flexible array member} SecureSharedData; #define SHM_MAGIC 0xDEADBEEF int create_secure_shm(void **ptr, size_t *size, int is_creator) { int shm_fd; int flags = O_RDWR; mode_t mode = 0600; // Owner only - most restrictive if (is_creator) { // Creator uses O_EXCL to ensure we're creating, not reusing flags |= O_CREAT | O_EXCL; // First, try to remove any stale segment from previous crash shm_unlink(SECURE_SHM_NAME); // Ignore error if doesn't exist shm_fd = shm_open(SECURE_SHM_NAME, flags, mode); if (shm_fd == -1) { perror("shm_open (creator) failed"); return -1; } // Set size if (ftruncate(shm_fd, SECURE_SHM_SIZE) == -1) { perror("ftruncate failed"); close(shm_fd); shm_unlink(SECURE_SHM_NAME); return -1; } } else { // Consumer: open existing only shm_fd = shm_open(SECURE_SHM_NAME, flags, 0); if (shm_fd == -1) { if (errno == ENOENT) { fprintf(stderr, "Shared memory not created yet\n"); } else { perror("shm_open (consumer) failed"); } return -1; } // Verify size matches expected struct stat shm_stat; if (fstat(shm_fd, &shm_stat) == -1) { perror("fstat failed"); close(shm_fd); return -1; } if (shm_stat.st_size != SECURE_SHM_SIZE) { fprintf(stderr, "Size mismatch: expected %d, got %ld\n", SECURE_SHM_SIZE, (long)shm_stat.st_size); close(shm_fd); return -1; } // Verify ownership (security check) if (shm_stat.st_uid != getuid()) { fprintf(stderr, "Security: SHM owned by different user\n"); close(shm_fd); return -1; } } // Map with minimum required permissions *ptr = mmap(NULL, SECURE_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); close(shm_fd); // No longer needed after mmap if (*ptr == MAP_FAILED) { perror("mmap failed"); if (is_creator) shm_unlink(SECURE_SHM_NAME); return -1; } if (is_creator) { // Initialize with secure defaults SecureSharedData *data = (SecureSharedData *)*ptr; // Zero entire region first (prevent information leakage) memset(data, 0, SECURE_SHM_SIZE); // Set validation markers data->magic = SHM_MAGIC; data->version = 1; data->data_size = 0; } else { // Validate the segment SecureSharedData *data = (SecureSharedData *)*ptr; if (data->magic != SHM_MAGIC) { fprintf(stderr, "Invalid magic number - corrupted segment\n"); munmap(*ptr, SECURE_SHM_SIZE); *ptr = NULL; return -1; } } *size = SECURE_SHM_SIZE; return 0;} void cleanup_secure_shm(void *ptr, int is_creator) { if (ptr) { // Zero memory before unmapping (security) memset(ptr, 0, SECURE_SHM_SIZE); munmap(ptr, SECURE_SHM_SIZE); } if (is_creator) { shm_unlink(SECURE_SHM_NAME); }}Shared memory security should be part of a layered defense strategy. Combine restrictive permissions with validation (magic numbers, checksums), proper synchronization, input sanitization, and clear ownership semantics. No single measure is sufficient on its own.
Shared memory allocation is subject to both kernel limits and practical considerations. Understanding these constraints is essential for designing robust systems.
Page Alignment:
All shared memory allocations are rounded up to the system page size. On most x86_64 systems, pages are 4KB:
Requested: 100 bytes → Allocated: 4096 bytes (1 page)
Requested: 4097 bytes → Allocated: 8192 bytes (2 pages)
Requested: 1MB → Allocated: 1MB (256 pages, exact)
This has implications for memory efficiency and layout design.
| Limit | Parameter | Typical Default | How to Check/Change |
|---|---|---|---|
| Max segment size | SHMMAX | ~8GB (varies) | sysctl kernel.shmmax |
| Min segment size | SHMMIN | 1 byte | sysctl kernel.shmmin |
| Max segments system-wide | SHMMNI | 4096 | sysctl kernel.shmmni |
| Max total shared memory | SHMALL | ~8GB in pages | sysctl kernel.shmall |
| Max segments per process | SHMSEG | 128-256 | Rarely needs adjustment |
| POSIX shm max size | /dev/shm size | 50% of RAM | Mount option for tmpfs |
12345678910111213141516171819202122232425262728
#!/bin/bash# Script to check and display shared memory limits echo "=== System V Shared Memory Limits ==="echo "SHMMAX (max segment size): $(cat /proc/sys/kernel/shmmax) bytes"echo "SHMMIN (min segment size): $(cat /proc/sys/kernel/shmmin) bytes"echo "SHMMNI (max segments): $(cat /proc/sys/kernel/shmmni)"echo "SHMALL (total pages): $(cat /proc/sys/kernel/shmall) pages" PAGE_SIZE=$(getconf PAGE_SIZE)SHMALL=$(cat /proc/sys/kernel/shmall)echo "Total shared memory limit: $((SHMALL * PAGE_SIZE / 1024 / 1024)) MB" echo ""echo "=== POSIX Shared Memory (/dev/shm) ==="df -h /dev/shm echo ""echo "=== Current Shared Memory Usage ==="echo "System V segments:"ipcs -m | tail -n +4 | head -n -1 | wc -lecho ""echo "POSIX objects in /dev/shm:"ls -la /dev/shm/ 2>/dev/null || echo "(empty or not accessible)" echo ""echo "=== Detailed System V Segment Info ==="ipcs -m -lHuge Pages for Large Segments:
For very large shared memory segments (hundreds of MB or GB), standard 4KB pages become inefficient—each page requires a TLB (Translation Lookaside Buffer) entry, and TLBs are limited. Huge pages (2MB or 1GB on x86_64) reduce TLB pressure significantly.
To use huge pages with System V:
int shmid = shmget(key, size, IPC_CREAT | 0600 | SHM_HUGETLB);
To use huge pages with POSIX (Linux-specific):
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_HUGETLB, shm_fd, 0);
Huge pages must be pre-allocated by the system administrator. Check /proc/meminfo for HugePages_Total and HugePages_Free.
For performance-critical applications, align your shared memory structures to cache line boundaries (typically 64 bytes) to prevent false sharing. Design your total size to accommodate future growth—resizing shared memory is complex and often requires careful coordination between all attached processes.
Shared memory operations can fail in numerous ways. Robust error handling is essential—a failure to create or attach shared memory is typically fatal to your application's functionality.
Common Error Codes:
| errno | Cause | Function | Remediation |
|---|---|---|---|
| EEXIST | Segment exists with IPC_EXCL | shmget/shm_open | Remove IPC_EXCL or delete existing segment |
| ENOENT | Segment doesn't exist (no IPC_CREAT) | shmget/shm_open | Add IPC_CREAT or wait for creator |
| EACCES | Permission denied | shmget/shm_open/shmat/mmap | Check uid/gid and segment permissions |
| EINVAL | Invalid size or segment removed | shmget/shmat | Verify parameters; segment may be deleted |
| ENOMEM | Insufficient memory | shmget/mmap | Reduce size or free system memory |
| ENOSPC | Limit exceeded (SHMMNI/SHMALL) | shmget | Increase system limits or clean up segments |
| ENFILE | Too many open files system-wide | shm_open | Increase system file descriptor limit |
| EMFILE | Too many open files for process | shm_open | Close unused FDs or increase ulimit |
| EFAULT | Invalid address for attachment | shmat | Let kernel choose address (NULL) |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <sys/shm.h>#include <sys/ipc.h> /** * Demonstrates comprehensive error handling for shared memory operations. * Each error case is handled with appropriate action and clear messaging. */ typedef enum { SHM_SUCCESS = 0, SHM_ERR_FTOK, SHM_ERR_SHMGET, SHM_ERR_SHMAT, SHM_ERR_SHMDT, SHM_ERR_SHMCTL, SHM_ERR_RETRY_LIMIT} ShmError; const char* shm_error_string(ShmError err) { switch (err) { case SHM_SUCCESS: return "Success"; case SHM_ERR_FTOK: return "Failed to generate key"; case SHM_ERR_SHMGET: return "Failed to get/create segment"; case SHM_ERR_SHMAT: return "Failed to attach segment"; case SHM_ERR_SHMDT: return "Failed to detach segment"; case SHM_ERR_SHMCTL: return "Failed to control segment"; case SHM_ERR_RETRY_LIMIT: return "Retry limit exceeded"; default: return "Unknown error"; }} ShmError create_shared_memory_robust( const char *path, int proj_id, size_t size, int *shmid_out, void **ptr_out, int max_retries) { key_t key; int shmid; void *ptr; int retries = 0; // Generate key with retry logic (file might be temporarily unavailable) while (retries < max_retries) { key = ftok(path, proj_id); if (key != -1) break; if (errno == ENOENT) { fprintf(stderr, "Key file %s not found, creating...\n", path); FILE *f = fopen(path, "w"); if (f) fclose(f); retries++; continue; } else if (errno == EACCES) { fprintf(stderr, "No access to key file %s\n", path); return SHM_ERR_FTOK; } else { fprintf(stderr, "ftok error: %s\n", strerror(errno)); return SHM_ERR_FTOK; } } if (key == -1) return SHM_ERR_RETRY_LIMIT; // Create/get shared memory with handling for race conditions retries = 0; while (retries < max_retries) { // First try: create exclusively shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0660); if (shmid != -1) { printf("Created new segment with ID %d\n", shmid); break; } // Handle specific errors switch (errno) { case EEXIST: // Segment exists, try to attach shmid = shmget(key, 0, 0660); if (shmid != -1) { printf("Attached to existing segment %d\n", shmid); goto attach; } // If ENOENT here, segment was deleted between our attempts if (errno == ENOENT || errno == EIDRM) { retries++; continue; // Race condition, retry } fprintf(stderr, "Cannot access existing segment: %s\n", strerror(errno)); return SHM_ERR_SHMGET; case ENOSPC: fprintf(stderr, "System limit reached (SHMMNI or SHMALL)\n"); fprintf(stderr, "Run: ipcs -m to see segments\n"); fprintf(stderr, "Run: ipcrm -m <id> to remove unused segments\n"); return SHM_ERR_SHMGET; case ENOMEM: fprintf(stderr, "Insufficient memory for %zu bytes\n", size); return SHM_ERR_SHMGET; case EACCES: fprintf(stderr, "Permission denied for key 0x%x\n", key); return SHM_ERR_SHMGET; default: fprintf(stderr, "shmget failed: %s (errno %d)\n", strerror(errno), errno); return SHM_ERR_SHMGET; } } if (retries >= max_retries) return SHM_ERR_RETRY_LIMIT; attach: // Attach with retry for transient conditions retries = 0; while (retries < max_retries) { ptr = shmat(shmid, NULL, 0); if (ptr != (void *)-1) break; if (errno == EINVAL || errno == EIDRM) { fprintf(stderr, "Segment %d was removed during attach\n", shmid); return SHM_ERR_SHMAT; } else if (errno == ENOMEM) { fprintf(stderr, "No memory to map segment into address space\n"); return SHM_ERR_SHMAT; } else if (errno == EACCES) { fprintf(stderr, "Permission denied to attach segment\n"); return SHM_ERR_SHMAT; } retries++; } if (ptr == (void *)-1) return SHM_ERR_RETRY_LIMIT; *shmid_out = shmid; *ptr_out = ptr; return SHM_SUCCESS;} int main() { int shmid; void *ptr; ShmError result = create_shared_memory_robust( "/tmp/robust_shm_key", 'R', 4096, &shmid, &ptr, 5 // max retries ); if (result != SHM_SUCCESS) { fprintf(stderr, "Failed: %s\n", shm_error_string(result)); return EXIT_FAILURE; } printf("Success! shmid=%d, ptr=%p\n", shmid, ptr); // Use shared memory... // Cleanup shmdt(ptr); return EXIT_SUCCESS;}In production systems, wrap shared memory operations in abstraction layers that handle retries, logging, and metrics. Log every failure with full context (key, size, errno, surrounding state) to aid debugging. Consider circuit-breaker patterns for recovery from persistent failures.
We've covered the foundational concepts and practical techniques for creating shared memory regions. Let's consolidate the key takeaways:
What's Next:
Creating shared memory is only the beginning. In the next page, we explore attaching and detaching shared memory regions—how processes map segments into their address spaces, the nuances of address selection, and patterns for clean detachment when work is complete.
You now understand the mechanisms for creating shared memory segments using both System V and POSIX APIs. You've learned about key generation, permissions, size constraints, and robust error handling. Next, we'll explore how processes attach to and detach from these shared regions.