shmid_ds Structureshmid_ds Fields
Intermediate
TLPI Ch 48
Every shared memory segment created with shmget() has a kernel-maintained data structure called shmid_ds. Think of it as the “metadata record” for that segment — it stores who owns it, how big it is, when it was last used, and how many processes currently have it attached. You read or modify this structure using shmctl().
Understanding shmid_ds is essential for writing robust shared-memory programs because it lets you inspect the segment’s state, change permissions, and control cleanup.
The shmid_ds structure is defined in <sys/shm.h>. A simplified view of its fields is shown below. Linux may include extra non-standard fields, but the POSIX-standard fields are what matter for portable code.
| Field | Type | Description |
|---|---|---|
shm_perm |
struct ipc_perm |
Ownership and permissions (uid, gid, mode) |
shm_segsz |
size_t |
Requested size of segment in bytes |
shm_atime |
time_t |
Time of last shmat() call |
shm_dtime |
time_t |
Time of last shmdt() call |
shm_ctime |
time_t |
Time of creation or last IPC_SET |
shm_cpid |
pid_t |
PID of creator process |
shm_lpid |
pid_t |
PID of last shmat() or shmdt() caller |
shm_nattch |
shmatt_t |
Number of currently attached processes |
/* Reading shmid_ds with IPC_STAT */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>
int main(void)
{
key_t key = ftok("/tmp", 'A');
/* Create a 4 KB shared memory segment */
int shmid = shmget(key, 4096, IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); exit(1); }
struct shmid_ds info;
if (shmctl(shmid, IPC_STAT, &info) == -1) {
perror("shmctl IPC_STAT"); exit(1);
}
printf("Segment size : %lu bytes\n", (unsigned long)info.shm_segsz);
printf("Creator PID : %d\n", (int)info.shm_cpid);
printf("Attach count : %lu\n", (unsigned long)info.shm_nattch);
printf("Created at : %s", ctime(&info.shm_ctime));
/* cleanup */
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
shm_perm is an embedded struct ipc_perm. It works exactly like file permissions — owner user ID (uid), owner group ID (gid), and a 9-bit mode field. You can change these three with IPC_SET.
The shm_perm.mode field also carries two extra read-only Linux flags that you cannot set directly through IPC_SET:
| Flag | Set By | Meaning |
|---|---|---|
SHM_DEST |
shmctl(IPC_RMID) |
Segment is marked for deletion; will be removed when last process detaches |
SHM_LOCKED |
shmctl(SHM_LOCK) |
Segment is locked into physical RAM (will not be swapped out) |
/* Check SHM_DEST and SHM_LOCKED flags */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void print_flags(int shmid)
{
struct shmid_ds info;
shmctl(shmid, IPC_STAT, &info);
int mode = info.shm_perm.mode;
printf("SHM_DEST : %s\n", (mode & SHM_DEST) ? "YES" : "NO");
printf("SHM_LOCKED : %s\n", (mode & SHM_LOCKED) ? "YES" : "NO");
}
int main(void)
{
key_t key = ftok("/tmp", 'B');
int shmid = shmget(key, 4096, IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); exit(1); }
printf("--- Before IPC_RMID ---\n");
print_flags(shmid);
/* Mark for deletion */
shmctl(shmid, IPC_RMID, NULL);
/* Segment still exists because no processes attached,
but on re-stat it would show SHM_DEST if still open.
Demonstrate IPC_SET changing uid/gid/mode instead: */
int shmid2 = shmget(ftok("/tmp",'C'), 4096, IPC_CREAT | 0660);
struct shmid_ds ds;
shmctl(shmid2, IPC_STAT, &ds);
ds.shm_perm.mode = 0600; /* restrict to owner only */
shmctl(shmid2, IPC_SET, &ds);
printf("\nMode changed to 0600 via IPC_SET\n");
shmctl(shmid2, IPC_RMID, NULL);
return 0;
}
shm_segsz holds the requested size you passed to shmget(), not the rounded-up page-aligned size. The kernel actually allocates in whole pages, so the real allocated memory is usually slightly larger (rounded up to the next multiple of the system page size, typically 4096 bytes).
Example: if you ask for 5000 bytes, shm_segsz = 5000, but the kernel allocates 8192 bytes (2 pages × 4096).
| Requested Size | shm_segsz | Actual Kernel Allocation (page size=4096) |
|---|---|---|
| 1 byte | 1 | 4096 bytes (1 page) |
| 4096 bytes | 4096 | 4096 bytes (1 page) |
| 5000 bytes | 5000 | 8192 bytes (2 pages) |
| 10000 bytes | 10000 | 12288 bytes (3 pages) |
/* Demonstrate shm_segsz vs actual pages */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
long page_size = sysconf(_SC_PAGESIZE);
size_t requested = 5000;
key_t key = ftok("/tmp", 'D');
int shmid = shmget(key, requested, IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); exit(1); }
struct shmid_ds info;
shmctl(shmid, IPC_STAT, &info);
/* round up to next page */
size_t actual = ((requested + page_size - 1) / page_size) * page_size;
printf("Page size : %ld bytes\n", page_size);
printf("Requested size : %zu bytes\n", requested);
printf("shm_segsz reports : %lu bytes\n", (unsigned long)info.shm_segsz);
printf("Actual allocation : %zu bytes (%zu pages)\n",
actual, actual / page_size);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
The kernel automatically updates three time_t timestamps stored in shmid_ds. They help you audit when a segment was last used without any extra application-level bookkeeping.
| shmget() | → | atime = 0 dtime = 0 ctime = now |
→ | shmat() | → | atime = now lpid = caller |
→ | shmdt() | → | dtime = now lpid = caller |
/* Observe timestamp updates */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>
static void show_times(int shmid, const char *label)
{
struct shmid_ds info;
shmctl(shmid, IPC_STAT, &info);
printf("\n--- %s ---\n", label);
printf("shm_atime (last attach) : %s",
info.shm_atime ? ctime(&info.shm_atime) : "not set\n");
printf("shm_dtime (last detach) : %s",
info.shm_dtime ? ctime(&info.shm_dtime) : "not set\n");
printf("shm_ctime (created/set) : %s", ctime(&info.shm_ctime));
}
int main(void)
{
key_t key = ftok("/tmp", 'E');
int shmid = shmget(key, 4096, IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); exit(1); }
show_times(shmid, "After shmget");
/* attach */
void *addr = shmat(shmid, NULL, 0);
if (addr == (void *)-1) { perror("shmat"); exit(1); }
show_times(shmid, "After shmat");
/* write something */
strcpy((char *)addr, "Hello from EmbeddedPathashala!");
/* detach */
shmdt(addr);
show_times(shmid, "After shmdt");
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
shm_cpid stores the process ID that created the segment. shm_lpid stores the PID of the process that most recently called shmat() or shmdt().
These are useful for debugging — you can find out which process is actively touching the segment using ps -p <pid>.
/* Demonstrate shm_cpid and shm_lpid */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
int main(void)
{
key_t key = ftok("/tmp", 'F');
int shmid = shmget(key, 4096, IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); exit(1); }
struct shmid_ds info;
shmctl(shmid, IPC_STAT, &info);
printf("Creator PID (my PID) : %d shm_cpid=%d\n",
getpid(), (int)info.shm_cpid);
pid_t child = fork();
if (child == 0) {
/* child attaches and detaches */
void *addr = shmat(shmid, NULL, 0);
shmdt(addr);
struct shmid_ds ci;
shmctl(shmid, IPC_STAT, &ci);
printf("After child attach/detach: shm_lpid=%d (child=%d)\n",
(int)ci.shm_lpid, getpid());
exit(0);
}
wait(NULL);
shmctl(shmid, IPC_STAT, &info);
printf("Parent sees shm_lpid = %d (child PID)\n", (int)info.shm_lpid);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
shm_nattch is an unsigned integer (at least unsigned short, but unsigned long on Linux) that counts how many processes currently have the segment attached. It is incremented by shmat() and decremented by shmdt().
A useful pattern: before deleting a segment with IPC_RMID, check shm_nattch == 0 to confirm no process is using it. If IPC_RMID is called while shm_nattch > 0, the segment is only marked for deletion (SHM_DEST flag set) and physically removed when the last process detaches.
/* Monitor shm_nattch as processes attach/detach */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
static void show_nattch(int shmid, const char *msg)
{
struct shmid_ds info;
shmctl(shmid, IPC_STAT, &info);
printf("[%s] shm_nattch = %lu\n", msg, (unsigned long)info.shm_nattch);
}
int main(void)
{
key_t key = ftok("/tmp", 'G');
int shmid = shmget(key, 4096, IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); exit(1); }
show_nattch(shmid, "Before any attach");
void *a1 = shmat(shmid, NULL, 0);
show_nattch(shmid, "After parent attaches");
pid_t c1 = fork();
if (c1 == 0) {
void *ca = shmat(shmid, NULL, 0);
show_nattch(shmid, "Child also attached");
sleep(1);
shmdt(ca);
show_nattch(shmid, "Child detached");
exit(0);
}
wait(NULL);
shmdt(a1);
show_nattch(shmid, "Parent detached");
/* Safe to delete now */
shmctl(shmid, IPC_RMID, NULL);
printf("Segment removed.\n");
return 0;
}
Only three sub-fields of shm_perm are writable via IPC_SET: uid (owner user ID), gid (owner group ID), and mode (lower 9 permission bits). Everything else in shmid_ds is set automatically by the kernel.
Typical use: after creating a segment as root, downgrade ownership and restrict permissions before handing it to an unprivileged process.
/* Change permissions of an existing shared memory segment */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: %s <shmid>\n", argv[0]);
exit(1);
}
int shmid = atoi(argv[1]);
struct shmid_ds ds;
/* First read current state */
if (shmctl(shmid, IPC_STAT, &ds) == -1) {
perror("IPC_STAT"); exit(1);
}
printf("Before: mode=%o uid=%d gid=%d\n",
ds.shm_perm.mode & 0777,
(int)ds.shm_perm.uid,
(int)ds.shm_perm.gid);
/* Modify only what is allowed */
ds.shm_perm.mode = 0640; /* owner rw, group r, others nothing */
/* uid and gid remain the same */
if (shmctl(shmid, IPC_SET, &ds) == -1) {
perror("IPC_SET"); exit(1);
}
/* Read back to confirm */
shmctl(shmid, IPC_STAT, &ds);
printf("After : mode=%o uid=%d gid=%d\n",
ds.shm_perm.mode & 0777,
(int)ds.shm_perm.uid,
(int)ds.shm_perm.gid);
return 0;
}
Q1. What is shmid_ds and who maintains it?
The kernel maintains a shmid_ds structure for every System V shared memory segment. It records ownership, permissions, size, timestamps, PIDs, and the attach count. You access it through shmctl() with IPC_STAT or IPC_SET.
Q2. Which fields in shmid_ds can be changed by a user program?
Only shm_perm.uid, shm_perm.gid, and shm_perm.mode (the lower 9 permission bits) can be set via IPC_SET. All other fields are updated by the kernel automatically.
Q3. What does shm_segsz represent? Is it always the actual allocated size?
It holds the requested byte count passed to shmget(). The actual kernel allocation is rounded up to a page boundary, so it can be larger than shm_segsz.
Q4. What happens to shm_atime and shm_dtime when a segment is first created?
Both are initialized to 0 (not the creation time). shm_ctime is the field that is set to the current time on creation. shm_atime is set only after the first shmat(), and shm_dtime after the first shmdt().
Q5. What is shm_nattch? What is its data type on Linux?
It counts the number of processes currently attached to the segment. On Linux it is unsigned long. SUSv3 only requires it to be at least unsigned short.
Q6. What is the SHM_DEST flag? Can you set it directly via IPC_SET?
SHM_DEST is a read-only flag in shm_perm.mode set by the kernel when shmctl(IPC_RMID) is called while other processes still have the segment attached. You cannot set it via IPC_SET.
Q7. Difference between shm_cpid and shm_lpid?
shm_cpid is set once — to the PID of the process that called shmget() to create the segment. shm_lpid is updated on every shmat() and shmdt() call to the PID of the calling process.
Q8. How do you check if it is safe to delete a shared memory segment?
Read shmid_ds.shm_nattch via IPC_STAT. If it is 0, no process is using the segment and it is safe to call shmctl(IPC_RMID). Even if you call IPC_RMID while shm_nattch > 0, the kernel only marks it with SHM_DEST and physically removes it when the last process detaches.
Next: Shared Memory Limits, /proc filesystem, IPC_INFO and SHM_INFO operations
