Beej's Guide to Unix IPC — Chapter 9

Shared Memory

Direct pointer access to memory shared between processes — the fastest IPC mechanism.

Prerequisites: Semaphores (Ch. 8) + ftok() keys. That's it.
9
Chapters
3+
Simulations
0
Assumed Knowledge

Chapter 0: Why Shared Memory?

Every IPC mechanism we've seen so far involves copying data: from one process into the kernel (write/msgsnd), then from the kernel into another process (read/msgrcv). That's two copies per message. For high-performance applications — games, databases, real-time systems — this overhead adds up.

Shared memory eliminates the copies entirely. Both processes get a pointer to the same physical memory. When Process A writes to that pointer, Process B sees the change instantly. No kernel involvement for the data transfer itself.

The mental model: Imagine two people sharing a whiteboard. Neither has to write a letter and hand it to a postman. They both just look at the same board. If one writes "Hello", the other sees it immediately. The catch? If both write at the same time, you get a mess. That's why shared memory needs semaphores.
Shared Memory vs Pipes

Click "Write" to have Process A modify the shared segment. Process B sees changes instantly.

Segment: empty
What makes shared memory faster than pipes or message queues?

Chapter 1: shmget() — Creating a Segment

Just like message queues and semaphores, shared memory uses the same System V pattern: ftok() for the key, then a get function to create or connect.

c
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
ArgumentPurpose
keyUnique key from ftok()
sizeSize in bytes of the shared memory segment
shmflgPermissions OR'd with IPC_CREAT
c
key_t key;
int shmid;

key = ftok("/home/beej/somefile3", 'R');
shmid = shmget(key, 1024, 0644 | IPC_CREAT);

This creates a 1K shared memory segment with rw-r--r-- permissions. The shmid is just a handle — you can't use it as a pointer yet. For that, you need shmat().

Note: If the segment already exists, shmget() just connects to it (assuming IPC_CREAT is set). It doesn't hurt to always include IPC_CREAT.
What does the size argument to shmget() specify?

Chapter 2: shmat() — Getting a Pointer

This is the magic step. shmat() takes your segment ID and returns an actual pointer you can use like any other C pointer:

c
void *shmat(int shmid, void *shmaddr, int shmflg);
ArgumentPurpose
shmidSegment ID from shmget()
shmaddrWhere to map it (set to 0 to let the OS choose)
shmflgSHM_RDONLY for read-only, or 0 for read-write
c
char *data;

data = shmat(shmid, (void *)0, 0);
if (data == (char *)(-1))
    perror("shmat");
Critical detail: shmat() returns a void pointer cast to -1 on failure (not NULL!). You must check for (char *)(-1), not NULL.

Once you have the pointer, you can read and write to it like any memory. The pointer is just a char * (or whatever type you cast it to). You're directly touching the shared segment.

Key insight: The pointer from shmat() points to the same physical memory page in every process that attaches. No copying, no system calls needed for the actual data transfer. Write to it in Process A, read it in Process B — instant.
What does shmat() return on success?

Chapter 3: Reading and Writing

Once you have the pointer from shmat(), reading and writing is trivially easy. It's just normal C pointer operations:

c
char *data = shmat(shmid, (void *)0, 0);

/* Write to the segment */
printf("Enter a string: ");
fgets(data, 1024, stdin);

/* Read from the segment */
printf("Segment contains: %s\n", data);

That's it. No special system calls for reading or writing. data behaves exactly like any other pointer. You can treat it as a char *, a pointer to a struct, an array — whatever you need.

Process A
strcpy(data, "Hello!") — writes directly to shared memory
↓ same physical memory page
Process B
printf("%s", data) — reads "Hello!" instantly
No boundary enforcement: The segment is just raw bytes. If you write 2000 bytes into a 1024-byte segment, you'll overwrite whatever comes after it in memory. Be careful with buffer sizes, just as you would with any pointer.
Do you need special system calls to read from a shared memory segment?

Chapter 4: Detaching and Deleting

When you're done with the shared memory segment, there are two separate steps: detach (disconnect your process) and delete (remove the segment from the system).

c
/* Detach: disconnect from the segment */
shmdt(data);    /* pass the pointer from shmat() */

