What You Will Learn
This file explains the complete lifecycle of a POSIX IPC object — from creation through deletion. You will understand reference counting, how and when objects are actually destroyed, object persistence across process lifetimes, and how to list and remove IPC objects from the Linux command line.
The kernel maintains a reference count for every open POSIX IPC object. The count tracks how many processes currently have the object open. This is the same concept used for open files.
| Event | Reference Count | Object Exists? |
|---|---|---|
Process A calls mq_open() (creates object) |
1 | ✔ Yes |
Process B calls mq_open() on same object |
2 | ✔ Yes |
Process A calls mq_unlink() (removes name) |
2 | ⚠ Name gone, object still alive |
Process A calls mq_close() |
1 | ✔ Still alive (Process B using it) |
Process B calls mq_close() |
0 | ✘ Object destroyed |
The critical insight: unlink removes the name. The object data is not destroyed until the reference count reaches zero.
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/*
* Demonstrates that after mq_unlink(), the object is still
* accessible through an already-open descriptor.
*/
int main(void)
{
mqd_t mqd;
char msg[] = "hello";
char buf[64];
unsigned int prio;
/* Step 1: Create and open the queue */
mqd = mq_open("/refcount_demo", O_CREAT | O_RDWR, 0644, NULL);
if (mqd == (mqd_t)-1) { perror("mq_open"); exit(1); }
printf("Queue created and opened (refcount = 1)\n");
/* Step 2: Send a message through the open descriptor */
if (mq_send(mqd, msg, sizeof(msg), 0) == -1) {
perror("mq_send"); exit(1);
}
printf("Message sent: '%s'\n", msg);
/* Step 3: Unlink — removes the name, NOT the object */
if (mq_unlink("/refcount_demo") == -1) { perror("mq_unlink"); exit(1); }
printf("Name unlinked — but descriptor mqd still valid!\n");
/* Step 4: We can still receive through the open descriptor */
if (mq_receive(mqd, buf, sizeof(buf), &prio) == -1) {
perror("mq_receive"); exit(1);
}
printf("Still received after unlink: '%s'\n", buf);
/* Step 5: No new open() on the same name would find this object */
mqd_t mqd2 = mq_open("/refcount_demo", O_RDONLY);
if (mqd2 == (mqd_t)-1) {
printf("Cannot open by name after unlink — as expected.\n");
}
/* Step 6: Close — refcount drops to 0, object is now destroyed */
mq_close(mqd);
printf("Closed — object now truly destroyed.\n");
return 0;
}
Compile and run:
gcc -o refcount_demo refcount_demo.c -lrt
./refcount_demo
Each POSIX IPC mechanism has a matching unlink function. Calling unlink on an object does two things:
- Immediately removes the object’s name from the system
- Marks the object for destruction once the reference count reaches zero
| IPC Type | Unlink Function | Object destroyed when… |
|---|---|---|
| Message Queue | mq_unlink(name) | All processes have called mq_close() |
| Semaphore | sem_unlink(name) | All processes have called sem_close() |
| Shared Memory | shm_unlink(name) | All processes have called munmap() |
After unlink, any new call to open the same name will either fail (if O_CREAT was not specified) or create a brand new, separate object with that name.
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
sem_t *sem1, *sem2;
/* Create semaphore with initial value 5 */
sem1 = sem_open("/unlink_demo", O_CREAT | O_RDWR, 0600, 5);
if (sem1 == SEM_FAILED) { perror("sem_open"); exit(1); }
/* Unlink the name — sem1 still usable */
sem_unlink("/unlink_demo");
printf("Unlinked. Old descriptor still valid.\n");
/* Open again with O_CREAT — this is a NEW semaphore, value = 1 */
sem2 = sem_open("/unlink_demo", O_CREAT | O_RDWR, 0600, 1);
if (sem2 == SEM_FAILED) { perror("sem_open 2"); exit(1); }
printf("Created a NEW semaphore with the same name.\n");
int val1, val2;
sem_getvalue(sem1, &val1);
sem_getvalue(sem2, &val2);
printf("sem1 value: %d (original, should be 5)\n", val1);
printf("sem2 value: %d (new, should be 1)\n", val2);
sem_close(sem1); /* Original object destroyed here (refcount = 0) */
sem_close(sem2);
sem_unlink("/unlink_demo");
return 0;
}
gcc -o unlink_demo unlink_demo.c -lrt -lpthread
./unlink_demo
POSIX IPC objects have kernel persistence. This means:
- An object survives even after the process that created it exits
- The object exists until it is explicitly unlinked or the system is shut down
keeps object
process
This is different from process-level persistence (anonymous pipes, which disappear when the creating process exits) but less than filesystem persistence (regular files survive reboots).
| Persistence Type | Survives process exit? | Survives reboot? | Examples |
|---|---|---|---|
| Process | ✘ No | ✘ No | Pipes, unnamed semaphores |
| Kernel | ✔ Yes | ✘ No | POSIX IPC, System V IPC |
| Filesystem | ✔ Yes | ✔ Yes | Regular files, FIFOs |
A practical consequence: if your program crashes or exits without calling unlink, the IPC object remains in the kernel. The next time the program starts, it may find a leftover object. Always handle cleanup with error handling or signal handlers.
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
/* Signal handler to clean up on Ctrl+C */
static void cleanup_handler(int sig)
{
printf("\nCaught signal %d, cleaning up...\n", sig);
mq_unlink("/persistent_demo");
exit(0);
}
int main(void)
{
mqd_t mqd;
/* Register cleanup for SIGINT (Ctrl+C) and SIGTERM */
signal(SIGINT, cleanup_handler);
signal(SIGTERM, cleanup_handler);
/*
* Try to create with O_EXCL — detect leftover from previous crash.
* If EEXIST: the previous run did not clean up.
*/
mqd = mq_open("/persistent_demo", O_CREAT | O_EXCL | O_RDWR, 0644, NULL);
if (mqd == (mqd_t)-1) {
if (errno == EEXIST) {
fprintf(stderr, "Warning: leftover queue from previous run. Removing it.\n");
mq_unlink("/persistent_demo");
mqd = mq_open("/persistent_demo", O_CREAT | O_RDWR, 0644, NULL);
}
if (mqd == (mqd_t)-1) { perror("mq_open"); exit(1); }
}
printf("Queue created. Send SIGINT to test cleanup.\n");
printf("Without cleanup, queue survives this process.\n");
/* Normal exit path — clean up properly */
mq_close(mqd);
mq_unlink("/persistent_demo");
printf("Cleaned up properly.\n");
return 0;
}
gcc -o persist_demo persist_demo.c -lrt
./persist_demo
Unlike System V IPC (which has ipcs and ipcrm), POSIX IPC has no standard CLI commands. However, on Linux, POSIX IPC objects are stored in virtual filesystems, so standard ls and rm commands work.
| IPC Type | Virtual Filesystem Mount Point | Filesystem Type |
|---|---|---|
| Message Queues | /dev/mqueue/ | mqueue |
| Semaphores | /dev/shm/ | tmpfs |
| Shared Memory | /dev/shm/ | tmpfs |
# Create a message queue using the C program, then list it:
ls -la /dev/mqueue/
# Output shows queue objects like:
# -rwxr--r-- 1 ravi ravi 80 Jun 10 10:00 my_queue
# View queue attributes (Linux specific — cat shows queue info):
cat /dev/mqueue/my_queue
# Output shows: QSIZE, NOTIFY, SIGNO, NOTIFY_PID
# Remove a message queue from the shell:
rm /dev/mqueue/my_queue
# List shared memory objects:
ls -la /dev/shm/
# Output:
# -rw------- 1 ravi ravi 4096 Jun 10 10:00 shared_buffer
# Remove a shared memory object from the shell:
rm /dev/shm/shared_buffer
# Semaphores on Linux appear in /dev/shm/ with "sem." prefix:
ls /dev/shm/
# sem.app_semaphore
rm /dev/shm/sem.app_semaphore
4.1 The Sticky Bit on IPC Directories
The directories holding POSIX IPC objects (like /dev/shm) have the sticky bit set. This is the restricted deletion flag. It means:
- An unprivileged process can only
rman IPC object that it owns - A privileged process (root) can remove any object
# Check the sticky bit on /dev/shm:
ls -ld /dev/shm
# Output: drwxrwxrwt 2 root root 60 Jun 10 10:00 /dev/shm
# ^
# 't' = sticky bit set
# Check /dev/mqueue:
ls -ld /dev/mqueue
# Output: drwxrwxrwt 2 root root 0 Jun 10 09:00 /dev/mqueue
4.2 Mounting the mqueue Filesystem Manually
On some systems the mqueue filesystem is not mounted by default. You can mount it manually:
# Mount mqueue filesystem (requires root):
mount -t mqueue none /dev/mqueue
# Or add to /etc/fstab for automatic mounting at boot:
# mqueue /dev/mqueue mqueue defaults 0 0
# Verify it is mounted:
mount | grep mqueue
# none on /dev/mqueue type mqueue (rw,relatime)
4.3 Practical Cleanup Script
If your program crashes and leaves orphaned IPC objects, use this shell script to clean them up:
#!/bin/bash
# cleanup_ipc.sh — remove all POSIX IPC objects owned by current user
echo "=== Cleaning up POSIX IPC objects ==="
# Remove message queues
for f in /dev/mqueue/*; do
if [ -O "$f" ]; then
echo "Removing MQ: $f"
rm "$f"
fi
done
# Remove shared memory and semaphores
for f in /dev/shm/*; do
if [ -O "$f" ]; then
echo "Removing SHM/SEM: $f"
rm "$f"
fi
done
echo "Done."
chmod +x cleanup_ipc.sh
./cleanup_ipc.sh
The following example shows a complete producer-consumer pattern using a POSIX message queue, with proper creation, use, and cleanup.
/* producer.c — creates queue and sends a message */
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define QUEUE_NAME "/lifecycle_demo"
#define MSG_SIZE 256
#define MAX_MSGS 10
int main(void)
{
mqd_t mqd;
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = MAX_MSGS;
attr.mq_msgsize = MSG_SIZE;
attr.mq_curmsgs = 0;
/* Create the queue — only producer creates it */
mqd = mq_open(QUEUE_NAME, O_CREAT | O_EXCL | O_WRONLY, 0644, &attr);
if (mqd == (mqd_t)-1) {
perror("producer mq_open");
exit(EXIT_FAILURE);
}
const char *msg = "Data from producer process";
if (mq_send(mqd, msg, strlen(msg) + 1, 1) == -1) {
perror("mq_send");
mq_close(mqd);
mq_unlink(QUEUE_NAME);
exit(EXIT_FAILURE);
}
printf("Producer: sent '%s'\n", msg);
mq_close(mqd);
/* Producer does NOT unlink — consumer will do that */
printf("Producer: done, queue persists for consumer.\n");
return 0;
}
/* consumer.c — opens existing queue, reads, then destroys */
#include <mqueue.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define QUEUE_NAME "/lifecycle_demo"
#define MSG_SIZE 256
int main(void)
{
mqd_t mqd;
char buf[MSG_SIZE];
unsigned int prio;
/* Open existing queue — do NOT create */
mqd = mq_open(QUEUE_NAME, O_RDONLY);
if (mqd == (mqd_t)-1) {
perror("consumer mq_open");
fprintf(stderr, "Has the producer run yet?\n");
exit(EXIT_FAILURE);
}
ssize_t bytes = mq_receive(mqd, buf, MSG_SIZE, &prio);
if (bytes == -1) {
perror("mq_receive");
mq_close(mqd);
exit(EXIT_FAILURE);
}
printf("Consumer: received '%s' (priority %u)\n", buf, prio);
/* Consumer is responsible for final cleanup */
mq_close(mqd);
mq_unlink(QUEUE_NAME);
printf("Consumer: queue unlinked and destroyed.\n");
return 0;
}
# Run producer first, then consumer:
gcc -o producer producer.c -lrt
gcc -o consumer consumer.c -lrt
./producer
# Check it's there:
ls /dev/mqueue/
./consumer
# Check it's gone:
ls /dev/mqueue/
The kernel counts how many processes have the IPC object open. The object is only destroyed when this count reaches zero — even if the name was already unlinked.
Yes. Unlink only removes the name. Process B’s open descriptor is still valid and fully functional. The object is destroyed only when Process B also closes it (reference count = 0).
Kernel persistence means the object survives process termination but is lost on system reboot. Filesystem persistence (regular files) survives reboots. POSIX IPC objects have kernel persistence.
In the virtual mqueue filesystem, typically mounted at /dev/mqueue/. They can be listed and removed using ls and rm.
The sticky bit (restricted deletion flag) prevents unprivileged users from deleting IPC objects they don’t own. Only the owning user or root can delete the objects.
If a program crashes without calling unlink, the IPC object persists in the kernel. The next run of the program may find a stale object from the previous crash. Good practice is to use O_CREAT | O_EXCL to detect this, and register signal handlers to clean up on crash.
There are no standard POSIX commands for this. On Linux, you use ls /dev/mqueue/ and ls /dev/shm/ to list objects, and rm to remove them. SUSv3 does not specify standard commands for this task.
Yes. After unlink the name is freed. A new mq_open() with O_CREAT will create a completely new, independent object even though an old object with the same name may still exist (referenced by old open descriptors).
