What is System V Shared Memory?
System V shared memory is the fastest form of Inter-Process Communication (IPC) available on Linux/Unix. Instead of passing data through kernel buffers (like pipes or message queues do), shared memory lets two or more processes map the same physical memory pages into their own virtual address spaces. Once mapped, reading or writing to that memory is just like accessing any normal variable — no system call overhead on every access.
Think of it like two people sharing a whiteboard. Person A writes something on the whiteboard; Person B can immediately read it without anyone having to “send” the message. The whiteboard is the shared memory segment.
Key point: Shared memory provides NO built-in synchronization. You must use semaphores (or mutexes) alongside shared memory to coordinate access between processes, otherwise data corruption will occur.
Every other IPC mechanism requires data to pass through the kernel. For example, when you write to a pipe, the data goes: user space → kernel buffer → user space. That means two copies of the data plus two system calls.
With shared memory, after the initial setup (which uses system calls), processes access the shared region as if it were normal memory. Zero copies. Zero system calls per access.
| IPC Mechanism | Data Copies | Kernel Involvement per Access | Speed |
|---|---|---|---|
| Pipe / FIFO | 2 copies | Yes (read/write syscall) | Slow |
| Message Queue | 2 copies | Yes (msgsnd/msgrcv) | Medium |
| Socket | 2+ copies | Yes (send/recv) | Slow |
| Shared Memory | 0 copies | No (direct access) | Fastest |
The kernel maintains a shared memory segment in physical RAM. Multiple processes can each call shmat() to attach (map) that segment into their own virtual address space. Each process may attach it at a different virtual address, but they all point to the same physical pages.
Both processes map the same physical segment — at different virtual addresses
The API has 4 key system calls. Here is the lifecycle:
| Step | System Call | Purpose | Who calls it |
|---|---|---|---|
| 1 | shmget() |
Create or get a shared memory segment by key | Creator (usually server/writer) |
| 2 | shmat() |
Attach (map) the segment into process address space | All processes that need access |
| 3 | shmdt() |
Detach (unmap) the segment from address space | Each process when done |
| 4 | shmctl() |
Control operations: delete, get/set info | Creator (cleanup) or admin |
Important: Detaching a segment (shmdt()) does NOT delete it from the kernel. The segment persists until explicitly removed with shmctl(IPC_RMID) or the system reboots. This is a common source of resource leaks!
A minimal example: one process creates and writes to shared memory; another reads it.
writer.c — creates segment and writes a message:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define SHM_KEY 0x1234 /* Agreed key between writer and reader */
#define SHM_SIZE 1024 /* Bytes to allocate */
int main(void)
{
int shmid;
char *shmp;
/* Step 1: Create the shared memory segment */
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0660);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
printf("Created segment, shmid = %d\n", shmid);
/* Step 2: Attach (map) the segment */
shmp = shmat(shmid, NULL, 0); /* NULL = let kernel choose address */
if (shmp == (void *) -1) { /* shmat returns (void*)-1 on error */
perror("shmat");
exit(EXIT_FAILURE);
}
printf("Attached at address %p\n", shmp);
/* Step 3: Use the memory like any normal memory */
strncpy(shmp, "Hello from writer!", SHM_SIZE - 1);
printf("Wrote: %s\n", shmp);
/* Step 4: Detach — segment still exists in kernel */
if (shmdt(shmp) == -1) {
perror("shmdt");
exit(EXIT_FAILURE);
}
printf("Detached. Segment persists in kernel until deleted.\n");
return 0;
}
reader.c — attaches and reads:
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define SHM_KEY 0x1234
#define SHM_SIZE 1024
int main(void)
{
int shmid;
char *shmp;
/* Step 1: Get the existing segment (no IPC_CREAT, size=0 means "existing") */
shmid = shmget(SHM_KEY, 0, 0);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
/* Step 2: Attach read-only (SHM_RDONLY flag) */
shmp = shmat(shmid, NULL, SHM_RDONLY);
if (shmp == (void *) -1) {
perror("shmat");
exit(EXIT_FAILURE);
}
/* Step 3: Read the data */
printf("Reader got: %s\n", shmp);
/* Step 4: Detach */
if (shmdt(shmp) == -1) {
perror("shmdt");
exit(EXIT_FAILURE);
}
/* Step 5: Delete the segment (cleanup) */
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID");
exit(EXIT_FAILURE);
}
printf("Segment deleted.\n");
return 0;
}
Compile and run:
gcc writer.c -o writer && gcc reader.c -o reader
./writer (run first)
./reader (run in another terminal)
When you create a shared memory segment with shmget(), the kernel allocates a shmid_ds structure to track it. This is similar to how the kernel tracks files with inodes.
The struct shmid_ds (from <sys/shm.h>) contains:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment in bytes */
time_t shm_atime; /* Last shmat() time */
time_t shm_dtime; /* Last shmdt() time */
time_t shm_ctime; /* Last shmctl() change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat/shmdt */
shmatt_t shm_nattch; /* Number of current attaches */
/* ... kernel internal fields ... */
};
You can inspect this structure using shmctl(shmid, IPC_STAT, &ds). This is extremely useful for debugging and interview questions about “how many processes have this segment attached.”
Next: shmget() — Creating Segments →
Q1. Why is shared memory considered the fastest IPC mechanism?
Because after the initial setup, processes read and write directly to shared physical memory pages — no data copying through kernel buffers, no system call overhead on each access. Other IPC mechanisms like pipes and message queues require the kernel to copy data twice (write to kernel buffer, read from kernel buffer).
Q2. If shared memory is the fastest, why don’t we always use it?
Shared memory provides NO synchronization. You must separately manage concurrent access using semaphores or mutexes. This extra complexity makes simpler use cases easier to handle with message queues or pipes. Shared memory is best when you need high-throughput data transfer.
Q3. What happens to a shared memory segment when all processes detach from it?
The segment continues to exist in the kernel. It is not automatically deleted when the last process detaches. You must explicitly call shmctl(shmid, IPC_RMID, NULL) to delete it. This is a key difference from anonymous mappings with mmap().
Q4. What is the difference between detaching and deleting a shared memory segment?
shmdt() removes the mapping from the calling process’s address space only — like closing a file descriptor. The segment itself remains in the kernel. shmctl(IPC_RMID) marks the segment for deletion from the kernel — it is actually removed once all processes have detached.
Q5. What does shmat() return on error? Why is this unusual?
On error, shmat() returns (void *) -1 (i.e., (void *)-1), not NULL. This is because NULL (address 0) is actually a potentially valid address to attach to on some systems. Always check: if (shmp == (void *) -1).
Q6. Can two processes attach the same segment at different virtual addresses?
Yes. Each process gets to choose (or let the kernel choose) where in its virtual address space the segment maps. Both virtual addresses point to the same physical memory pages. This is why you must be careful with pointers stored inside shared memory — a pointer valid in one process’s virtual space may be meaningless in another’s.
