Creates IPC objects
Accesses objects
Stale ID error
How IPC Works in Client-Server Design
In a typical client-server application using System V IPC:
- The server creates the IPC objects (message queue, semaphore, or shared memory) using a get call with
IPC_CREAT. - The client opens the same objects using a get call with the same key but without
IPC_CREAT.
This pattern works smoothly when both server and client start up normally. But what happens when the server crashes and restarts?
Imagine a server has been running for a while. It has a message queue with ID 12345. Clients have been sending and receiving messages. Now the server crashes (or is deliberately restarted).
The clean pattern for a restarting server:
- Try to create the IPC object with
IPC_CREAT | IPC_EXCL - If it fails with
EEXIST→ the old server left it behind - Fetch the old ID with a plain get call, then delete it with
IPC_RMID - Loop back and try creating again
This is exactly what Listing 45-1 from TLPI demonstrates. The key insight is that after the old queue is deleted and a new one created, the new queue will have a different identifier (due to the sequence-number algorithm). This invalidates any stale IDs held by clients of the old server.
/* svmsg_demo_server.c — demonstrates safe server-side IPC creation */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
/* This file must exist. Create it with: touch /tmp/sv_demo_key */
#define KEY_FILE "/tmp/sv_demo_key"
int main(int argc, char *argv[]) {
int msqid;
key_t key;
const int MQ_PERMS = S_IRUSR | S_IWUSR | S_IWGRP; /* rw--w---- */
/* Generate a key from the agreed-upon file */
key = ftok(KEY_FILE, 1);
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
printf("Using key = 0x%x\n", (unsigned int)key);
/* Try to create the queue exclusively.
Loop handles the case where an old queue exists. */
while ((msqid = msgget(key, IPC_CREAT | IPC_EXCL | MQ_PERMS)) == -1) {
if (errno == EEXIST) {
/* A queue with this key already exists (from a previous
server run). Get its ID and remove it. */
int old_id = msgget(key, 0);
if (old_id == -1) {
perror("msgget (retrieve old queue)");
exit(EXIT_FAILURE);
}
if (msgctl(old_id, IPC_RMID, NULL) == -1) {
perror("msgctl IPC_RMID (delete old queue)");
exit(EXIT_FAILURE);
}
printf("Removed stale queue (old ID=%d). Retrying...\n", old_id);
/* Loop continues → tries IPC_CREAT | IPC_EXCL again */
} else {
/* Some other error — give up */
perror("msgget");
exit(EXIT_FAILURE);
}
}
/* At this point: new queue created successfully */
printf("New message queue created. ID = %d\n", msqid);
printf("This new ID differs from any old ID clients may have.\n");
printf("Old clients will get EIDRM when they try to use their stale IDs.\n");
/* ... server continues to serve clients ... */
/* Cleanup when server exits normally */
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl IPC_RMID");
}
return 0;
}
When a server restarts and recreates the IPC object with a new ID, any client holding the old ID will receive EIDRM (“identifier removed”) when it tries to use it. A well-written client handles this gracefully:
/* client_demo.c — demonstrates handling of stale IPC ID */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define KEY_FILE "/tmp/sv_demo_key"
struct mymsg { long mtype; char mtext[256]; };
int get_server_queue_id(void) {
key_t key = ftok(KEY_FILE, 1);
if (key == -1) { perror("ftok"); return -1; }
int id = msgget(key, 0); /* open existing queue, no IPC_CREAT */
if (id == -1) { perror("msgget"); return -1; }
return id;
}
int main(void) {
int msqid;
struct mymsg msg;
/* Initial connection to server's queue */
msqid = get_server_queue_id();
if (msqid == -1) exit(EXIT_FAILURE);
printf("Client: Connected to queue ID=%d\n", msqid);
/* Simulate client doing some work, then server restarts
In a real scenario, msqid would now be stale */
/* Try to send a message */
msg.mtype = 1;
strncpy(msg.mtext, "Request from client", sizeof(msg.mtext));
if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
if (errno == EIDRM) {
/* Identifier was removed — server restarted */
printf("Client: Queue was removed (server restarted?). Reconnecting...\n");
/* Reconnect: fetch the new queue ID */
msqid = get_server_queue_id();
if (msqid == -1) {
printf("Client: Cannot reconnect. Server may be down.\n");
exit(EXIT_FAILURE);
}
printf("Client: Reconnected. New queue ID=%d\n", msqid);
/* Retry the send */
if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd (retry)");
exit(EXIT_FAILURE);
}
} else {
perror("msgsnd");
exit(EXIT_FAILURE);
}
}
printf("Client: Message sent successfully.\n");
return 0;
}
EINVAL— the corresponding entries[] slot is empty (never existed or was freed)EIDRM— the slot exists but the sequence number doesn’t match (old object was deleted, slot reused)
Unlike message queues and semaphores, a shared memory segment is only deleted after all processes have detached. This means the above server-restart pattern works differently for shared memory. Typically, semaphores are used alongside shared memory — and since semaphores are immediately deleted on IPC_RMID, clients will notice the restart when they try to access the deleted semaphore:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define SHM_KEY 0x1234
#define SEM_KEY 0x5678
int main(void) {
int shmid, semid;
/* Server creates both shared memory AND a companion semaphore */
shmid = shmget(SHM_KEY, 4096, IPC_CREAT | IPC_EXCL | 0600);
semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0600);
if (shmid == -1 || semid == -1) {
perror("shmget/semget");
exit(EXIT_FAILURE);
}
printf("Created: shmid=%d, semid=%d\n", shmid, semid);
/* On server restart:
- semctl(semid, 0, IPC_RMID) → immediate delete → clients get EIDRM
- shmctl(shmid, IPC_RMID, NULL) → deferred until all detach */
/* Delete semaphore: immediate */
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("semctl IPC_RMID");
}
printf("Semaphore immediately deleted. Clients will detect restart.\n");
/* Mark shared memory for deletion (actual removal deferred) */
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID");
}
printf("Shared memory marked for deletion (deferred until detached).\n");
return 0;
}
An important property of System V IPC objects: they are connectionless. The kernel does not keep a record of which processes have an IPC object “open.”
This is fundamentally different from sockets or pipes, where the kernel knows the connected endpoints. With System V IPC:
- There is no concept of “connected” clients
- There is no reference count of “users” (unlike file descriptors)
- There is no automatic cleanup when all users exit (unlike pipes)
- The server cannot know how many clients currently have the object open
