Mapping Shared Memory into Your Process
After you have a shmid from shmget(), the segment is just a kernel object — your process cannot access it yet. shmat() (attach) creates a mapping in your process’s virtual address space, making the shared memory accessible like any ordinary pointer. When you are done, shmdt() (detach) removes that mapping.
Attaching and detaching are similar in concept to opening and closing a file, but the analogy is not perfect — a detached segment still exists in the kernel, unlike closing a file does not delete it either.
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
/* Returns: address of attached segment on success,
(void *) -1 on error (NOT NULL!) */
| Parameter | Meaning |
|---|---|
shmid |
The identifier returned by shmget() |
shmaddr |
Where in virtual address space to attach. Pass NULL to let the kernel choose (strongly recommended). |
shmflg |
0 for read/write, SHM_RDONLY for read-only, SHM_RND for address rounding |
Option 1: NULL (recommended) — kernel chooses
/* Best practice: always pass NULL and let the kernel decide */
char *shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("shmat");
exit(EXIT_FAILURE);
}
/* shmp now points to the attached region */
The kernel picks an available virtual address in the region above TASK_UNMAPPED_BASE (typically 0x40000000 on x86-32). This avoids conflicts with the program’s heap and stack.
Option 2: Explicit address (not recommended for most programs)
/* Attach at a specific address — risky: address might be in use */
void *desired = (void *) 0x50000000;
char *shmp = shmat(shmid, desired, SHM_RND);
/* SHM_RND rounds the address down to nearest SHMLBA boundary */
/* SHMLBA is usually the page size (4096) on most systems */
Specifying an address is almost never needed. If you do specify one, use SHM_RND to let the kernel round it to a valid boundary (SHMLBA — Lower Boundary Address).
Option 3: Explicit address without SHM_RND
/* shmaddr must be a multiple of SHMLBA, else EINVAL */
/* SHMLBA is defined in <sys/shm.h> */
#include <sys/shm.h>
printf("SHMLBA = %ld\n", SHMLBA); /* usually 4096 on modern Linux */
| Flag | Effect |
|---|---|
0 |
Default: read and write access |
SHM_RDONLY |
Attach read-only. Writes to the returned pointer cause SIGSEGV. Process must have read permission on the segment. |
SHM_RND |
Round down shmaddr to nearest SHMLBA boundary (only useful when shmaddr != NULL) |
SHM_REMAP (Linux) |
Replace any existing mapping at shmaddr. Linux-specific, non-portable. |
/* Read-only attach: useful for a "reader" that must not corrupt the data */
struct SharedData *shmp = shmat(shmid, NULL, SHM_RDONLY);
if (shmp == (void *) -1) {
perror("shmat SHM_RDONLY");
exit(EXIT_FAILURE);
}
/* Attempting to write will crash with SIGSEGV: */
/* shmp->value = 42; // DANGER: would cause SIGSEGV */
/* Safe read only: */
printf("Value = %d\n", shmp->value);
#include <sys/shm.h>
int shmdt(const void *shmaddr);
/* Returns: 0 on success, -1 on error */
shmdt() removes the mapping from the calling process’s virtual address space. It takes the pointer that was returned by shmat(). After shmdt():
- The virtual address mapping is removed (accessing the pointer would cause a segfault)
- The kernel decrements the segment’s
shm_nattchcount - The segment still exists in the kernel (until deleted with
shmctl(IPC_RMID))
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
int main(void)
{
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0660);
char *shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) { perror("shmat"); exit(1); }
/* Use the memory */
shmp[0] = 'A';
printf("Before detach: shmp[0] = %c\n", shmp[0]);
/* Detach */
if (shmdt(shmp) == -1) {
perror("shmdt");
exit(EXIT_FAILURE);
}
/* After shmdt(), shmp is invalid — do NOT use it */
/* shmp[0] would cause SIGSEGV here */
printf("Detached successfully.\n");
/* Segment still exists — must delete explicitly */
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
Common mistake: Using the pointer after shmdt(). Always set the pointer to NULL after detaching to prevent accidental use: shmdt(shmp); shmp = NULL;
A single process can attach the same segment multiple times. Each attach returns a different virtual address pointing to the same physical memory. Changes through one pointer are immediately visible through others.
#include <stdio.h>
#include <sys/shm.h>
int main(void)
{
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0660);
/* Attach twice — get two different virtual addresses */
int *ptr1 = shmat(shmid, NULL, 0);
int *ptr2 = shmat(shmid, NULL, 0);
printf("ptr1 = %p\n", ptr1);
printf("ptr2 = %p\n", ptr2);
/* ptr1 and ptr2 are different virtual addresses */
*ptr1 = 12345;
printf("Written via ptr1: %d\n", *ptr1);
printf("Read via ptr2: %d\n", *ptr2); /* Same value! */
/* Detach both */
shmdt(ptr1);
shmdt(ptr2);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
/* ptr1 = 0x7f1234000000
ptr2 = 0x7f1234001000 (different VA, same physical page)
Written via ptr1: 12345
Read via ptr2: 12345 */
One practical use: attach the same segment once read-write and once read-only (e.g., for a buffer you want to write in one part of the code but pass a const pointer to another part).
The most common pattern is to define a struct that describes the layout of the shared memory, then cast the returned pointer to that struct type.
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
/* Define the shared memory layout */
#define MAX_MSG 256
struct SharedBuffer {
int ready; /* Flag: 1 = data is ready */
int data_len; /* Length of data in buf */
char buf[MAX_MSG]; /* The actual data */
};
int main(void)
{
int shmid;
struct SharedBuffer *shmp;
/* Create segment sized to hold our struct */
shmid = shmget(IPC_PRIVATE, sizeof(struct SharedBuffer),
IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); return 1; }
/* Attach and cast to struct pointer */
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) { perror("shmat"); return 1; }
/* Initialize the struct */
shmp->ready = 0;
shmp->data_len = 0;
memset(shmp->buf, 0, MAX_MSG);
/* Write data */
strncpy(shmp->buf, "Hello via shared memory!", MAX_MSG - 1);
shmp->data_len = strlen(shmp->buf);
shmp->ready = 1;
printf("Wrote: %s (len=%d)\n", shmp->buf, shmp->data_len);
shmdt(shmp);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
Since different processes may attach the same segment at different virtual addresses, a pointer value that is valid in Process A is likely meaningless (or dangerous) in Process B.
| Process A | Shared Memory | Process B |
|---|---|---|
Segment at 0x40000000 |
ptr = 0x40000010 stored here |
Segment at 0x60000000 |
| A reads ptr → valid (0x40000010 is inside its segment) | B reads ptr → INVALID (0x40000010 is NOT in B’s segment range 0x60000000+) |
/* WRONG — do not store absolute pointers in shared memory */
struct BadSharedData {
char *name; /* Pointer — WRONG! Different VA in each process */
int value;
};
/* RIGHT — use offsets relative to the segment's start address */
struct GoodSharedData {
size_t name_offset; /* Offset from start of segment */
int value;
char data[256]; /* Data stored inline */
};
/* To access the name field using offset: */
void *shm_base = shmat(shmid, NULL, 0);
struct GoodSharedData *s = shm_base;
char *name_ptr = (char *)shm_base + s->name_offset;
/* This works in any process because offset is relative, not absolute */
Q1. Why does shmat() return (void*)-1 on error instead of NULL?
Because NULL (virtual address 0) could theoretically be a valid attach address on some systems (if shmaddr is explicitly specified and the system allows it). Returning (void*)-1 (all bits set, i.e., the highest possible address) avoids this ambiguity. Always check shmp == (void*)-1, not shmp == NULL.
Q2. What happens if you call shmdt() with an address that was not returned by shmat()?
shmdt() fails with EINVAL. The address must be exactly the pointer returned by shmat() — you cannot pass a pointer to somewhere inside the segment, only the exact start address that was returned.
Q3. Can two processes attach the same segment and access it simultaneously without synchronization?
They can attach and access it, but without synchronization, concurrent writes (or a write and a read) create a race condition — the data will be in an inconsistent, unpredictable state. Shared memory provides no locking. You must use a semaphore set (System V semaphores) or POSIX mutex stored in the shared memory itself.
Q4. What is SHMLBA and when does it matter?
SHMLBA (Shared Memory Lower Boundary Address) is the alignment requirement for explicit attach addresses. On most modern Linux systems it equals the page size (4096 bytes). It matters only when you specify a non-NULL shmaddr — the address must be aligned to SHMLBA, or you must use SHM_RND to let the kernel round it down.
Q5. Why should you not store pointers inside shared memory?
Because each process may attach the segment at a different virtual address. A pointer that is valid in Process A points to absolute virtual address X. Process B, having the segment at a different base address, interprets X as pointing to a completely different (possibly unmapped) location. Use offsets relative to the segment’s start address instead.
Q6. After shmdt(), can you use the pointer again?
No. After shmdt(), the virtual address mapping is removed. Dereferencing the pointer produces undefined behavior — typically a SIGSEGV (segmentation fault). Best practice: assign the pointer to NULL immediately after detaching.
