SHM Limits
Intermediate
TLPI Ch 48
Linux puts hard upper bounds on shared memory so one application cannot exhaust all physical RAM with shared segments. If your program calls shmget() and gets ENOSPC or EINVAL, a limit has been hit. Knowing what the limits are and how to read or change them is a key skill for systems programming and embedded Linux work.
Linux defines four tuneable limits for System V shared memory and one additional limit found on some other UNIX systems (but not implemented on Linux):
| Limit Name | What it Controls | Scope | Error if exceeded | On Linux? |
|---|---|---|---|---|
| SHMMNI | Max number of shared memory segments (IDs) on the system | System-wide | ENOSPC |
✔ Yes |
| SHMMIN | Minimum size (bytes) of a single segment; defined as 1 but effective minimum is page size | Per-segment | EINVAL |
✔ Yes |
| SHMMAX | Maximum size (bytes) of a single segment | Per-segment | EINVAL |
✔ Yes |
| SHMALL | Max total pages of shared memory across all segments | System-wide | ENOSPC |
✔ Yes |
| SHMSEG | Max attached segments per process | Per-process | EMFILE |
✘ Not on Linux |
Linux exposes shared memory limits as virtual files under /proc/sys/kernel/. You can read them with cat and write new values with echo (as root) or persistently via /etc/sysctl.conf.
| Limit | /proc/sys/kernel/ file | x86-32 ceiling value | Typical default (2.6.31) |
|---|---|---|---|
| SHMMNI | shmmni |
32768 (IPCMNI) | 4096 |
| SHMMAX | shmmax |
Depends on RAM | 32 MB (varies) |
| SHMALL | shmall |
Depends on RAM | 2097152 pages |
/* Reading /proc limits from a C program */
#include <stdio.h>
#include <stdlib.h>
static unsigned long read_proc_ulong(const char *path)
{
FILE *f = fopen(path, "r");
if (!f) { perror(path); return 0; }
unsigned long val;
fscanf(f, "%lu", &val);
fclose(f);
return val;
}
int main(void)
{
printf("SHMMNI (max segments) : %lu\n",
read_proc_ulong("/proc/sys/kernel/shmmni"));
printf("SHMMAX (max seg size) : %lu bytes\n",
read_proc_ulong("/proc/sys/kernel/shmmax"));
printf("SHMALL (max pages) : %lu pages\n",
read_proc_ulong("/proc/sys/kernel/shmall"));
return 0;
}
# Shell commands to read limits
cat /proc/sys/kernel/shmmni
cat /proc/sys/kernel/shmmax
cat /proc/sys/kernel/shmall
# Temporarily increase SHMMAX to 256 MB (as root)
echo 268435456 > /proc/sys/kernel/shmmax
# Persistent change via sysctl.conf
echo "kernel.shmmax = 268435456" >> /etc/sysctl.conf
sysctl -p
Each limit produces a specific errno value when breached. Knowing which limit caused the error helps you fix it.
| Call | errno | Meaning | Fix |
|---|---|---|---|
shmget() |
ENOSPC |
SHMMNI or SHMALL exceeded | Delete unused segments or raise the limit |
shmget() |
EINVAL |
Requested size < SHMMIN or > SHMMAX | Adjust size or raise SHMMAX via /proc |
/* Detect and report limit-related errors */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
/* Try to create a huge segment — likely to exceed SHMMAX */
size_t huge = 1UL << 32; /* 4 GB */
key_t key = ftok("/tmp", 'H');
int shmid = shmget(key, huge, IPC_CREAT | 0660);
if (shmid == -1) {
if (errno == EINVAL) {
fprintf(stderr,
"EINVAL: requested size exceeds SHMMAX "
"or is below SHMMIN\n");
fprintf(stderr,
"Check: cat /proc/sys/kernel/shmmax\n");
} else if (errno == ENOSPC) {
fprintf(stderr,
"ENOSPC: SHMMNI (too many segments) "
"or SHMALL (total pages) limit hit\n");
fprintf(stderr,
"Run: ipcs -m to list existing segments\n");
} else {
perror("shmget");
}
exit(1);
}
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
Linux provides a non-standard shmctl() operation called IPC_INFO that fills a struct shminfo with the current values of all shared memory limits in one call. It is faster than reading each /proc file individually.
You pass 0 as the shmid argument because this operation is system-wide, not segment-specific. The third argument must be cast to (struct shmid_ds *) even though you pass a pointer to struct shminfo.
| shminfo Field | Corresponding Limit | Description |
|---|---|---|
shmmax |
SHMMAX | Max segment size in bytes |
shmmin |
SHMMIN | Min segment size (always 1) |
shmmni |
SHMMNI | Max number of segments |
shmseg |
SHMSEG | Max attached per process (not enforced on Linux) |
shmall |
SHMALL | Max total shared memory pages |
/* Use IPC_INFO to read all SHM limits */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
struct shminfo si;
/*
* shmid = 0 for system-wide operation.
* Cast struct shminfo* to struct shmid_ds* as required by API.
*/
if (shmctl(0, IPC_INFO, (struct shmid_ds *)&si) == -1) {
perror("shmctl IPC_INFO");
exit(1);
}
printf("SHMMAX (max segment bytes) : %lu\n", si.shmmax);
printf("SHMMIN (min segment bytes) : %lu\n", si.shmmin);
printf("SHMMNI (max segments) : %lu\n", si.shmmni);
printf("SHMSEG (max per-proc att.) : %lu\n", si.shmseg);
printf("SHMALL (max total pages) : %lu\n", si.shmall);
/* Convert SHMALL pages to bytes */
long page = sysconf(_SC_PAGESIZE);
printf("SHMALL in bytes : %lu\n",
si.shmall * (unsigned long)page);
return 0;
}
While IPC_INFO shows configured limits, the related SHM_INFO operation shows actual current usage — how many segments exist right now, how many pages are allocated, and how many are swapped. This is the runtime view as opposed to the configuration view.
The result is returned in a struct shm_info (note the underscore — different from shminfo).
| shm_info Field | Description |
|---|---|
used_ids |
Number of shared memory segments currently in existence |
shm_tot |
Total number of shared memory pages currently allocated |
shm_rss |
Number of shared memory pages currently in physical RAM |
shm_swp |
Number of shared memory pages currently swapped out |
/* Use SHM_INFO to see current SHM usage */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
struct shm_info si; /* NOTE: shm_info, not shminfo */
int maxidx = shmctl(0, SHM_INFO, (struct shmid_ds *)&si);
if (maxidx == -1) {
perror("shmctl SHM_INFO");
exit(1);
}
long page = sysconf(_SC_PAGESIZE);
printf("Segments in use : %d\n", si.used_ids);
printf("Total pages allocated : %lu (%lu bytes)\n",
si.shm_tot, si.shm_tot * (unsigned long)page);
printf("Pages in RAM : %lu\n", si.shm_rss);
printf("Pages swapped out : %lu\n", si.shm_swp);
printf("Highest SHM index : %d\n", maxidx);
return 0;
}
SHM_INFO returns the highest used index as its return value. You can combine it with SHM_STAT to walk through all existing segments — this is how tools like ipcs -m work internally.SHM_INFO returns the highest valid kernel table index. You can then iterate from 0 to that index, calling shmctl(i, SHM_STAT, &ds) on each. If index i has no segment, it returns -1 (skip it). This gives you the full list — exactly what ipcs -m does.
/* List all System V shared memory segments (like ipcs -m) */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>
int main(void)
{
struct shm_info si;
int maxidx = shmctl(0, SHM_INFO, (struct shmid_ds *)&si);
if (maxidx == -1) { perror("SHM_INFO"); exit(1); }
printf("%-10s %-12s %-10s %-8s %-6s\n",
"shmid", "key", "size(B)", "owner", "nattch");
printf("%s\n", "-------------------------------------------------------");
for (int i = 0; i <= maxidx; i++) {
struct shmid_ds ds;
int shmid = shmctl(i, SHM_STAT, &ds);
if (shmid == -1)
continue; /* slot unused */
printf("%-10d 0x%-10lx %-10lu %-8d %-6lu\n",
shmid,
(unsigned long)ds.shm_perm.__key,
(unsigned long)ds.shm_segsz,
(int)ds.shm_perm.uid,
(unsigned long)ds.shm_nattch);
}
return 0;
}
In embedded or server environments you may have leftover shared memory segments from crashed processes. The ipcrm tool or a cleanup program using SHM_INFO + IPC_RMID can remove them.
/* Remove all shared memory segments owned by current user */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(void)
{
struct shm_info si;
int maxidx = shmctl(0, SHM_INFO, (struct shmid_ds *)&si);
if (maxidx == -1) { perror("SHM_INFO"); exit(1); }
int removed = 0;
uid_t my_uid = getuid();
for (int i = 0; i <= maxidx; i++) {
struct shmid_ds ds;
int shmid = shmctl(i, SHM_STAT, &ds);
if (shmid == -1) continue;
if (ds.shm_perm.uid == my_uid) {
if (shmctl(shmid, IPC_RMID, NULL) == 0) {
printf("Removed shmid %d (size=%lu)\n",
shmid, (unsigned long)ds.shm_segsz);
removed++;
} else {
perror("IPC_RMID");
}
}
}
printf("Total removed: %d segment(s)\n", removed);
return 0;
}
# Shell equivalent for cleanup
# List all shm segments
ipcs -m
# Remove a specific segment by shmid
ipcrm -m <shmid>
# Remove ALL shared memory segments you own
ipcs -m | awk 'NR>3 {print $2}' | xargs -I{} ipcrm -m {}
Q1. What are the four System V shared memory limits on Linux?
SHMMNI (max number of segments), SHMMIN (min size — effectively 1 page), SHMMAX (max size of one segment), and SHMALL (max total pages across all segments).
Q2. Which error does shmget() return when SHMMNI is exceeded?
ENOSPC. The same error is returned when SHMALL (total page pool) is exhausted. EINVAL is returned when the requested size violates SHMMIN or SHMMAX.
Q3. What is SHMSEG and is it enforced on Linux?
SHMSEG limits how many segments a single process can have attached simultaneously. It exists on some UNIX systems but is not implemented on Linux.
Q4. How do you change SHMMAX without rebooting?
Write the new value to /proc/sys/kernel/shmmax: echo 268435456 > /proc/sys/kernel/shmmax. For persistence across reboots, add kernel.shmmax = 268435456 to /etc/sysctl.conf and run sysctl -p.
Q5. Difference between IPC_INFO and SHM_INFO?
IPC_INFO returns the configured limits (SHMMAX, SHMMNI, etc.) in a struct shminfo. SHM_INFO returns actual current resource usage (how many segments exist, how many pages are allocated/swapped) in a struct shm_info.
Q6. What does SHM_INFO return as its return value?
The highest used index in the kernel’s shared memory table. You use this as the upper bound when iterating with SHM_STAT to enumerate all segments.
Q7. What is SHM_STAT and how does it differ from IPC_STAT?
Both retrieve a shmid_ds structure. IPC_STAT takes a real shmid as argument. SHM_STAT takes a kernel table index (0, 1, 2 …) and returns the shmid as its return value, making it suitable for iterating the entire table.
Q8. A production process crashed and left shared memory behind. How do you find and remove it?
Run ipcs -m to list segments. Note the nattch column — segments with 0 attachments are orphaned. Remove with ipcrm -m <shmid>, or write a program that uses SHM_INFO + SHM_STAT to iterate and calls IPC_RMID on each orphaned segment.
Q9. SHMALL is measured in pages, not bytes. Why?
Because shared memory is allocated in page-sized chunks. Measuring in pages avoids false precision and aligns with kernel memory management, which operates at page granularity.
Next: Summary, best practices, relative addressing, and full producer-consumer example
