ipcs ipcrm and System V IPC Limits
Because System V IPC objects persist in the kernel until something explicitly removes them, every Linux system needs a way to see what is resident and to delete leaked objects, and every kernel imposes limits on how many such objects (and how large) may exist. The two userspace commands are
ipcs(list message queues, semaphore sets, and shared-memory segments, with their keys, ids, owners, permissions, and attach counts) andipcrm(remove an object by itsidor itskey). The limits are a handful of sysctls under/proc/sys/kernel/—shmmax/shmall/shmmnifor shared memory,msgmax/msgmnb/msgmnifor message queues, and the four-valuedsemfor semaphores. The most important, frequently-misunderstood fact about the modern defaults is that the shared-memory size limits are effectively disabled: at Linux 6.12,SHMMAXandSHMALLboth default toULONG_MAX − 2²⁴, i.e. ~18 exabytes, so the historical “raise SHMMAX before installing your database” ritual is obsolete on current kernels (include/uapi/linux/shm.h, v6.12; ipc/shm.c, v6.12).
This is the operational/administrative companion to System V IPC Overview and Keys. It walks ipcs and ipcrm first, then dissects each kernel limit, its sysctl, its verified v6.12 default, and its per-namespace scoping.
Mental Model
ipcs and ipcrm are to System V IPC what ls and rm are to files — except the “directory” is a kernel namespace, not a path. Both commands read their listing from the /proc/sysvipc/ pseudo-files (/proc/sysvipc/shm, .../msg, .../sem) when /proc is mounted, falling back to the *ctl(IPC_STAT) syscalls otherwise (ipcs(1)). The limits are ceilings the kernel checks inside the *get() and operation syscalls; exceeding one yields ENOSPC, EINVAL, or E2BIG rather than a silent truncation.
flowchart TB subgraph CMD["Admin commands"] IPCS["ipcs -a / -m / -q / -s / -l / -u"] IPCRM["ipcrm -m id / -M key / -a"] end subgraph PROC["/proc — read & tune"] SYSVIPC["/proc/sysvipc/{shm,msg,sem}<br/>(live object listing)"] SYSCTL["/proc/sys/kernel/{shmmax,shmall,shmmni,<br/>msgmax,msgmnb,msgmni,sem}<br/>(per-namespace limits)"] end subgraph K["Kernel — per IPC namespace"] NS["ipc_namespace fields:<br/>shm_ctlmax, shm_ctlall, shm_ctlmni<br/>msg_ctlmax, msg_ctlmnb, msg_ctlmni<br/>sem_ctls[4]"] end IPCS -- "reads" --> SYSVIPC IPCS -- "-l reads" --> SYSCTL IPCRM -- "IPC_RMID syscall" --> K SYSCTL <-- "proc_handlers" --> NS K --> SYSVIPC
The admin-facing surface of System V IPC. What it shows: ipcs reads the live object list from /proc/sysvipc/ and the ceilings from /proc/sys/kernel/; ipcrm issues IPC_RMID; the sysctls map to per-namespace fields inside struct ipc_namespace. The insight: there is no global System V state — every limit and every object lives in one IPC namespace, so a container with its own namespace has its own independent limits and its own leaked objects.
ipcs — Seeing What Is Resident
Run with no arguments, ipcs “displays information about System V inter-process communication facilities” for all three resource types at once (ipcs(1)). The resource-selection options narrow it to one type — -m/--shmems (shared memory), -q/--queues (message queues), -s/--semaphores (semaphore arrays), -a/--all (all three, the default). The output-format options, of which only the last given takes effect, change what columns you see:
-c/--creator— show the creator and owner UIDs/GIDs (thecuid/cgidvsuid/giddistinction from the [[System V IPC Overview and Keys|ipc_permheader]]).-p/--pid— “Show PIDs of creator and last operator” (e.g. for shm, the PID that created it and the PID of the lastshmat/shmdt).-t/--time— show last-operation timestamps (shm_atime,shm_dtime,msg_stime, etc.).-u/--summary— a status summary (totals: how many segments, how much memory in use) rather than a per-object listing.-l/--limits— print the limits (the sysctl ceilings) for the selected type rather than the live objects.-i ID/--id ID— “Show full details on just the one resource element identified by id”; must be combined with-m,-q, or-s.
A default ipcs -m (shared memory) prints, per segment: the key (the ftok/hard-coded key, shown as hex, or 0x00000000 for an IPC_PRIVATE object that has no key), the shmid (the id you pass to ipcrm), the owner, the perms (the octal mode from shm_perm.mode), the segment bytes, the nattch (number of processes currently attached via shmat), and the status — where dest means the segment has been marked for destruction (SHM_DEST, an IPC_RMID was issued but processes are still attached) and locked means it is mlock-pinned. A representative listing:
$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 32768 postgres 600 4194304 3 dest
0x52010d2f 65537 app 660 1048576 1
The first row is a dest-flagged segment: key 0x0 (it was IPC_PRIVATE), three processes still attached, already marked for removal — it will vanish when nattch reaches 0. Reading this correctly is the difference between “this is leaking” and “this is mid-teardown.” ipcs -m -i 32768 would dump full details on just that one segment, including the creator/last-operator PIDs and the attach/detach times.
ipcrm — Removing Leaked Objects
ipcrm “removes System V inter-process communication (IPC) objects” — and you must be the object’s creator, its owner, or the superuser to do so (ipcrm(1)). Its defining design choice is lowercase = by id, uppercase = by key:
- Message queues:
-q/--queue-id msgidremoves by id;-Q/--queue-key msgkeyremoves by key. - Semaphores:
-s/--semaphore-id semidby id;-S/--semaphore-key semkeyby key. - Shared memory:
-m/--shmem-id shmidby id;-M/--shmem-key shmkeyby key.
Numbers may be decimal, hex (0x…), or octal (leading 0) — convenient because ipcs prints keys in hex. There are also POSIX variants (--posix-shmem, --posix-mqueue, --posix-semaphore) for POSIX named objects, and -a/--all to “remove all resources” (optionally limited to specified types) — which the man page warns against using casually, since “it is possible that other programs depend on these resources” at startup. A typical leaked-object cleanup:
ipcs -m # find the leaked shmid, say 32768
ipcrm -m 32768 # remove by id
# or, if you only know the key the program used:
ipcrm -M 0x52010d2f # remove by key (uppercase M)The crucial behavioural asymmetry: shared memory removal is deferred, semaphore and message-queue removal is immediate. Per the man page, “A shared memory object is only removed after all currently attached processes have detached” it (ipcrm(1)) — ipcrm -m on an attached segment merely sets the SHM_DEST flag (the dest status above), and the segment is freed only when the last shmdt happens. Message queues and semaphores have no such grace: IPC_RMID destroys them instantly, immediately waking any process blocked in msgrcv/semop with EIDRM. This mirrors the kernel logic in ipc/shm.c, where if shp->shm_nattch is non-zero on IPC_RMID the segment “receives SHM_DEST marking and becomes inaccessible; otherwise shm_destroy() executes immediately” (ipc/shm.c, v6.12).
The Kernel Limits and Their Sysctls
Each limit is a field of the per-namespace struct ipc_namespace, exposed as a writable sysctl under /proc/sys/kernel/. The registrations live in ipc/ipc_sysctl.c; all of the substantive ones operate on ns->… fields, confirming they are per-IPC-namespace, not global (ipc/ipc_sysctl.c, v6.12). The defaults are assigned in the three *_init_ns() functions and ultimately come from the UAPI header constants.
Shared memory — shmmax, shmall, shmmni
These map to ns->shm_ctlmax, ns->shm_ctlall, ns->shm_ctlmni, initialised in shm_init_ns() (ipc/shm.c, v6.12):
void shm_init_ns(struct ipc_namespace *ns)
{
ns->shm_ctlmax = SHMMAX; /* per-segment max size, bytes */
ns->shm_ctlall = SHMALL; /* system-wide total, in pages */
ns->shm_ctlmni = SHMMNI; /* max number of segments */
ns->shm_rmid_forced = 0;
ns->shm_tot = 0;
ipc_init_ids(&shm_ids(ns));
}The constants resolve as follows in the v6.12 UAPI header (include/uapi/linux/shm.h, v6.12):
#define SHMMIN 1 /* min shared seg size (bytes) */
#define SHMMNI 4096 /* max num of segs system wide */
#define SHMMAX (ULONG_MAX - (1UL << 24))
#define SHMALL (ULONG_MAX - (1UL << 24))
#define SHMSEG SHMMNI /* max shared segs per process */shmmax— the maximum size of a single segment, in bytes. DefaultULONG_MAX − 2²⁴≈ 1.8 × 10¹⁹ bytes (~18 EB) on a 64-bit kernel — i.e. effectively unlimited.shmgetreturnsEINVALifsizeexceeds it (shmget(2)).shmall— the system-wide ceiling on total shared memory, counted in pages (not bytes). AlsoULONG_MAX − 2²⁴pages by default — likewise effectively unlimited. Exceeding it yieldsENOSPC.shmmni— the maximum number of segments in the namespace. Default 4096. Exhaustion yieldsENOSPC.
Uncertain
The headline claim —
SHMMAX = SHMALL = ULONG_MAX − (1 << 24)at Linux 6.12 — is verified directly against the v6.12 UAPI header andshm_init_ns()(both fetched above), so it is not in doubt. What is worth a flag: the man page shmget(2) attributes this default to “Linux 3.16+”, whereas the LWN discussion of Manfred Spraul’s patch I read does not state the exact landing release. Verify: the first release in which theULONG_MAX − 2²⁴default shipped. Reason: man-page says 3.16; LWN thread (April 2014) does not pin the merge. To resolve:git log --oneline -- include/uapi/linux/shm.haround the 3.16 merge window. This does not affect the 6.12 fact. uncertain
The deliberately-not-quite-ULONG_MAX value has a real rationale. Manfred Spraul chose ULONG_MAX − (1 << 24) instead of literal ULONG_MAX specifically to leave headroom for a known userspace anti-pattern: “there are known cases where an application simply tries to increment the value SHMMAX rather than setting it, which causes an overflow” (LWN, Changing the default shared memory limits). Subtracting 2²⁴ (16 MiB) gives such code room to add to the value without wrapping around to a tiny number. The change was motivated by the fact that the old 32 MB SHMMAX had become a perennial install-time obstacle — “it has been routine procedure for several years for users to increase SHMMAX on production systems” (LWN) — so the maintainers simply set it out of the way. The old SHMALL of roughly 8 GB (in the 2.4–3.15 era, per shmget(2)) was raised at the same time.
There is also shm_rmid_forced (ns->shm_rmid_forced, default 0). When set to 1, “all System V shared memory segments will be marked for destruction as soon as the number of attached processes falls to zero” (proc_sys_kernel(5)) — effectively turning off the kernel-persistent lifetime for shm and making segments behave more like POSIX objects. Its proc handler, proc_ipc_dointvec_minmax_orphans, immediately runs shm_destroy_orphaned(ns) on write so existing orphans are reaped at once (ipc/ipc_sysctl.c, v6.12).
Message queues — msgmax, msgmnb, msgmni
These map to ns->msg_ctlmax, ns->msg_ctlmnb, ns->msg_ctlmni, initialised in msg_init_ns() to fixed constants — not the historical memory-scaled value (ipc/msg.c, v6.12):
ns->msg_ctlmax = MSGMAX; /* 8192 — max bytes in one message */
ns->msg_ctlmnb = MSGMNB; /* 16384 — default max bytes in a queue */
ns->msg_ctlmni = MSGMNI; /* 32000 — max number of queues */with the constants from the header (include/uapi/linux/msg.h, v6.12):
#define MSGMNI 32000 /* <= IPCMNI */ /* max # of msg queue identifiers */
#define MSGMAX 8192 /* <= INT_MAX */ /* max size of message (bytes) */
#define MSGMNB 16384 /* <= INT_MAX */ /* default max size of a message queue */msgmax— the maximum number of bytes in a single message body. Default 8192.msgsndof a larger message fails withEINVAL.msgmnb— the value used to initialisemsg_qbytesfor newly created queues, i.e. the default per-queue byte capacity. Default 16384. A privileged process can raise an individual queue’smsg_qbytesbeyond this viaIPC_SET.msgmni— the maximum number of message-queue identifiers in the namespace. Default 32000.
The msgmni default deserves emphasis because the documentation landscape is full of stale claims that it is auto-computed from RAM. That was true historically: from Linux 2.6.27 to 3.18, the auto_msgmni sysctl recomputed msgmni on memory hotplug or IPC-namespace creation. Since Linux 3.19 this no longer happens — msgmni simply defaults “near the maximum value possible” (32000), and “the content of [auto_msgmni] has no effect … and reads from this file always return the value 0” (proc_sys_kernel(5)). Correspondingly, ipc/ipc_sysctl.c still registers auto_msgmni but only via the read-only-ish handler proc_ipc_auto_msgmni, whose comment notes “writing to auto_msgmni has no effect” (ipc/ipc_sysctl.c, v6.12). Treat any guide that tells you to tune auto_msgmni as out of date.
Semaphores — the four-valued sem
Unlike the others, the semaphore limits are packed into a single sysctl that holds four numbers, mapping to ns->sem_ctls[4]. Reading /proc/sys/kernel/sem yields four whitespace-separated values, “in order: SEMMSL, SEMMNS, SEMOPM, SEMMNI” (proc_sys_kernel(5)). The kernel’s accessor macros confirm the index order (ipc/sem.c, v6.12):
#define sc_semmsl sem_ctls[0] /* SEMMSL — max semaphores per set */
#define sc_semmns sem_ctls[1] /* SEMMNS — max semaphores system-wide */
#define sc_semopm sem_ctls[2] /* SEMOPM — max ops per semop() call */
#define sc_semmni sem_ctls[3] /* SEMMNI — max number of sets */and sem_init_ns() seeds them from the header constants:
void sem_init_ns(struct ipc_namespace *ns)
{
ns->sc_semmsl = SEMMSL; /* 32000 */
ns->sc_semmns = SEMMNS; /* SEMMNI * SEMMSL = 32000 * 32000 */
ns->sc_semopm = SEMOPM; /* 500 */
ns->sc_semmni = SEMMNI; /* 32000 */
ns->used_sems = 0;
ipc_init_ids(&ns->ids[IPC_SEM_IDS]);
}with (include/uapi/linux/sem.h, v6.12):
#define SEMMNI 32000 /* max # of semaphore identifiers */
#define SEMMSL 32000 /* max num of semaphores per id */
#define SEMMNS (SEMMNI*SEMMSL) /* max # of semaphores in system */
#define SEMOPM 500 /* max num of ops per semop call */
#define SEMVMX 32767 /* semaphore maximum value */SEMMSL(index 0) — maximum number of semaphores per set; bounds thensemsargument tosemget. Default 32000 (since Linux 3.19, per semget(2)).semgetwith a largernsemsreturnsEINVAL.SEMMNS(index 1) — system-wide total number of semaphores across all sets. DefaultSEMMNI * SEMMSL= 32000 × 32000 = 1.024 × 10⁹. Hitting it yieldsENOSPC.SEMOPM(index 2) — maximum operations in a singlesemop/semtimedopcall (System V semaphores uniquely allow an atomic batch of operations). Default 500. A larger batch returnsE2BIG.SEMMNI(index 3) — maximum number of semaphore sets (identifiers) in the namespace. Default 32000. Exhaustion yieldsENOSPC.
(A separate, non-tunable constant SEMVMX = 32767 caps the value any individual semaphore may hold.) The sem sysctl’s proc handler is special — proc_ipc_sem_dointvec validates the proposed quad with sem_check_semmni() and reverts the whole write on error, so you cannot set an internally inconsistent set of four values (ipc/ipc_sysctl.c, v6.12).
Per-Namespace Scoping — Why Containers Have Their Own Limits
Every limit above is a field of struct ipc_namespace, and every sysctl handler in ipc/ipc_sysctl.c reads or writes ns->… (ipc/ipc_sysctl.c, v6.12). The “namespace” a process sees is determined by its membership in an IPC namespace (created with unshare(CLONE_NEWIPC) or clone(CLONE_NEWIPC)). The consequence is that /proc/sys/kernel/shmmax (and the others) shows and tunes the limits for the calling process’s IPC namespace, not a machine-global value. A container with its own IPC namespace starts from a fresh copy of the defaults and can be tuned independently; a process sharing the host namespace tunes the host’s. Likewise the objects counted against shmmni/msgmni/semmni are only those in the same namespace — which is why ipcs inside a container shows only that container’s objects and why destroying an IPC namespace (last member exits) reaps all its objects at once, a clean leak boundary the host namespace does not enjoy.
Failure Modes and Diagnostics
“shmget fails with ENOSPC even though I have free RAM.” You hit shmmni (4096 segments) or, far less likely now, shmall. Because shmmax/shmall are effectively infinite at v6.12, the segment count limit is the one you actually reach — check ipcs -m | wc -l against shmmni. A flood of leaked segments from crashed workers is the usual cause; ipcrm them and fix the leak.
“semop fails with E2BIG.” Your batch exceeds SEMOPM (500). Split the operation array.
“My old runbook says to set kernel.shmmax in /etc/sysctl.conf before installing Oracle/Postgres.” On 6.12 this is harmless but pointless for the size limits — the defaults are already astronomically high. The databases’ own documentation and installers may still set it for compatibility with ancient kernels. The one shm sysctl still worth touching is sometimes shmmni (raise the segment count) or shm_rmid_forced=1 (auto-reap orphans) — not shmmax.
“ipcs shows a dest segment that won’t go away.” That is a segment already marked for destruction (someone ran ipcrm -m or IPC_RMID) but with nattch > 0. It will disappear when the last attached process detaches or exits. Find the holders via the creator/last-operator PID (ipcs -m -p) and the system-wide picture, then kill or fix them.
“I ipcrm’d a message queue and a blocked reader died.” Expected: IPC_RMID on a queue or semaphore is immediate and wakes blocked msgrcv/semop callers with EIDRM (“identifier removed”). Only shared memory defers. Code that uses System V queues/semaphores must handle EIDRM.
Alternatives and When to Choose Them
For monitoring, ipcs is fine but coarse; production systems usually scrape /proc/sysvipc/{shm,msg,sem} directly or use the lsipc utility (from util-linux, the same package as ipcs/ipcrm), which offers a more parseable, column-selectable output. For cleanup, prefer designing the leak away — use the [[System V Shared Memory|lazy SHM_DEST removal]] (mark for destruction immediately after shmat so it self-cleans), set shm_rmid_forced=1 namespace-wide, or move to POSIX named objects which die on last-close-after-unlink. For limits, the modern stance is to leave the shm size sysctls at their effectively-infinite defaults and only manage the count limits and namespace scoping.
Production Notes
The single biggest real-world takeaway is that the shm-size tuning ritual is dead on current kernels. Years of database install guides, Stack Overflow answers, and vendor docs tell administrators to bump kernel.shmmax/kernel.shmall — advice that was essential on 2.4–3.15 kernels (32 MB SHMMAX, ~8 GB SHMALL) but is inert on anything from Linux 3.16 onward, including 6.12 (LWN; shmget(2)). The limits that still bite are counts (shmmni segment exhaustion from leaks) and the still-modest msgmnb/msgmax for message queues. The second takeaway is the container leak boundary: relying on per-IPC-namespace teardown to reap leaked objects is robust only if the container actually has its own IPC namespace — docker run --ipc=host (or Kubernetes hostIPC: true) re-shares the host namespace and re-exposes both the host’s limits and the host-wide leak risk, so security- and reliability-conscious deployments avoid it.
See Also
- System V IPC Overview and Keys — keys, the
key_t→idmapping, and the kernel-persistent lifetime these tools manage - System V Shared Memory —
shmget/shmat, theSHM_DESTlazy-destroy that makesipcrm -mdeferred - IPC Namespaces — why every limit and object above is per-namespace
- Linux IPC MOC — the parent map