Controlling Shared Memory Segments
shmctl() is the multipurpose control function for System V shared memory. It lets you retrieve segment metadata, modify permissions, lock pages in RAM, and delete a segment. Think of it as the “ioctl of shared memory.”
The key data structure involved is struct shmid_ds, which the kernel maintains for each segment. shmctl() either reads or writes this structure depending on the operation requested.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/* Returns: 0 on success (most cmds), positive int for some cmds, -1 on error */
| Parameter | Purpose |
|---|---|
shmid |
The segment identifier from shmget() |
cmd |
The operation to perform (see table below) |
buf |
Pointer to struct shmid_ds; used by IPC_STAT and IPC_SET. Pass NULL for IPC_RMID. |
| Command | Description | buf argument |
|---|---|---|
IPC_STAT |
Copy segment’s shmid_ds info into *buf. Use to inspect size, permissions, attach count, timestamps. |
Output: filled by kernel |
IPC_SET |
Update selected fields of shmid_ds from *buf. Only permission bits and shm_perm.uid/gid can be changed. |
Input: caller fills it |
IPC_RMID |
Mark segment for deletion. Segment is actually removed when the last process detaches. | NULL |
IPC_INFO |
Return system-wide shared memory limits (Linux-specific). Returns max shmid in use. | Pointer to struct shminfo |
SHM_STAT |
Like IPC_STAT but shmid is treated as an index into kernel’s internal array (Linux-specific). | Output buffer |
SHM_LOCK |
Lock segment pages in RAM — prevent swapping to disk. Requires CAP_IPC_LOCK. | NULL |
SHM_UNLOCK |
Unlock locked pages — allow swapping again. | NULL |
Every shared memory segment has a corresponding shmid_ds in the kernel. This is what IPC_STAT copies out for you to inspect:
/* From <sys/shm.h> — simplified for clarity */
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment in bytes (requested) */
time_t shm_atime; /* Time of last shmat() */
time_t shm_dtime; /* Time of last shmdt() */
time_t shm_ctime; /* Time of last shmctl() change */
pid_t shm_cpid; /* PID of creator (who called shmget) */
pid_t shm_lpid; /* PID of process that last called shmat/shmdt */
shmatt_t shm_nattch; /* Current number of attached processes */
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget() */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
cgid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
Complete IPC_STAT example:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/shm.h>
void print_shm_info(int shmid)
{
struct shmid_ds ds;
if (shmctl(shmid, IPC_STAT, &ds) == -1) {
perror("shmctl IPC_STAT");
return;
}
printf("=== Shared Memory Segment Info ===\n");
printf("Segment size : %zu bytes\n", ds.shm_segsz);
printf("Creator PID : %d\n", (int)ds.shm_cpid);
printf("Last attach PID: %d\n", (int)ds.shm_lpid);
printf("Attach count : %lu\n", (unsigned long)ds.shm_nattch);
printf("Permissions : %04o\n", ds.shm_perm.mode & 0777);
printf("Owner UID : %d\n", (int)ds.shm_perm.uid);
printf("Owner GID : %d\n", (int)ds.shm_perm.gid);
if (ds.shm_atime)
printf("Last shmat() : %s", ctime(&ds.shm_atime));
else
printf("Last shmat() : Never\n");
if (ds.shm_dtime)
printf("Last shmdt() : %s", ctime(&ds.shm_dtime));
else
printf("Last shmdt() : Never\n");
printf("Last shmctl() : %s", ctime(&ds.shm_ctime));
}
int main(void)
{
int shmid = shmget(IPC_PRIVATE, 8192, IPC_CREAT | 0660);
if (shmid == -1) { perror("shmget"); return 1; }
print_shm_info(shmid);
/* Attach then detach to update timestamps */
void *p = shmat(shmid, NULL, 0);
shmdt(p);
printf("\nAfter attach+detach:\n");
print_shm_info(shmid);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
When you call shmctl(shmid, IPC_RMID, NULL), the kernel marks the segment as “deleted” but does NOT immediately free the memory if processes still have it attached. The actual deletion is deferred until the last attached process calls shmdt().
| Event | shm_nattch | Segment in kernel? | New attaches allowed? |
|---|---|---|---|
| shmget() creates segment | 0 | Yes | Yes |
| 2 processes call shmat() | 2 | Yes | Yes |
| shmctl(IPC_RMID) called | 2 | Yes (marked deleted) | No — EINVAL |
| 1st process calls shmdt() | 1 | Yes (still in use) | No |
| Last process calls shmdt() | 0 | Deleted — gone! | No |
/* Good pattern: mark for deletion right after creation
so it auto-cleans even if the program crashes */
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0660);
void *shmp = shmat(shmid, NULL, 0);
/* Mark for deletion immediately — still accessible while attached */
shmctl(shmid, IPC_RMID, NULL);
/* Use the memory normally — it's still valid */
((int *)shmp)[0] = 42;
/* When shmdt() is called, the segment is actually freed */
shmdt(shmp);
Tip: Calling IPC_RMID right after shmget() + shmat() (like anonymous mmap) is a clean pattern — the segment is automatically cleaned up when the last process detaches, even if the program crashes.
Only three fields can be changed with IPC_SET: the permission mode, the owner UID, and the owner GID. You must call IPC_STAT first to get the current values, then modify what you need, then call IPC_SET.
#include <stdio.h>
#include <sys/shm.h>
int main(void)
{
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
struct shmid_ds ds;
/* Step 1: Read current info */
if (shmctl(shmid, IPC_STAT, &ds) == -1) {
perror("IPC_STAT"); return 1;
}
printf("Before: mode = %04o\n", ds.shm_perm.mode & 0777);
/* Step 2: Modify the permission mode */
ds.shm_perm.mode = 0660; /* Add group read/write */
/* Step 3: Apply the change */
if (shmctl(shmid, IPC_SET, &ds) == -1) {
perror("IPC_SET"); return 1;
}
/* Verify */
shmctl(shmid, IPC_STAT, &ds);
printf("After: mode = %04o\n", ds.shm_perm.mode & 0777);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
/* Output:
Before: mode = 0600
After: mode = 0660 */
SHM_LOCK forces the kernel to keep all pages of the segment in physical RAM — the pages are never swapped to disk. This is used in real-time and high-performance applications where swap latency is unacceptable.
#include <stdio.h>
#include <sys/shm.h>
#include <sys/capability.h> /* For capabilities info */
int main(void)
{
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0660);
void *shmp = shmat(shmid, NULL, 0);
/* Lock pages in RAM — requires CAP_IPC_LOCK capability
(root, or process with that specific capability) */
if (shmctl(shmid, SHM_LOCK, NULL) == -1) {
perror("SHM_LOCK failed (need CAP_IPC_LOCK)");
/* Non-fatal — continue without locking */
} else {
printf("Segment pages locked in RAM.\n");
/* Check lock status via IPC_STAT */
struct shmid_ds ds;
shmctl(shmid, IPC_STAT, &ds);
if (ds.shm_perm.mode & SHM_LOCKED)
printf("Lock confirmed: SHM_LOCKED bit is set.\n");
}
/* Unlock when no longer needed */
shmctl(shmid, SHM_UNLOCK, NULL);
shmdt(shmp);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
Note: SHM_LOCK requires the CAP_IPC_LOCK capability (typically only root). On Linux, SHM_LOCKED flag in shm_perm.mode indicates the segment is locked.
← Previous: shmat() and shmdt() Next: Data Transfer Example →
Q1. What does shmctl(shmid, IPC_RMID, NULL) do if processes still have the segment attached?
The kernel marks the segment as deleted (no new attaches are allowed) but does not free it. The memory remains accessible to currently attached processes. The segment is actually destroyed only when the last process detaches (calls shmdt()). This is deferred deletion.
Q2. How do you find out how many processes currently have a shared memory segment attached?
Call shmctl(shmid, IPC_STAT, &ds) and check ds.shm_nattch. This field gives the current attach count. It is incremented on each successful shmat() and decremented on each shmdt().
Q3. What fields can be modified with IPC_SET?
Only three fields: shm_perm.uid (owner UID), shm_perm.gid (owner GID), and shm_perm.mode (permission bits). You cannot change the segment size or key with IPC_SET.
Q4. What is the purpose of SHM_LOCK and when would you use it?
SHM_LOCK pins the segment’s pages in physical RAM, preventing them from being swapped to disk. Use it in real-time applications where access latency must be bounded (swapping can introduce unpredictable delays of milliseconds). Requires CAP_IPC_LOCK privilege.
Q5. What does shm_lpid tell you?
It records the PID of the process that most recently performed an shmat() or shmdt() operation on the segment. Useful for debugging — you can identify which process last interacted with the segment.
Q6. How does the cleanup-on-creation pattern work?
Create the segment → attach it → immediately call IPC_RMID. The segment is marked deleted (no new attaches), but your process can still use it normally. When your process exits (or calls shmdt), the segment is automatically freed. This prevents leaked segments even on crashes — similar to how creating a file, unlinking it, then keeping the fd open works.