/* Delete: remove the segment from the kernel */
shmctl(shmid, IPC_RMID, NULL);
Critical distinction: Detaching does NOT destroy the segment. Other processes can still be attached to it. The segment is only destroyed when you call shmctl(shmid, IPC_RMID, NULL). And even then, it waits until all processes have detached before actually freeing the memory.
OperationFunctionEffect
Detachshmdt(ptr)This process can no longer access the segment
Deleteshmctl(id, IPC_RMID, NULL)Segment will be destroyed after all processes detach

After shmdt(), any access through the old pointer will cause a segmentation fault. The pointer is dangling.

Cleanup tip: Segments persist in the kernel like message queues. Use ipcs -m to list them and ipcrm -m <shmid> to delete them. Always clean up after yourself.
What is the difference between shmdt() and shmctl() with IPC_RMID?

Chapter 5: Concurrency Dangers

Here's the elephant in the room: shared memory has no built-in synchronization. If two processes write to the same address at the same time, you get a data race. The result is undefined — corrupted data, partial writes, nonsense values.

The problem: Process A is writing "Hello World" to the segment. Halfway through, Process B reads it and gets "Hello Worxx" (garbage). Or worse, Process A writes the first half of a struct while Process B writes the second half. The result is a Frankenstein object.

The solution: use semaphores (from Chapter 8) to protect access:

c
/* Lock the semaphore before writing */
sb.sem_op = -1;
semop(semid, &sb, 1);

/* Write to shared memory (critical section) */
strcpy(data, "Hello World");

/* Unlock the semaphore */
sb.sem_op = 1;
semop(semid, &sb, 1);
Key insight: Shared memory + semaphores is the canonical combination. Shared memory provides the speed (no copying). Semaphores provide the safety (no data races). You almost never use one without the other.
Why does shared memory typically require semaphores?

Chapter 6: shmdemo.c — The Full Program

Beej's demo program reads or writes to a shared memory segment depending on command-line arguments. Run it with an argument to store data, without to read it:

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_SIZE 1024

int main(int argc, char *argv[]) {
    key_t key;
    int shmid;
    char *data;

    key = ftok("shmdemo.c", 'R');
    shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT);

    data = shmat(shmid, (void *)0, 0);
    if (data == (char *)(-1)) {
        perror("shmat");
        exit(1);
    }

    if (argc == 2) {
        printf("writing to segment: \"%s\"\n", argv[1]);
        strncpy(data, argv[1], SHM_SIZE);
    } else {
        printf("segment contains: \"%s\"\n", data);
    }

    shmdt(data);
    return 0;
}
Try it: Run shmdemo "Hello, shared memory!" to write. Then run shmdemo (no args) to read. The data persists between runs because the segment lives in the kernel. Use ipcrm to delete when done.
$ shmdemo "Hello!"
writing to segment: "Hello!"
↓ data persists in kernel memory
$ shmdemo
segment contains: "Hello!"
In shmdemo, what happens when you run it with no command-line arguments?

Chapter 7: Shared Memory Simulator

Watch two processes read and write to the same memory region. Without synchronization, simultaneous writes can corrupt data.

Shared Memory Segment

Click to have processes write values into the shared segment. Both see the same memory.

Segment: empty
Can two processes write to different offsets of the same shared memory segment simultaneously?

Chapter 8: Beyond Shared Memory

Shared memory is the fastest IPC because there's no kernel involvement for actual data transfer. But it requires careful synchronization.

FeaturePipes/FIFOsMessage QueuesShared Memory
SpeedMedium (2 copies)Medium (2 copies)Fast (0 copies)
SyncBuilt-in (blocking)Built-in (blocking)Manual (semaphores)
Data modelByte streamDiscrete messagesRaw memory
DirectionOne-wayOne-way (per queue)Any
PersistenceProcess lifetimeKernel-persistentKernel-persistent
CleanupAutomaticManual (ipcrm)Manual (ipcrm)
Coming up: One more way to share memory between processes: memory-mapped files. They combine the speed of shared memory with the persistence of files — and they're simpler to use.

"The cool thing about shared memory segments is that they are what they sound like: a segment of memory that is shared between processes." — Beej

Which IPC mechanism provides the fastest data transfer?