shmat() and shmdt() Attaching & Detaching Shared Memory

 

shmat() and shmdt()
Chapter 48 Part 3 – Attaching & Detaching Shared Memory | EmbeddedPathashala
Part 3
shmat / shmdt
Attach
Map into Address Space
Flags
SHM_RDONLY, SHM_REMAP

shmget() gives you an ID but you can’t use the memory yet. You need to attach the segment into your process’s virtual address space using shmat(). This is what causes the kernel to create a page-table mapping from your process’s virtual memory to the shared physical RAM.

When you’re done, shmdt() removes that mapping from your process. It does not delete the segment — it just makes it inaccessible from this process.

shmat() – Attach a Segment

#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
 * shmid   – ID returned by shmget()
 * shmaddr – where in your address space to map it:
 *             NULL → let kernel choose (recommended)
 *             non-NULL → request a specific address (use with SHM_RND)
 * shmflg  – SHM_RDONLY, SHM_REMAP, SHM_RND, or 0
 *
 * Returns: pointer to start of attached segment on success
 *          (void *)-1 on error (NOT NULL — check for -1)
 */

Important: shmat() returns (void *)-1 on error, not NULL. Always check if (addr == (void *)-1), not if (addr == NULL).

shmaddr Options Explained

Option 1: NULL (Best)
shmat(id, NULL, 0)
Kernel picks a free address. Safe, portable, avoids conflicts. Use this in almost all cases.
Option 2: Fixed Address
shmat(id, 0x50000000, 0)
Maps at exact address. Fails if address is occupied. Rarely used — only for special cases.
Option 3: SHM_RND
shmat(id, addr, SHM_RND)
Rounds down addr to nearest SHMLBA boundary. SHMLBA is usually the page size (4096).

shmflg Bit Values

Flag Effect When to Use
0 Read-write access, kernel chooses address Default — use for most cases
SHM_RDONLY Map as read-only. Write causes SIGSEGV. Client/reader process that must not corrupt data
SHM_REMAP Replace any existing mapping at shmaddr. Linux-specific. When remapping at a fixed address that may be in use
SHM_RND Round shmaddr down to SHMLBA boundary When specifying a non-page-aligned address

shmat() Code Examples

Example 1: Basic attach and use as a struct

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

/* Define a structure to impose on the shared memory */
typedef struct {
    int   counter;
    float temperature;
    char  status[32];
} sensor_data_t;

