When you run ipcs -q in a terminal, it shows you all System V message queues on the system. How does ipcs get this information? There are two methods the kernel provides:
- Method 1: /proc filesystem — Read files under
/proc/sysvipc/. This was covered in Section 45.7 of TLPI. - Method 2: Linux-specific msgctl() operations — Use the special
MSG_INFOandMSG_STAToperations ofmsgctl(). This is what Section 46.6 covers, and this is howipcsactually works on many Linux systems.
The Linux kernel maintains an internal table of slots for each type of IPC object (message queues, semaphore sets, shared memory segments). Each slot in this table either holds a pointer to a live IPC object or is empty.
The key insight is that the IPC identifier (msqid) is not the same as the array index. The array index is just a slot number (0, 1, 2, …). The msqid is a larger number computed from the slot index combined with a sequence counter to prevent reuse confusion. MSG_STAT takes an index and returns the msqid as its function return value.
Purpose: Two things at once:
- Fills a
struct msginfowith resource consumption statistics for all message queues system-wide (number of queues, total bytes used, etc.). - Returns (as the function’s return value) the index of the highest occupied slot in the entries array. This is called
maxind.
Analogy: MSG_INFO is like asking the library “how many shelves do you have, and what is the highest shelf number that has a book on it?” — you use the answer to know how far to scan.
#define _GNU_SOURCE before including headers. The return value of msgctl() when using MSG_INFO is the highest index (maxind), not an error code.Purpose: Retrieve the msqid_ds structure for the IPC object at a given array index (not an msqid). This is how you read metadata for each queue when scanning.
Differs from IPC_STAT in two ways:
| IPC_STAT | MSG_STAT | |
|---|---|---|
| First argument | IPC identifier (msqid) | Array index (0, 1, 2…) |
| Return value | 0 on success | The msqid of that slot (if occupied) |
| On empty slot | EINVAL (invalid msqid) | EINVAL (empty slot) |
| No permission | EACCES | EACCES (ignore and continue scanning) |
#define _GNU_SOURCE.The TLPI book describes a two-step algorithm (which is how ipcs works internally):
msgctl(0, MSG_INFO, (struct msqid_ds*)&info). The return value is maxind — the highest occupied slot index in the message queue entries array. This tells you how far to scan.msgctl(index, MSG_STAT, &ds). On success, the return value is the msqid for that slot and ds holds the queue’s metadata. Ignore EINVAL (empty slot) and EACCES (no permission) — these are expected during scanning.ipcs. Always treat the output as a snapshot, not a guarantee.The same pattern applies to the other two System V IPC mechanisms — semaphore sets and shared memory segments:
| IPC Type | Info Operation | Stat Operation | Relevant ctl Function |
|---|---|---|---|
| Message Queues | MSG_INFO | MSG_STAT | msgctl() |
| Semaphore Sets | SEM_INFO | SEM_STAT | semctl() |
| Shared Memory | SHM_INFO | SHM_STAT | shmctl() |
All six constants require #define _GNU_SOURCE to be defined. The algorithm to enumerate all objects is identical across all three types — call the INFO variant to get maxind, then loop with the STAT variant.
This program implements the two-step algorithm described above. It mirrors the svmsg_ls program from Listing 46-6 in TLPI.
#define _GNU_SOURCE /* Required for MSG_INFO, MSG_STAT, struct msginfo */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/msg.h>
int main(void)
{
int maxind; /* Highest occupied index in entries array */
int msqid; /* IPC identifier returned by MSG_STAT */
int idx; /* Loop index for scanning */
struct msginfo info; /* Receives resource stats from MSG_INFO */
struct msqid_ds ds; /* Receives queue metadata from MSG_STAT */
/*
* Step 1: MSG_INFO
* - Fills 'info' with system-wide resource statistics
* - Return value = highest occupied slot index (maxind)
* - First argument is ignored (pass 0)
* - Cast msginfo ptr to (struct msqid_ds *) — required by API signature
*/
maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &info);
if (maxind == -1) {
perror("msgctl MSG_INFO");
exit(EXIT_FAILURE);
}
printf("maxind: %d\n\n", maxind);
/*
* Step 2: Loop from 0 to maxind, using MSG_STAT for each index.
*
* MSG_STAT:
* - First arg is the array INDEX (not a queue ID)
* - On success: fills 'ds', returns the msqid of that slot
* - On empty slot: returns -1, errno = EINVAL → skip
* - On no permission: returns -1, errno = EACCES → skip
*/
printf("%-8s %-10s %-14s %s\n", "index", "ID", "key", "messages");
printf("%-8s %-10s %-14s %s\n", "-----", "--", "---", "--------");
for (idx = 0; idx <= maxind; idx++) {
msqid = msgctl(idx, MSG_STAT, &ds);
if (msqid == -1) {
if (errno == EINVAL || errno == EACCES)
continue; /* Empty slot or no permission — normal, skip */
perror("msgctl MSG_STAT");
continue;
}
/* Live queue found: print its details */
printf("%-8d %-10d 0x%08lx %lu\n",
idx,
msqid,
(unsigned long) ds.msg_perm.__key,
(unsigned long) ds.msg_qnum);
}
return 0;
}
gcc -D_GNU_SOURCE -o list_queues list_queues.c && ./list_queuesCreate some queues first:
ipcmk -Q (if available) or use msgget() in another program. Compare output with ipcs -q.This example focuses on using MSG_INFO to print system-wide message queue resource statistics, not just the list of queues. Useful for monitoring queue pressure in production systems.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/msg.h>
int main(void)
{
struct msginfo info;
int maxind;
/*
* MSG_INFO fills struct msginfo with system-wide resource usage:
* msgpool - number of message queue headers allocated
* msgmap - number of entries in message map
* msgmax - max bytes per message (same as MSGMAX)
* msgmnb - max bytes per queue (same as MSGMNB)
* msgmni - max queues on system (same as MSGMNI)
* msgssz - message segment size
* msgtql - max total messages
* msgseg - max number of message segments
*
* The RETURN VALUE is the highest occupied index in the entries array.
*/
maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &info);
if (maxind == -1) {
perror("msgctl MSG_INFO");
exit(EXIT_FAILURE);
}
printf("===== Message Queue Resource Info (MSG_INFO) =====\n\n");
printf("Highest occupied entry index : %d\n", maxind);
printf("Max queues on system (msgmni): %d\n", info.msgmni);
printf("Max bytes per message (msgmax): %d\n", info.msgmax);
printf("Max bytes per queue (msgmnb): %d\n", info.msgmnb);
printf("Max total messages (msgtql): %d\n", info.msgtql);
printf("\n");
printf("To list all queues, scan indices 0..%d using MSG_STAT.\n", maxind);
return 0;
}
The ipcs utility is a standard tool on every Linux system. When you run ipcs -q, it internally performs exactly the two-step algorithm above:
- Calls
msgctl(MSG_INFO)to discovermaxind - Loops with
msgctl(MSG_STAT)to read each live queue
The shell session in TLPI Listing 46-6’s commentary confirms this — the output of the custom svmsg_ls program exactly matches ipcs -q output, showing the same msqid values, keys, and message counts.
ipcs -q — list all message queuesipcs -q -i <msqid> — detailed info for one queueipcrm -q <msqid> — remove a specific queueipcrm --all=msg — remove all message queues (use with caution!)/proc/sysvipc/msg, /proc/sysvipc/sem, and /proc/sysvipc/shm. These files contain tabular data about all live IPC objects. Method 2: Use the Linux-specific msgctl() (or semctl/shmctl) operations MSG_INFO and MSG_STAT — this is what the ipcs command uses internally.msqid_ds structure. The key differences are: IPC_STAT takes an IPC identifier (msqid) as the first argument and returns 0 on success. MSG_STAT takes an array index as the first argument and returns the msqid of that slot on success (or -1 on error). IPC_STAT is for querying a known queue. MSG_STAT is for scanning all queues by index.struct msginfo with system-wide resource usage statistics for message queues (limits like msgmni, msgmax, msgmnb, and current usage statistics). Its return value is the highest occupied slot index (maxind) in the kernel’s message queue entries array. This is used as the upper bound for scanning with MSG_STAT.EINVAL. Some live queues may have permissions that deny the calling process access, causing EACCES. Both of these are expected and normal during an exhaustive scan. Ignoring them allows the scan to continue past these “holes” in the table.index + (sequence_counter × MSGMNI). The sequence counter increments each time a slot is reused, ensuring that a stale msqid from a deleted queue cannot accidentally refer to a new queue occupying the same slot. MSG_STAT takes an index; all other operations take an msqid._GNU_SOURCE must be defined before including the System V IPC header files. This is because MSG_INFO, MSG_STAT, and the associated struct msginfo are Linux/GNU extensions, not part of the POSIX standard. The same applies to SEM_INFO, SEM_STAT, SHM_INFO, and SHM_STAT.semctl(0, SEM_INFO, ...) to get maxind, then semctl(idx, SEM_STAT, ...) to scan. For shared memory: shmctl(0, SHM_INFO, ...) to get maxind, then shmctl(idx, SHM_STAT, ...) to scan. All six constants (MSG_INFO, MSG_STAT, SEM_INFO, SEM_STAT, SHM_INFO, SHM_STAT) require _GNU_SOURCE.ipcs -q command. It lists all System V message queues with their msqid, key, owner, permissions, and message count — exactly what the two-step scan produces. ipcs without flags shows all three IPC types. ipcs -q -i <msqid> shows detailed info for a specific queue (equivalent to IPC_STAT for that queue).46.4 — msqid_ds Fields 46.5 — Queue Limits embeddedpathashala.com
