Overview
Linux supports two families of IPC mechanisms: the older System V IPC (inherited from UNIX System V) and the newer POSIX IPC (defined by POSIX.1b). Both provide message queues, semaphores, and shared memory — but they have very different interfaces, naming models, and portability characteristics. This file compares them side by side.
| Feature | System V IPC | POSIX IPC |
|---|---|---|
| Object Identification | Integer keys (key_t) |
String names (/name) |
| Open / Create API | msgget(), semget(), shmget() |
mq_open(), sem_open(), shm_open() |
| Interface Similarity to Files | Low — different model | High — open/close/unlink |
| Reference Counting | No | Yes |
| Deletion Simplicity | Hard (must check usage) | Easy (unlink when done) |
| SUSv3 Mandatory? | Yes (mandatory) | Optional component |
| Portability | High — nearly all UNIX | Lower — some gaps |
| CLI Management | ipcs / ipcrm |
ls / rm (non-standard) |
| ACL Support (Linux) | No | Yes (shm + sem, kernel 2.6.19+) |
One of the most visible differences between System V and POSIX IPC is how objects are identified.
2.1 System V IPC — Integer Keys
System V IPC uses integer keys of type key_t. You generate a key using ftok() from a pathname and a project ID, or use the special key IPC_PRIVATE.
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
key_t key;
int msqid;
/*
* System V approach: generate a key from a path and project ID.
* Both communicating processes must use the SAME path + proj_id
* to get the SAME key.
*/
key = ftok("/tmp/my_app", 'A');
if (key == -1) { perror("ftok"); exit(1); }
printf("System V key: %d (0x%x)\n", (int)key, (unsigned int)key);
/* Create or open the message queue using the key */
msqid = msgget(key, IPC_CREAT | 0644);
if (msqid == -1) { perror("msgget"); exit(1); }
printf("System V message queue ID: %d\n", msqid);
/* Delete — no reference counting, deleted immediately */
msgctl(msqid, IPC_RMID, NULL);
printf("Queue deleted.\n");
return 0;
}
Problems with System V keys:
ftok()can produce the same key for different inputs (collision)- You must manage key uniqueness yourself across your application
- The integer key is harder to read and debug than a string name
2.2 POSIX IPC — String Names
POSIX IPC uses human-readable string names starting with /. Two processes sharing /my_queue clearly know they are talking to the same object.
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/*
* POSIX approach: use a clear string name.
* Any process that knows the name can open it.
* No key generation step needed.
*/
mqd_t mqd = mq_open("/my_queue", O_CREAT | O_RDWR, 0644, NULL);
if (mqd == (mqd_t)-1) { perror("mq_open"); exit(1); }
printf("POSIX message queue '/my_queue' opened.\n");
mq_close(mqd);
mq_unlink("/my_queue");
return 0;
}
POSIX IPC follows the same model as regular files: open → use → close → unlink. System V IPC uses a different model with get / ctl / op style calls that are inconsistent with the file API.
The POSIX model is easier to understand because every Linux/UNIX programmer already knows the open/close/unlink file model. The System V API requires learning an entirely separate set of concepts.
System V IPC has no reference counting. If you call IPC_RMID on a System V queue while another process is using it, the object is deleted immediately. That other process will get an error on its next operation.
POSIX IPC has reference counting. Calling mq_unlink() only removes the name. The object stays alive until all processes close it. This makes POSIX IPC much safer to use in multi-process applications.
/* DANGER: System V IPC — no reference counting */
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
key_t key = ftok("/tmp", 'X');
int msqid = msgget(key, IPC_CREAT | 0644);
pid_t pid = fork();
if (pid == 0) {
/* Child: sleep then try to use the queue */
sleep(1);
/* By now, parent has deleted the queue.
* Any attempt to use msqid will fail with EINVAL. */
struct { long mtype; char mtext[64]; } buf;
ssize_t r = msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0);
if (r == -1)
perror("Child msgrcv — queue was deleted!");
exit(0);
}
/* Parent: delete queue immediately (dangerous with child using it) */
msgctl(msqid, IPC_RMID, NULL);
printf("Parent: deleted queue (child still running!)\n");
wait(NULL);
return 0;
}
/* SAFE: POSIX IPC — reference counting protects the object */
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
mqd_t mqd = mq_open("/safe_demo", O_CREAT | O_RDWR, 0644, NULL);
pid_t pid = fork();
if (pid == 0) {
/* Child: sleep then receive */
sleep(1);
mqd_t child_mqd = mq_open("/safe_demo", O_RDONLY);
if (child_mqd == (mqd_t)-1) {
/* Name is gone, but if we had it open before unlink, it works */
printf("Child: name already unlinked (expected)\n");
}
/* Child uses its own descriptor safely */
char buf[64];
unsigned int prio;
if (mq_receive(mqd, buf, sizeof(buf), &prio) != -1)
printf("Child: received '%s'\n", buf);
mq_close(mqd);
exit(0);
}
/* Parent: send then unlink — child still has descriptor, it's safe */
const char *msg = "safe message";
mq_send(mqd, msg, strlen(msg)+1, 0);
mq_unlink("/safe_demo"); /* Name removed, object lives on for child */
printf("Parent: unlinked, but child descriptor still valid\n");
mq_close(mqd);
wait(NULL);
return 0;
}
gcc -o safe_demo safe_demo.c -lrt
./safe_demo
Despite POSIX IPC being the more modern and cleaner interface, System V IPC is more portable in practice. Here is why:
5.1 SUSv3 Status
| Mechanism | System V SUSv3 Status | POSIX SUSv3 Status |
|---|---|---|
| Message Queues | Mandatory | Optional |
| Semaphores | Mandatory | Optional |
| Shared Memory | Mandatory | Optional |
5.2 Linux Kernel Support History
POSIX IPC was added to Linux in stages. Not all features were available in older kernels:
| Linux Kernel Version | Feature Added |
|---|---|
| 2.4 | POSIX Shared Memory (shm_open) added |
| 2.6 | Full POSIX Semaphore implementation available |
| 2.6.6 | POSIX Message Queues (mq_open) added |
| 2.6.19 | ACL support for POSIX shm and semaphores |
System V IPC has been in Linux since the very early kernel versions and is available on virtually every UNIX-like system. If your code needs to run on many platforms — including old kernels and non-Linux UNIX systems — System V IPC is the safer choice.
5.3 Non-Standard Naming Details
Different UNIX implementations use different conventions for POSIX IPC object names and file-system paths. For example:
- On Linux, message queues appear under
/dev/mqueue/ - On macOS, they are not accessible as files at all
- Some systems require a double slash prefix (
//name)
This makes writing portable POSIX IPC programs slightly more work than writing portable System V IPC programs.
/* Portable name helper — works around naming quirks */
#ifdef __linux__
#define IPC_NAME(n) "/" n /* Linux: single slash */
#elif defined(__APPLE__)
#define IPC_NAME(n) "/" n /* macOS: same on Darwin */
#else
#define IPC_NAME(n) "/" n /* Adjust per platform */
#endif
/* Usage: */
mqd_t mqd = mq_open(IPC_NAME("my_queue"), O_CREAT | O_RDWR, 0644, NULL);
| Situation | Recommended | Reason |
|---|---|---|
| Linux-only application | POSIX IPC | Cleaner API, reference counting, easier debugging |
| Must run on multiple UNIX systems | System V IPC | Universally available, mandatory in SUSv3 |
| Learning / new development | POSIX IPC | Simpler, consistent with file model, modern standard |
| Maintaining legacy code | System V IPC | Legacy code already uses it; no reason to rewrite |
| Old Linux kernel (< 2.6.6) | System V IPC | POSIX message queues not available before 2.6.6 |
/*
* Side-by-side: same task (create a semaphore, lock, unlock, delete)
* implemented in System V and POSIX styles.
*/
/* ========== SYSTEM V STYLE ========== */
#include <sys/sem.h>
#include <sys/ipc.h>
void sysv_demo(void)
{
union semun { int val; struct semid_ds *buf; unsigned short *array; } arg;
key_t key = ftok("/tmp", 'S');
int semid = semget(key, 1, IPC_CREAT | 0600);
arg.val = 1; /* binary semaphore — initial value 1 */
semctl(semid, 0, SETVAL, arg);
/* Lock (decrement) */
struct sembuf op = { 0, -1, 0 };
semop(semid, &op, 1);
printf("SysV: locked\n");
/* Unlock (increment) */
op.sem_op = 1;
semop(semid, &op, 1);
printf("SysV: unlocked\n");
/* Delete immediately — no reference counting */
semctl(semid, 0, IPC_RMID);
printf("SysV: deleted\n");
}
/* ========== POSIX STYLE ========== */
#include <semaphore.h>
void posix_demo(void)
{
sem_t *sem = sem_open("/posix_compare", O_CREAT, 0600, 1);
sem_wait(sem); /* lock */
printf("POSIX: locked\n");
sem_post(sem); /* unlock */
printf("POSIX: unlocked\n");
sem_close(sem);
sem_unlink("/posix_compare"); /* object destroyed when refcount = 0 */
printf("POSIX: unlinked\n");
}
int main(void)
{
sysv_demo();
printf("\n");
posix_demo();
return 0;
}
gcc -o compare_ipc compare_ipc.c -lrt -lpthread
./compare_ipc
POSIX IPC is the modern alternative to System V IPC, providing three mechanisms: message queues, semaphores, and shared memory. Here are the key takeaways from Chapter 51:
- POSIX IPC uses string names and a file-like open/close/unlink model that is easier to understand than System V IPC’s key/get/ctl model.
- POSIX IPC objects are reference counted. Unlink removes the name; the object is destroyed only when all processes close it. This simplifies safe deletion.
- POSIX IPC objects have kernel persistence — they survive process termination but not system reboot.
- On Linux, POSIX IPC objects live in virtual filesystems (
/dev/mqueue,/dev/shm) and can be managed withlsandrm. - System V IPC is more portable — it is mandatory in SUSv3 and present on all UNIX systems. POSIX IPC is optional in SUSv3 and was added to Linux in stages (shm in 2.4, semaphores in 2.6, queues in 2.6.6).
- Compile POSIX IPC programs on Linux with
-lrt(and-lpthreadfor semaphores).
POSIX message queues ↔ System V message queues; POSIX named semaphores ↔ System V semaphores; POSIX shared memory ↔ System V shared memory.
POSIX IPC uses string names starting with / (e.g., /my_queue). System V IPC uses integer keys of type key_t, typically generated with ftok().
POSIX IPC has reference counting. Calling mq_unlink() only removes the name; the object lives until all processes close it. System V IPC has no reference counting — IPC_RMID deletes the object immediately even if other processes are using it.
Linux 2.6.6. POSIX shared memory was added in 2.4 and full POSIX semaphores in 2.6.
Portability. System V IPC is mandatory in SUSv3 and available on virtually every UNIX system. POSIX IPC is optional in SUSv3 and not supported by some UNIX implementations.
System V has ipcs (list) and ipcrm (delete). POSIX IPC has no standard commands. On Linux you use ls /dev/mqueue/ and ls /dev/shm/ to list, and rm to delete.
POSIX IPC uses the same open/close/unlink pattern that UNIX programmers already know from working with files. Objects are named with strings, opened with a function similar to open(), closed with a close function, and deleted with an unlink function. System V IPC uses an entirely different get/ctl/op naming convention.
The semaphore still exists in the kernel (kernel persistence). The next run should use O_CREAT | O_EXCL to detect this: if sem_open() fails with EEXIST, call sem_unlink() to remove the leftover object before creating a fresh one. Register signal handlers so cleanup happens even on crashes.