int main(void) {
    int shmid = shmget(0x1234, sizeof(sensor_data_t), IPC_CREAT | 0660);
    if (shmid == -1) { perror("shmget"); return 1; }

    /* Attach — cast return value to your struct pointer */
    sensor_data_t *data = (sensor_data_t *)shmat(shmid, NULL, 0);
    if (data == (sensor_data_t *)-1) { perror("shmat"); return 1; }

    /* Use it exactly like a normal struct */
    data->counter = 42;
    data->temperature = 98.6f;
    snprintf(data->status, sizeof(data->status), "OK");

    printf("counter=%d temp=%.1f status=%s\n",
           data->counter, data->temperature, data->status);

    shmdt(data);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

Example 2: Read-only attach (client/reader)

/* Reader process — attaches with SHM_RDONLY */
#include <sys/shm.h>
#include <stdio.h>

typedef struct {
    int   counter;
    float temperature;
    char  status[32];
} sensor_data_t;

int main(void) {
    /* Open existing segment — no IPC_CREAT */
    int shmid = shmget(0x1234, sizeof(sensor_data_t), 0);
    if (shmid == -1) { perror("shmget"); return 1; }

    /* Attach read-only — any write will cause SIGSEGV */
    const sensor_data_t *data =
        (const sensor_data_t *)shmat(shmid, NULL, SHM_RDONLY);
    if (data == (const sensor_data_t *)-1) { perror("shmat"); return 1; }

    printf("counter=%d temp=%.1f status=%s\n",
           data->counter, data->temperature, data->status);

    /* data->counter = 99;  -- This would SIGSEGV */

    shmdt(data);
    return 0;
}

Example 3: Multiple attachments of the same segment

/* A process can attach the same segment multiple times
 * at different addresses — useful for aliasing tricks */
#include <sys/shm.h>
#include <stdio.h>

int main(void) {
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
    if (shmid == -1) { perror("shmget"); return 1; }

    /* Attach #1 — read-write */
    char *rw = (char *)shmat(shmid, NULL, 0);
    /* Attach #2 — read-only alias of the same segment */
    const char *ro = (const char *)shmat(shmid, NULL, SHM_RDONLY);

    if (rw == (char *)-1 || ro == (const char *)-1) {
        perror("shmat"); return 1;
    }

    /* Write via rw pointer */
    rw[0] = 'H'; rw[1] = 'i'; rw[2] = '\0';

    /* Read via ro pointer — sees same data */
    printf("Via ro alias: %s\n", ro);  /* prints: Hi */

    shmdt(rw);
    shmdt(ro);  /* Must detach each attachment separately */
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

shmdt() – Detach a Segment

#include <sys/shm.h>

int shmdt(const void *shmaddr);
/*
 * shmaddr – the pointer returned by shmat()
 *           (must be the exact pointer, not an offset into it)
 *
 * Returns: 0 on success, -1 on error (EINVAL if invalid address)
 */

Rules for shmdt():

  • You must pass the exact pointer that shmat() returned — not a pointer into the middle of the segment.
  • If you attached the same segment multiple times, you must call shmdt() once for each attachment.
  • After shmdt(), accessing the old pointer causes a SIGSEGV. Set it to NULL after detaching.
  • Detaching does not delete the segment. Other processes remain attached.
/* Correct pattern: detach then nullify */
shmdt(ptr);
ptr = NULL;  /* Prevent accidental use after detach */

/* Wrong: passing offset */
char *base = (char *)shmat(shmid, NULL, 0);
base += 100;
shmdt(base);  /* WRONG — must pass original pointer */

Behaviour on fork() and exec()

After fork()
Child inherits all parent’s attached segments. Both parent and child share the same physical memory. Changes by one are visible to the other. Attach count (shm_nattch) increases.
After exec()
All attached segments are automatically detached. The segment still exists in the kernel. The new program must call shmat() again with the shmid if it needs the segment.
On process exit
All attached segments are automatically detached. Segment persists in kernel unless IPC_RMID was called. Leak risk if server crashes without cleanup.
/* fork() example — child inherits parent's shm attachment */
#include <sys/shm.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

int main(void) {
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
    int *shared = (int *)shmat(shmid, NULL, 0);
    *shared = 0;

    pid_t pid = fork();
    if (pid == 0) {
        /* Child: increment the counter */
        (*shared)++;
        printf("Child wrote: %d\n", *shared);
        shmdt(shared);
        return 0;
    }

    /* Parent: wait for child then read */
    wait(NULL);
    printf("Parent reads: %d\n", *shared);  /* sees 1 */

    shmdt(shared);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

Checking Attach Count (shm_nattch)

You can check how many processes are currently attached using shmctl(IPC_STAT). This is useful in server shutdown logic — wait until all clients have detached before deleting.

#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   : %lu bytes\n", (unsigned long)ds.shm_segsz);
    printf("Attached count : %lu\n",       (unsigned long)ds.shm_nattch);
    printf("Last attach PID: %d\n",        (int)ds.shm_lpid);
    printf("Creator PID    : %d\n",        (int)ds.shm_cpid);
}

/* Usage */
int shmid = shmget(0x1234, 4096, IPC_CREAT | 0660);
void *p   = shmat(shmid, NULL, 0);
print_shm_info(shmid);  /* nattch = 1 */
shmdt(p);
print_shm_info(shmid);  /* nattch = 0 */

Safe Pointer Pattern in Shared Memory

Since different processes attach the segment at different virtual addresses, never store raw pointers inside the segment. Use offsets from the segment base address instead.

/* WRONG — stores a virtual pointer; breaks in other process */
typedef struct {
    char *name;       /* BAD: pointer only valid in this process */
    int   value;
} bad_t;

/* CORRECT — stores an offset; valid in all processes */
typedef struct {
    size_t name_offset;  /* offset from start of segment */
    int    value;
    char   name_buf[64]; /* actual string stored in-segment */
} good_t;

/* Access pattern using offset */
char *base   = (char *)shmat(shmid, NULL, 0);
good_t *hdr  = (good_t *)base;

/* Write */
hdr->name_offset = sizeof(good_t);  /* name_buf starts after header */
snprintf(base + hdr->name_offset, 64, "sensor_A");
hdr->value = 100;

/* Read in another process (different base address but same offset) */
char *base2   = (char *)shmat(shmid, NULL, 0);
good_t *hdr2  = (good_t *)base2;
char *name    = base2 + hdr2->name_offset;
printf("Name: %s, Value: %d\n", name, hdr2->value);

Interview Questions

Q1. What does shmat() return on error and why is it unusual?

It returns (void *)-1 (which equals 0xFFFFFFFF...FF), not NULL. This is unusual because most functions return NULL on failure. The reason is that NULL (address 0) is a valid return value if the kernel happens to map the segment there (though this is rare). The only unambiguous error indicator is (void *)-1. Always check if (addr == (void *)-1).

Q2. What is SHM_RDONLY and when would you use it?

SHM_RDONLY maps the segment as read-only in the calling process’s page table. Any write attempt causes a SIGSEGV. Use it in reader/client processes that should consume data but must not modify it — similar to opening a file with O_RDONLY. It provides both protection (prevents accidental writes) and a documentation hint about the process’s role.

Q3. What is shm_nattch and how is it useful?

shm_nattch is a field in struct shmid_ds that the kernel maintains automatically. It counts how many processes currently have the segment attached. It increments on each shmat() and decrements on each shmdt(). A server can poll this field to wait until all clients detach before destroying the segment, or check that no orphaned processes are still using a segment before restarting.

Q4. What happens if you pass an offset into the segment to shmdt()?

shmdt() requires the exact pointer returned by shmat() — the start of the mapping. If you pass a pointer that is offset from the start (e.g. you incremented the pointer), shmdt() returns -1 with errno = EINVAL. Always save the original pointer from shmat() separately for use in shmdt().

Q5. Can a process attach the same segment twice? What happens?

Yes. Each call to shmat() creates a new mapping at a new virtual address. The segment’s attach count (shm_nattch) increments for each attachment. You get two different virtual addresses pointing to the same physical memory. You must call shmdt() separately for each attachment to fully detach. This is sometimes used deliberately to create read-only and read-write aliases simultaneously.

Q6. Why can’t you store raw pointers in shared memory?

Because shmat() maps the segment at different virtual addresses in different processes (unless you force a fixed address). A pointer value stored by Process A (e.g. 0x7f001234) has no meaning in Process B where the segment might be at 0x7e001234. The solution is to store byte offsets from the start of the segment, which are consistent across all processes regardless of their individual attachment addresses.

Chapter 48 – System V Shared Memory Series

EmbeddedPathashala.com | Free Embedded & Linux Tutorials

Leave a Reply

Your email address will not be published. Required fields are marked *