After shmget() gives you a segment identifier, shmat() attaches the segment — it maps it into your process’s virtual address space and returns a pointer. From that point on you use the segment exactly like normal memory: dereference the pointer, cast it to your struct, memcpy into it, whatever you need.
When you are done, shmdt() detaches the segment — it removes the mapping from your virtual address space. The segment itself still exists; only your process loses access to it. Detach happens automatically at process exit even if you forget to call it.
#include <sys/types.h> /* For portability */
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
/* Returns: address at which segment is attached on success,
or (void *) -1 on error */
| Parameter | Purpose |
|---|---|
shmid |
The segment identifier returned by shmget(). |
shmaddr |
Desired attach address in virtual memory. Pass NULL to let the kernel choose (strongly recommended). |
shmflg |
Control flags: 0 for read-write, SHM_RDONLY, SHM_RND, SHM_REMAP. |
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
/* Returns: 0 on success, or -1 on error */
shmaddr must be the value previously returned by shmat(). Passing any other address causes EINVAL. After this call the pointer is invalid — any further dereference causes a segfault.
When you call shmat(), the kernel adds new entries to the calling process’s page table. These entries map some range of virtual addresses to the physical frames of the shared segment. Two processes can map the same physical frames at different virtual addresses — the pointer values will differ but they point to the same RAM.
Notice: ptr A (0x7f3a…) ≠ ptr B (0x7f8c…) — different virtual addresses, same physical frames. Never store raw pointers inside a shared memory struct — the virtual addresses differ between processes!
Let the kernel choose a suitable address. Always portable, never conflicts with existing mappings. This is what you should use 99% of the time.
void *shm = shmat(shmid, NULL, 0);
/* shm is now a kernel-chosen address, guaranteed valid */
Attach at exactly the address you specify. It must be a multiple of the page size or you get EINVAL. Fails if that range is already in use. Not portable.
/* Must be page-aligned */
void *addr = (void *)0x40000000; /* must be multiple of 4096 */
void *shm = shmat(shmid, addr, 0);
if (shm == (void *)-1) perror("shmat fixed addr");
Address is rounded down to the nearest SHMLBA boundary. SHMLBA (Shared Memory Low Boundary Address) is a multiple of the page size — on x86 it equals the page size itself. Only relevant on architectures where misaligned segment attaches cause cache inconsistencies (e.g., some RISC architectures).
#include <sys/shm.h>
void *desired = (void *)0x40001234; /* not aligned */
/* SHM_RND rounds down to nearest SHMLBA boundary */
void *shm = shmat(shmid, desired, SHM_RND);
printf("SHMLBA = %ld\n", SHMLBA);
Pass SHM_RDONLY in shmflg to attach the segment for reading only. Attempting to write to a read-only attachment causes a SIGSEGV (segmentation fault), the same signal you get from writing to a const pointer or a write-protected page.
To attach read-only, the process needs only read permission on the segment. Without SHM_RDONLY, both read and write permissions are required.
/* Read-only attach */
const char *shm_ro = shmat(shmid, NULL, SHM_RDONLY);
if (shm_ro == (void *)-1) { perror("shmat rdonly"); exit(1); }
printf("Data: %s\n", shm_ro); /* OK — reading */
/* shm_ro[0] = 'X'; — WRONG! This would cause SIGSEGV */
shmdt(shm_ro);
/* Practical: producer attaches read-write, consumer read-only */
/* Producer (writer): */
char *buf = shmat(shmid, NULL, 0); /* read-write */
/* Consumer (reader): */
const char *buf = shmat(shmid, NULL, SHM_RDONLY); /* read-only */
A single process can call shmat() on the same segment multiple times. Each call creates an independent mapping at a different virtual address. All mappings point to the same physical RAM.
You can mix read-write and read-only attachments in the same process. This is unusual but valid — you might attach read-only for most code and keep one read-write pointer for a privileged update function.
/* Multiple attaches — same segment, different virtual addresses */
char *rw_ptr = shmat(shmid, NULL, 0); /* read-write */
char *ro_ptr = shmat(shmid, NULL, SHM_RDONLY); /* read-only */
/* Both point to the same physical memory */
printf("rw_ptr = %p\n", (void *)rw_ptr);
printf("ro_ptr = %p\n", (void *)ro_ptr);
/* These addresses will differ, but *rw_ptr == *ro_ptr */
strcpy(rw_ptr, "hello");
printf("via ro: %s\n", ro_ptr); /* prints: hello */
/* Must detach both independently */
shmdt(rw_ptr);
shmdt(ro_ptr);
Normally, attaching at an address range already occupied by another mapping fails with EINVAL. The Linux-specific SHM_REMAP flag overrides this: it replaces any existing mapping in the target range with the new shared memory segment.
SHM_REMAP requires shmaddr to be non-NULL. It is a non-standard Linux extension — do not use it in portable code.
/* SHM_REMAP: replace whatever is at 'addr' with this segment */
void *addr = (void *)0x60000000;
/* First attach something at that address */
void *old = shmat(other_shmid, addr, 0);
/* Now replace it — without SHM_REMAP this would fail with EINVAL */
void *new = shmat(shmid, addr, SHM_REMAP);
if (new == (void *)-1) {
perror("shmat SHM_REMAP");
}
/* old mapping is gone, 'addr' now maps to shmid */
shmdt() is straightforward but has important rules:
Any pointer arithmetic on the shmat address is fine during use, but you must pass the original address back to
shmdt().If you do
ptr++ after shmat(), you cannot pass ptr to shmdt(). Save the original./* CORRECT: save original pointer */
char *shm_base = shmat(shmid, NULL, 0); /* original */
char *p = shm_base; /* working pointer */
while (*p) p++; /* advance p */
/* ... */
shmdt(shm_base); /* pass original — correct */
/* shmdt(p); would be WRONG if p != shm_base */
/* WRONG pattern — don't do this */
char *shm = shmat(shmid, NULL, 0);
shm += 100; /* moved shm away from base */
shmdt(shm); /* EINVAL — wrong address! */
| Flag | Effect | Notes |
|---|---|---|
0 |
Attach read-write. Kernel chooses address if shmaddr=NULL. | Most common. |
SHM_RDONLY |
Attach read-only. Writes cause SIGSEGV. | Only read permission needed on segment. |
SHM_RND |
Round shmaddr down to SHMLBA. Requires shmaddr != NULL. | Rarely needed; mainly for non-x86 architectures. |
SHM_REMAP |
Replace existing mapping at shmaddr. Requires shmaddr != NULL. | Linux only — non-standard extension. |
Because two processes attach the same segment at different virtual addresses, a pointer that is valid in Process A (pointing into the shared segment) will point to the wrong place in Process B — or crash it outright.
| WRONG — Pointer stored in shared memory | ||
|---|---|---|
| Process A sees | Shared Memory | Process B sees |
| attached at 0x7f30 | ptr field = 0x7f30 + 100 | attached at 0x7f80 → ptr 0x7f30+100 = INVALID! |
Use offsets from the base address instead of absolute pointers:
/* WRONG: storing absolute pointer in shared memory */
struct bad_shm {
char data[512];
char *next; /* absolute pointer — process-specific! */
};
/* CORRECT: use offset from base */
struct good_shm {
char data[512];
size_t next_offset; /* offset within the segment */
};
/* Writer (Process A): */
struct good_shm *shm = shmat(shmid, NULL, 0);
shm->next_offset = sizeof(struct good_shm); /* offset, not pointer */
/* Reader (Process B): */
struct good_shm *shm = shmat(shmid, NULL, 0); /* different virtual addr */
struct good_shm *next = (struct good_shm *)((char *)shm + shm->next_offset);
/* This works regardless of attach address */
This example puts a struct directly in shared memory along with a POSIX mutex (configured for process sharing) to prevent race conditions.
/* shared_struct.h — put this in shared memory */
#ifndef SHARED_STRUCT_H
#define SHARED_STRUCT_H
#include <pthread.h>
#define MAX_MSG 256
typedef struct {
pthread_mutex_t mutex; /* MUST be process-shared */
int count;
char message[MAX_MSG];
int ready; /* flag: 1 = new data available */
} shared_data_t;
#endif
/* producer.c — creates segment and writes data */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#include "shared_struct.h"
#define KEY_PATH "/tmp"
#define KEY_ID 'P'
int main(void)
{
key_t key = ftok(KEY_PATH, KEY_ID);
int shmid = shmget(key, sizeof(shared_data_t),
IPC_CREAT | IPC_EXCL | 0660);
if (shmid == -1) { perror("shmget"); exit(1); }
shared_data_t *shm = shmat(shmid, NULL, 0);
if (shm == (void *)-1) { perror("shmat"); exit(1); }
/* Initialize mutex for cross-process sharing */
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&shm->mutex, &attr);
pthread_mutexattr_destroy(&attr);
shm->count = 0;
shm->ready = 0;
for (int i = 1; i <= 5; i++) {
pthread_mutex_lock(&shm->mutex);
shm->count = i;
snprintf(shm->message, MAX_MSG, "Message #%d from PID %d", i, getpid());
shm->ready = 1;
pthread_mutex_unlock(&shm->mutex);
printf("[producer] wrote: %s\n", shm->message);
sleep(1);
}
/* Signal done */
pthread_mutex_lock(&shm->mutex);
shm->ready = -1; /* sentinel: done */
pthread_mutex_unlock(&shm->mutex);
/* Cleanup after consumer has finished */
sleep(2);
pthread_mutex_destroy(&shm->mutex);
shmdt(shm);
shmctl(shmid, IPC_RMID, NULL);
printf("[producer] segment deleted\n");
return 0;
}
/* consumer.c — reads from existing segment */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#include "shared_struct.h"
#define KEY_PATH "/tmp"
#define KEY_ID 'P'
int main(void)
{
key_t key = ftok(KEY_PATH, KEY_ID);
int shmid = shmget(key, 0, 0); /* open existing */
if (shmid == -1) { perror("shmget"); exit(1); }
shared_data_t *shm = shmat(shmid, NULL, 0);
if (shm == (void *)-1) { perror("shmat"); exit(1); }
int last_count = 0;
while (1) {
pthread_mutex_lock(&shm->mutex);
int done = (shm->ready == -1);
int fresh = (shm->ready == 1 && shm->count != last_count);
if (fresh) {
printf("[consumer] read: %s\n", shm->message);
last_count = shm->count;
}
pthread_mutex_unlock(&shm->mutex);
if (done) break;
usleep(100000); /* poll every 100ms */
}
printf("[consumer] producer finished\n");
shmdt(shm);
return 0;
}
gcc -o producer producer.c -lpthread
gcc -o consumer consumer.c -lpthread
# Terminal 1:
./producer
# Terminal 2:
./consumer
The shmid_ds structure maintained by the kernel contains shm_nattch — the number of processes currently attached to the segment. You can query it with shmctl(IPC_STAT).
#include <sys/shm.h>
#include <stdio.h>
void print_shm_info(int shmid)
{
struct shmid_ds ds;
if (shmctl(shmid, IPC_STAT, &ds) == -1) {
perror("shmctl IPC_STAT");
return;
}
printf("Segment size: %zu bytes\n", ds.shm_segsz);
printf("Attach count: %lu\n", (unsigned long)ds.shm_nattch);
printf("Owner UID: %u\n", ds.shm_perm.uid);
printf("Creator PID: %d\n", (int)ds.shm_cpid);
printf("Last attach PID: %d\n", (int)ds.shm_lpid);
}
int main(void)
{
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
if (shmid == -1) { perror("shmget"); return 1; }
printf("Before attach:\n");
print_shm_info(shmid);
void *p = shmat(shmid, NULL, 0);
printf("\nAfter attach:\n");
print_shm_info(shmid); /* nattch = 1 */
shmdt(p);
printf("\nAfter detach:\n");
print_shm_info(shmid); /* nattch = 0 */
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
| Function | errno | Cause & Fix |
|---|---|---|
shmat |
EACCES |
No read/write permission on segment. Check segment perms. |
shmat |
EINVAL |
Bad shmid, or shmaddr not page-aligned (without SHM_RND), or range in use without SHM_REMAP. |
shmat |
ENOMEM |
Not enough virtual address space to attach. Common in 32-bit processes with large segments. |
shmdt |
EINVAL |
shmaddr is not the value returned by shmat() (e.g., pointer was incremented). |
shmat() returns a void * pointing to the start of the shared memory segment in the calling process’s virtual address space. You cast it to a pointer-to-struct (or char*, etc.) and then read/write through it just like any normal memory. On error it returns (void *) -1 — not NULL — so always check if (ptr == (void *)-1).
Passing NULL lets the kernel select a free, aligned address — portable across architectures and guaranteed not to conflict with existing mappings. Specifying a fixed address is not portable (valid on one OS/arch, invalid on another), and will fail with EINVAL if that range is already in use (e.g., by a library already loaded at that address).
The process receives a SIGSEGV (segmentation fault). This is because the kernel marks the page table entries as read-only; any write triggers a hardware protection fault which the kernel converts to SIGSEGV and delivers to the process.
Yes. Each shmat() call creates an independent mapping at a different virtual address, all backed by the same physical RAM. You can mix read-write and read-only. Each attachment must be individually released with shmdt(). This is useful if you need to pass the segment to code that expects to receive pointers via different mechanisms.
Different processes attach the segment at different virtual addresses. A pointer value recorded by Process A refers to an address in A’s virtual space — it will be wrong (or outright invalid) in Process B. Always store offsets from the base of the segment, then reconstruct pointers as (char *)shm_base + offset.
SHMLBA (Shared Memory Low Boundary Address) is a multiple of the system page size. SHM_RND rounds the user-supplied attach address down to the nearest SHMLBA boundary. On x86/x86-64 SHMLBA equals the page size, so alignment is trivial. On some RISC architectures (SPARC, MIPS), cache aliasing means segments must be attached at SHMLBA-aligned addresses to ensure all attachments have a consistent view in the CPU cache.
shmdt() removes the segment’s mapping from the calling process’s virtual address space. If you forget it, the mapping is automatically removed when the process exits. The segment itself is NOT deleted — it persists until shmctl(IPC_RMID) is called. The only consequence of missing shmdt() is a minor virtual address space leak during the process lifetime.
Place a pthread_mutex_t inside the shared struct. Before initializing it, create a pthread_mutexattr_t, call pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED), then pthread_mutex_init(&shm->mutex, &attr). The PTHREAD_PROCESS_SHARED attribute makes the mutex usable across process boundaries as long as it lives in shared memory visible to both processes.
| Task | Code |
|---|---|
| Attach read-write | void *p = shmat(shmid, NULL, 0); |
| Attach read-only | void *p = shmat(shmid, NULL, SHM_RDONLY); |
| Check for error | if (p == (void *)-1) { perror("shmat"); } |
| Cast to struct | my_struct_t *s = (my_struct_t *)shmat(shmid, NULL, 0); |
| Detach | shmdt(original_ptr); |
| Check nattch | shmctl(shmid, IPC_STAT, &ds); ds.shm_nattch; |
| Delete segment | shmctl(shmid, IPC_RMID, NULL); |
System V Shared Memory – Chapter 48 | EmbeddedPathashala
