Interprocess Communication (IPC) is the foundation of modern operating systems, enabling processes to exchange data, synchronize actions, and coordinate work. From simple pipes to complex shared memory, understanding IPC mechanisms is essential for building robust, scalable systems in C. This comprehensive guide explores every major IPC method with practical implementations, performance considerations, and real-world patterns.
Why IPC Matters
IPC enables:
- Process Collaboration: Multiple processes working together on complex tasks
- Client-Server Architecture: Separating concerns across process boundaries
- Resource Sharing: Efficiently sharing data without duplication
- Process Isolation: Maintaining security while enabling communication
- Distributed Computing: Building scalable systems across cores and machines
IPC Methods Overview
| Method | Speed | Complexity | Use Case |
|---|---|---|---|
| Pipes | Medium | Low | Parent-child communication |
| FIFOs | Medium | Low | Unrelated process communication |
| Message Queues | Medium | Medium | Structured message passing |
| Shared Memory | High | High | Fast data sharing |
| Semaphores | Medium | Medium | Synchronization |
| Signals | Low | Low | Simple notifications |
| Sockets | Medium | High | Network and local communication |
Part 1: Pipes
Anonymous Pipes
Anonymous pipes provide unidirectional communication between related processes (typically parent-child).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
// Basic pipe example
void basic_pipe() {
int pipefd[2];
char buffer[256];
pid_t pid;
// Create pipe
if (pipe(pipefd) == -1) {
perror("pipe");
exit(1);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
if (pid == 0) {
// Child process: write to pipe
close(pipefd[0]); // Close read end
const char *msg = "Hello from child!";
write(pipefd[1], msg, strlen(msg) + 1);
close(pipefd[1]);
exit(0);
} else {
// Parent process: read from pipe
close(pipefd[1]); // Close write end
read(pipefd[0], buffer, sizeof(buffer));
printf("Parent received: %s\n", buffer);
close(pipefd[0]);
wait(NULL);
}
}
// Bidirectional pipe (two pipes)
void bidirectional_pipe() {
int pipe_parent[2]; // Parent -> Child
int pipe_child[2]; // Child -> Parent
pid_t pid;
char buffer[256];
// Create both pipes
if (pipe(pipe_parent) == -1 || pipe(pipe_child) == -1) {
perror("pipe");
exit(1);
}
pid = fork();
if (pid == 0) {
// Child process
close(pipe_parent[1]); // Close write end of parent pipe
close(pipe_child[0]); // Close read end of child pipe
// Read from parent
read(pipe_parent[0], buffer, sizeof(buffer));
printf("Child received: %s\n", buffer);
// Send response
const char *response = "Hello back from child!";
write(pipe_child[1], response, strlen(response) + 1);
close(pipe_parent[0]);
close(pipe_child[1]);
exit(0);
} else {
// Parent process
close(pipe_parent[0]); // Close read end of parent pipe
close(pipe_child[1]); // Close write end of child pipe
// Send message to child
const char *msg = "Hello from parent!";
write(pipe_parent[1], msg, strlen(msg) + 1);
// Read response
read(pipe_child[0], buffer, sizeof(buffer));
printf("Parent received: %s\n", buffer);
close(pipe_parent[1]);
close(pipe_child[0]);
wait(NULL);
}
}
// Pipe with multiple messages
void multi_message_pipe() {
int pipefd[2];
pid_t pid;
if (pipe(pipefd) == -1) {
perror("pipe");
exit(1);
}
pid = fork();
if (pid == 0) {
close(pipefd[0]);
// Send multiple messages
for (int i = 0; i < 10; i++) {
char msg[32];
snprintf(msg, sizeof(msg), "Message %d", i);
write(pipefd[1], msg, strlen(msg) + 1);
sleep(1);
}
close(pipefd[1]);
exit(0);
} else {
close(pipefd[1]);
char buffer[256];
ssize_t n;
// Read messages until pipe closed
while ((n = read(pipefd[0], buffer, sizeof(buffer))) > 0) {
printf("Received: %s\n", buffer);
}
close(pipefd[0]);
wait(NULL);
}
}
// Pipe with non-blocking I/O
void nonblocking_pipe() {
int pipefd[2];
pid_t pid;
char buffer[256];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(1);
}
// Set non-blocking on read end
int flags = fcntl(pipefd[0], F_GETFL, 0);
fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
pid = fork();
if (pid == 0) {
close(pipefd[0]);
sleep(2); // Delay writing
const char *msg = "Delayed message";
write(pipefd[1], msg, strlen(msg) + 1);
close(pipefd[1]);
exit(0);
} else {
close(pipefd[1]);
// Try to read multiple times (non-blocking)
for (int i = 0; i < 5; i++) {
ssize_t n = read(pipefd[0], buffer, sizeof(buffer));
if (n > 0) {
printf("Received: %s\n", buffer);
break;
} else if (n == -1 && errno == EAGAIN) {
printf("No data yet...\n");
sleep(1);
} else {
break;
}
}
close(pipefd[0]);
wait(NULL);
}
}
Part 2: FIFOs (Named Pipes)
FIFOs allow communication between unrelated processes through a filesystem entry.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#define FIFO_NAME "/tmp/my_fifo"
// FIFO writer
void fifo_writer() {
int fd;
const char *messages[] = {
"Hello",
"World",
"IPC",
"FIFO",
NULL
};
// Create FIFO
umask(0);
if (mkfifo(FIFO_NAME, 0666) == -1 && errno != EEXIST) {
perror("mkfifo");
exit(1);
}
printf("FIFO writer: opening FIFO...\n");
fd = open(FIFO_NAME, O_WRONLY);
if (fd == -1) {
perror("open");
exit(1);
}
printf("FIFO writer: sending messages...\n");
for (int i = 0; messages[i] != NULL; i++) {
write(fd, messages[i], strlen(messages[i]) + 1);
printf("Sent: %s\n", messages[i]);
sleep(1);
}
close(fd);
unlink(FIFO_NAME);
printf("FIFO writer: done\n");
}
// FIFO reader
void fifo_reader() {
int fd;
char buffer[256];
// Open FIFO for reading (blocks until writer opens)
printf("FIFO reader: waiting for writer...\n");
fd = open(FIFO_NAME, O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
printf("FIFO reader: reading messages...\n");
ssize_t n;
while ((n = read(fd, buffer, sizeof(buffer))) > 0) {
printf("Received: %s\n", buffer);
}
close(fd);
printf("FIFO reader: done\n");
}
// FIFO with multiple readers (using O_EXCL)
void fifo_multi_reader() {
int fd;
char buffer[256];
int reader_id;
// Try to create FIFO exclusively
if (mkfifo(FIFO_NAME, 0666) == -1) {
if (errno == EEXIST) {
// FIFO exists, we're not the first reader
reader_id = 2;
} else {
perror("mkfifo");
exit(1);
}
} else {
reader_id = 1;
}
fd = open(FIFO_NAME, O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
printf("Reader %d started\n", reader_id);
ssize_t n;
while ((n = read(fd, buffer, sizeof(buffer))) > 0) {
printf("Reader %d received: %s\n", reader_id, buffer);
}
close(fd);
if (reader_id == 1) {
unlink(FIFO_NAME); // Last reader cleans up
}
}
// FIFO with timeout (using select)
void fifo_with_timeout() {
int fd;
char buffer[256];
fd_set readfds;
struct timeval tv;
mkfifo(FIFO_NAME, 0666);
fd = open(FIFO_NAME, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
perror("open");
exit(1);
}
printf("Waiting for data (5 second timeout)...\n");
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
tv.tv_sec = 5;
tv.tv_usec = 0;
int ret = select(fd + 1, &readfds, NULL, NULL, &tv);
if (ret == -1) {
perror("select");
} else if (ret == 0) {
printf("Timeout: no data received\n");
} else {
read(fd, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
}
close(fd);
unlink(FIFO_NAME);
}
Part 3: System V Message Queues
Message queues provide structured, priority-based message passing.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#define MSG_KEY 12345
#define MSG_SIZE 256
// Message structure
struct msgbuf {
long mtype; // Message type (> 0)
char mtext[MSG_SIZE]; // Message data
};
// Create message queue
int create_msg_queue() {
int msqid;
msqid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (msqid == -1) {
perror("msgget");
exit(1);
}
printf("Message queue created: %d\n", msqid);
return msqid;
}
// Send message
void send_message(int msqid, long type, const char *text) {
struct msgbuf msg;
msg.mtype = type;
strncpy(msg.mtext, text, MSG_SIZE - 1);
msg.mtext[MSG_SIZE - 1] = '\0';
if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
perror("msgsnd");
exit(1);
}
printf("Sent message type %ld: %s\n", type, msg.mtext);
}
// Receive message
void receive_message(int msqid, long type, int wait) {
struct msgbuf msg;
ssize_t size;
int flags = wait ? 0 : IPC_NOWAIT;
size = msgrcv(msqid, &msg, MSG_SIZE, type, flags);
if (size == -1) {
if (errno == ENOMSG) {
printf("No message of type %ld available\n", type);
} else {
perror("msgrcv");
}
} else {
printf("Received message type %ld: %s\n", msg.mtype, msg.mtext);
}
}
// Peek at message (without removing)
void peek_message(int msqid, long type) {
struct msgbuf msg;
ssize_t size;
size = msgrcv(msqid, &msg, MSG_SIZE, type, IPC_NOWAIT | MSG_NOERROR);
if (size != -1) {
printf("Peeked: %s\n", msg.mtext);
// Message is still in queue
}
}
// Get queue information
void queue_info(int msqid) {
struct msqid_ds buf;
if (msgctl(msqid, IPC_STAT, &buf) == -1) {
perror("msgctl");
return;
}
printf("Queue info:\n");
printf(" Number of messages: %ld\n", buf.msg_qnum);
printf(" Bytes in queue: %ld\n", buf.msg_cbytes);
printf(" Max bytes: %ld\n", buf.msg_qbytes);
printf(" Last send time: %ld\n", buf.msg_stime);
printf(" Last receive time: %ld\n", buf.msg_rtime);
}
// Delete message queue
void delete_msg_queue(int msqid) {
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
printf("Message queue deleted\n");
}
// Example: Priority-based message processing
void priority_message_demo() {
int msqid = create_msg_queue();
// Send messages with different priorities
send_message(msqid, 3, "Low priority message");
send_message(msqid, 1, "High priority message");
send_message(msqid, 2, "Medium priority message");
send_message(msqid, 1, "Another high priority");
queue_info(msqid);
// Receive messages by priority
printf("\nProcessing by priority:\n");
receive_message(msqid, 1, 0); // All high priority
receive_message(msqid, 1, 0);
receive_message(msqid, 2, 0); // Medium
receive_message(msqid, 3, 0); // Low
delete_msg_queue(msqid);
}
Part 4: System V Shared Memory
Shared memory is the fastest IPC method, allowing multiple processes to access the same memory region.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>
#define SHM_KEY 12346
#define SEM_KEY 12347
#define SHM_SIZE 4096
// Semaphore operations for synchronization
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void sem_init(int semid, int semnum, int value) {
union semun arg;
arg.val = value;
if (semctl(semid, semnum, SETVAL, arg) == -1) {
perror("semctl");
exit(1);
}
}
void sem_wait(int semid, int semnum) {
struct sembuf op = {semnum, -1, 0};
if (semop(semid, &op, 1) == -1) {
perror("semop wait");
exit(1);
}
}
void sem_signal(int semid, int semnum) {
struct sembuf op = {semnum, 1, 0};
if (semop(semid, &op, 1) == -1) {
perror("semop signal");
exit(1);
}
}
// Create shared memory
int create_shared_memory() {
int shmid;
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
printf("Shared memory created: %d\n", shmid);
return shmid;
}
// Attach shared memory
void* attach_shared_memory(int shmid) {
void *shmaddr;
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1) {
perror("shmat");
exit(1);
}
return shmaddr;
}
// Detach shared memory
void detach_shared_memory(void *shmaddr) {
if (shmdt(shmaddr) == -1) {
perror("shmdt");
exit(1);
}
}
// Delete shared memory
void delete_shared_memory(int shmid) {
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
exit(1);
}
printf("Shared memory deleted\n");
}
// Create semaphore set
int create_semaphores() {
int semid;
semid = semget(SEM_KEY, 2, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
// Initialize two semaphores
sem_init(semid, 0, 1); // Mutex for writer
sem_init(semid, 1, 0); // Data available flag
printf("Semaphores created: %d\n", semid);
return semid;
}
// Shared memory writer
void shared_memory_writer() {
int shmid = create_shared_memory();
int semid = create_semaphores();
char *shmaddr = attach_shared_memory(shmid);
int counter = 0;
printf("Writer started. Press Ctrl+C to stop.\n");
while (1) {
// Wait for mutex
sem_wait(semid, 0);
// Write to shared memory
snprintf(shmaddr, SHM_SIZE, "Message %d from writer (PID: %d)",
++counter, getpid());
printf("Writer wrote: %s\n", shmaddr);
// Signal data available
sem_signal(semid, 1);
sem_signal(semid, 0); // Release mutex
sleep(2);
}
detach_shared_memory(shmaddr);
delete_shared_memory(shmid);
}
// Shared memory reader
void shared_memory_reader() {
int shmid = shmget(SHM_KEY, 0, 0666);
int semid = semget(SEM_KEY, 0, 0666);
if (shmid == -1 || semid == -1) {
perror("shmget/semget");
exit(1);
}
char *shmaddr = attach_shared_memory(shmid);
printf("Reader started. Press Ctrl+C to stop.\n");
while (1) {
// Wait for data
sem_wait(semid, 1);
// Read from shared memory
printf("Reader read: %s\n", shmaddr);
// No need to signal - writer will signal next
}
detach_shared_memory(shmaddr);
}
// Circular buffer in shared memory
typedef struct {
int buffer[100];
int head;
int tail;
int count;
int size;
} CircularBuffer;
void shared_circular_buffer() {
int shmid = shmget(SHM_KEY, sizeof(CircularBuffer), IPC_CREAT | 0666);
CircularBuffer *cb = attach_shared_memory(shmid);
int semid = semget(SEM_KEY, 2, IPC_CREAT | 0666);
// Initialize
if (shmid != -1 && semid != -1) {
cb->head = 0;
cb->tail = 0;
cb->count = 0;
cb->size = 100;
sem_init(semid, 0, 1); // Mutex
sem_init(semid, 1, 0); // Not empty
}
// Use circular buffer with semaphores...
}
Part 5: System V Semaphores
Semaphores are essential for synchronizing access to shared resources.
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#define SEM_KEY 12348
// Semaphore operations
void sem_create(int semid, int semnum, int value) {
union semun arg;
arg.val = value;
if (semctl(semid, semnum, SETVAL, arg) == -1) {
perror("semctl");
exit(1);
}
}
void sem_wait(int semid, int semnum) {
struct sembuf op = {semnum, -1, 0};
if (semop(semid, &op, 1) == -1) {
perror("semop wait");
exit(1);
}
}
void sem_signal(int semid, int semnum) {
struct sembuf op = {semnum, 1, 0};
if (semop(semid, &op, 1) == -1) {
perror("semop signal");
exit(1);
}
}
int sem_get_value(int semid, int semnum) {
return semctl(semid, semnum, GETVAL);
}
// Producer-consumer with semaphores
void producer_consumer_demo() {
int semid = semget(SEM_KEY, 3, IPC_CREAT | 0666);
int buffer[10];
int in = 0, out = 0;
// Initialize semaphores
sem_create(semid, 0, 1); // Mutex
sem_create(semid, 1, 10); // Empty slots
sem_create(semid, 2, 0); // Full slots
pid_t pid = fork();
if (pid == 0) {
// Consumer
for (int i = 0; i < 20; i++) {
sem_wait(semid, 2); // Wait for full
sem_wait(semid, 0); // Wait for mutex
// Consume
int item = buffer[out];
out = (out + 1) % 10;
printf("Consumer: %d\n", item);
sem_signal(semid, 0); // Release mutex
sem_signal(semid, 1); // Signal empty
usleep(100000);
}
} else {
// Producer
for (int i = 0; i < 20; i++) {
sem_wait(semid, 1); // Wait for empty
sem_wait(semid, 0); // Wait for mutex
// Produce
buffer[in] = i;
in = (in + 1) % 10;
printf("Producer: %d\n", i);
sem_signal(semid, 0); // Release mutex
sem_signal(semid, 2); // Signal full
usleep(100000);
}
wait(NULL);
}
semctl(semid, 0, IPC_RMID);
}
// Counting semaphore example (readers-writers problem)
void readers_writers() {
int semid = semget(SEM_KEY, 2, IPC_CREAT | 0666);
int readers = 0;
sem_create(semid, 0, 1); // Mutex for readers count
sem_create(semid, 1, 1); // Writer mutex
// This would run in multiple processes...
}
Part 6: POSIX Shared Memory
POSIX shared memory provides a modern alternative to System V.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <semaphore.h>
#define SHM_NAME "/my_shared_memory"
#define SEM_NAME "/my_semaphore"
// Create POSIX shared memory
int create_posix_shm() {
int fd;
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("shm_open");
exit(1);
}
// Set size
if (ftruncate(fd, 4096) == -1) {
perror("ftruncate");
exit(1);
}
printf("Shared memory created\n");
return fd;
}
// Map shared memory
void* map_shared_memory(int fd) {
void *ptr;
ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(1);
}
return ptr;
}
// Unmap shared memory
void unmap_shared_memory(void *ptr) {
if (munmap(ptr, 4096) == -1) {
perror("munmap");
exit(1);
}
}
// Delete shared memory
void delete_posix_shm() {
if (shm_unlink(SHM_NAME) == -1) {
perror("shm_unlink");
exit(1);
}
printf("Shared memory deleted\n");
}
// Create POSIX semaphore
sem_t* create_posix_sem() {
sem_t *sem;
sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
if (sem == SEM_FAILED) {
perror("sem_open");
exit(1);
}
printf("Semaphore created\n");
return sem;
}
// POSIX shared memory writer
void posix_shm_writer() {
int fd = create_posix_shm();
void *ptr = map_shared_memory(fd);
sem_t *sem = create_posix_sem();
for (int i = 0; i < 10; i++) {
sem_wait(sem);
snprintf((char *)ptr, 4096, "Message %d from writer (PID: %d)",
i, getpid());
printf("Writer wrote: %s\n", (char *)ptr);
sem_post(sem);
sleep(1);
}
unmap_shared_memory(ptr);
close(fd);
sem_close(sem);
delete_posix_shm();
sem_unlink(SEM_NAME);
}
// POSIX shared memory reader
void posix_shm_reader() {
int fd = shm_open(SHM_NAME, O_RDWR, 0666);
if (fd == -1) {
perror("shm_open");
exit(1);
}
void *ptr = map_shared_memory(fd);
sem_t *sem = sem_open(SEM_NAME, 0);
if (sem == SEM_FAILED) {
perror("sem_open");
exit(1);
}
for (int i = 0; i < 10; i++) {
sem_wait(sem);
printf("Reader read: %s\n", (char *)ptr);
sem_post(sem);
sleep(1);
}
unmap_shared_memory(ptr);
close(fd);
sem_close(sem);
}
Part 7: Unix Domain Sockets
Unix domain sockets provide bidirectional communication with file-like permissions.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#define SOCKET_PATH "/tmp/my_socket"
// Unix socket server
void unix_socket_server() {
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[256];
// Create socket
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(1);
}
// Remove existing socket file
unlink(SOCKET_PATH);
// Bind
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(1);
}
// Listen
if (listen(server_fd, 5) == -1) {
perror("listen");
exit(1);
}
printf("Unix socket server waiting for connections...\n");
// Accept connection
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
exit(1);
}
printf("Client connected\n");
// Handle client
ssize_t n;
while ((n = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {
buffer[n] = '\0';
printf("Received: %s\n", buffer);
// Echo back
write(client_fd, buffer, n);
}
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH);
}
// Unix socket client
void unix_socket_client() {
int client_fd;
struct sockaddr_un addr;
char buffer[256];
// Create socket
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket");
exit(1);
}
// Connect
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("connect");
exit(1);
}
printf("Connected to server\n");
// Send messages
for (int i = 0; i < 5; i++) {
snprintf(buffer, sizeof(buffer), "Message %d", i);
write(client_fd, buffer, strlen(buffer) + 1);
// Read response
read(client_fd, buffer, sizeof(buffer));
printf("Response: %s\n", buffer);
sleep(1);
}
close(client_fd);
}
// Datagram Unix socket (connectionless)
void unix_datagram_server() {
int server_fd;
struct sockaddr_un addr;
char buffer[256];
server_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (server_fd == -1) {
perror("socket");
exit(1);
}
unlink(SOCKET_PATH);
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(1);
}
printf("Datagram server waiting...\n");
struct sockaddr_un client_addr;
socklen_t addr_len = sizeof(client_addr);
ssize_t n = recvfrom(server_fd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&client_addr, &addr_len);
if (n > 0) {
buffer[n] = '\0';
printf("Received: %s\n", buffer);
sendto(server_fd, "OK", 3, 0,
(struct sockaddr *)&client_addr, addr_len);
}
close(server_fd);
unlink(SOCKET_PATH);
}
Part 8: Signals for Simple IPC
Signals provide a simple way to send notifications.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
// Custom signal handler with data
typedef struct {
int value;
char message[64];
} SignalData;
volatile sig_atomic_t signal_received = 0;
volatile int signal_value = 0;
void signal_handler(int signum) {
signal_received = 1;
}
// Signal with real-time extensions (queued signals)
void rt_signal_handler(int signum, siginfo_t *info, void *context) {
printf("Signal %d received from PID %d, value: %d\n",
signum, info->si_pid, info->si_value.sival_int);
}
// Send signal with data
void send_signal_with_data(pid_t pid, int value) {
union sigval sv;
sv.sival_int = value;
if (sigqueue(pid, SIGUSR1, sv) == -1) {
perror("sigqueue");
exit(1);
}
printf("Sent signal with value %d to PID %d\n", value, pid);
}
// Signal-based IPC example
void signal_ipc_demo() {
struct sigaction sa;
pid_t pid;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = rt_signal_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
pid = fork();
if (pid == 0) {
// Child: wait for signals
printf("Child PID: %d waiting for signals...\n", getpid());
pause();
printf("Child exiting\n");
exit(0);
} else {
// Parent: send signals
sleep(1);
for (int i = 0; i < 5; i++) {
send_signal_with_data(pid, i * 10);
sleep(1);
}
// Send termination signal
kill(pid, SIGTERM);
wait(NULL);
}
}
Part 9: Complete IPC Framework Example
A complete example demonstrating multiple IPC methods working together.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
// Configuration
#define SHM_SIZE 1024
#define MSG_TYPE 1
#define SHM_KEY 12345
#define MSG_KEY 12346
#define SEM_KEY 12347
// Shared data structure
typedef struct {
int counter;
char data[SHM_SIZE];
int ready;
} SharedData;
// Message structure
struct msgbuf {
long mtype;
int pid;
char text[100];
};
// IPC Manager
typedef struct {
int shmid;
int msqid;
int semid;
SharedData *shm_ptr;
int server_pid;
int client_pid;
} IPCMgr;
// Initialize IPC
IPCMgr* ipc_init() {
IPCMgr *mgr = malloc(sizeof(IPCMgr));
// Shared memory
mgr->shmid = shmget(SHM_KEY, sizeof(SharedData), IPC_CREAT | 0666);
if (mgr->shmid == -1) {
perror("shmget");
free(mgr);
return NULL;
}
mgr->shm_ptr = shmat(mgr->shmid, NULL, 0);
if (mgr->shm_ptr == (void *)-1) {
perror("shmat");
shmctl(mgr->shmid, IPC_RMID, NULL);
free(mgr);
return NULL;
}
// Message queue
mgr->msqid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (mgr->msqid == -1) {
perror("msgget");
shmdt(mgr->shm_ptr);
shmctl(mgr->shmid, IPC_RMID, NULL);
free(mgr);
return NULL;
}
// Semaphore
mgr->semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (mgr->semid == -1) {
perror("semget");
msgctl(mgr->msqid, IPC_RMID, NULL);
shmdt(mgr->shm_ptr);
shmctl(mgr->shmid, IPC_RMID, NULL);
free(mgr);
return NULL;
}
union semun arg;
arg.val = 1;
semctl(mgr->semid, 0, SETVAL, arg);
// Initialize shared data
mgr->shm_ptr->counter = 0;
mgr->shm_ptr->ready = 0;
strcpy(mgr->shm_ptr->data, "");
return mgr;
}
// Cleanup IPC
void ipc_cleanup(IPCMgr *mgr) {
shmdt(mgr->shm_ptr);
shmctl(mgr->shmid, IPC_RMID, NULL);
msgctl(mgr->msqid, IPC_RMID, NULL);
semctl(mgr->semid, 0, IPC_RMID);
free(mgr);
}
// Server function
void ipc_server(IPCMgr *mgr) {
struct msgbuf msg;
printf("Server started (PID: %d)\n", getpid());
while (1) {
// Wait for message from client
if (msgrcv(mgr->msqid, &msg, sizeof(msg) - sizeof(long),
MSG_TYPE, 0) == -1) {
perror("msgrcv");
break;
}
printf("Server received from PID %d: %s\n", msg.pid, msg.text);
// Process request
if (strcmp(msg.text, "increment") == 0) {
semctl(mgr->semid, 0, GETVAL);
semop(mgr->semid, &(struct sembuf){0, -1, 0}, 1);
mgr->shm_ptr->counter++;
semop(mgr->semid, &(struct sembuf){0, 1, 0}, 1);
snprintf(msg.text, sizeof(msg.text), "Counter: %d",
mgr->shm_ptr->counter);
} else if (strcmp(msg.text, "get") == 0) {
snprintf(msg.text, sizeof(msg.text), "Counter: %d",
mgr->shm_ptr->counter);
} else if (strcmp(msg.text, "quit") == 0) {
break;
} else {
strcpy(msg.text, "Unknown command");
}
// Send response
msg.mtype = msg.pid;
msgsnd(mgr->msqid, &msg, sizeof(msg) - sizeof(long), 0);
}
printf("Server shutting down\n");
}
// Client function
void ipc_client(IPCMgr *mgr) {
struct msgbuf msg;
printf("Client started (PID: %d)\n", getpid());
// Send commands
const char *commands[] = {"increment", "increment", "get", "increment", "get", "quit"};
for (int i = 0; i < 6; i++) {
// Send command
msg.mtype = MSG_TYPE;
msg.pid = getpid();
strncpy(msg.text, commands[i], sizeof(msg.text));
if (msgsnd(mgr->msqid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
perror("msgsnd");
break;
}
printf("Client sent: %s\n", commands[i]);
// Wait for response
if (msgrcv(mgr->msqid, &msg, sizeof(msg) - sizeof(long),
getpid(), 0) == -1) {
perror("msgrcv");
break;
}
printf("Client received: %s\n", msg.text);
sleep(1);
}
printf("Client exiting\n");
}
// Main demo
int main() {
IPCMgr *mgr = ipc_init();
if (!mgr) {
fprintf(stderr, "Failed to initialize IPC\n");
return 1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
ipc_cleanup(mgr);
return 1;
}
if (pid == 0) {
// Child: Client
ipc_client(mgr);
exit(0);
} else {
// Parent: Server
ipc_server(mgr);
wait(NULL);
ipc_cleanup(mgr);
}
return 0;
}
Best Practices and Common Pitfalls
1. Error Handling
// Always check return values
int fd = open(path, O_RDWR);
if (fd == -1) {
perror("open");
// Handle error appropriately
}
// Save errno before calling other functions
int saved_errno = errno;
// ... recovery code ...
errno = saved_errno;
2. Resource Cleanup
void cleanup_resources(int *fds, int nfds, void *shm_ptr, int shmid) {
for (int i = 0; i < nfds; i++) {
if (fds[i] >= 0) close(fds[i]);
}
if (shm_ptr) shmdt(shm_ptr);
if (shmid >= 0) shmctl(shmid, IPC_RMID, NULL);
}
3. Synchronization
// Always use proper synchronization for shared data pthread_mutex_lock(&mutex); // Critical section pthread_mutex_unlock(&mutex);
4. Deadlock Prevention
// Always acquire locks in consistent order
void safe_lock(pthread_mutex_t *m1, pthread_mutex_t *m2) {
if (m1 < m2) {
pthread_mutex_lock(m1);
pthread_mutex_lock(m2);
} else {
pthread_mutex_lock(m2);
pthread_mutex_lock(m1);
}
}
Summary
| IPC Method | Best For | Performance | Complexity |
|---|---|---|---|
| Pipes | Parent-child communication | Medium | Low |
| FIFOs | Unrelated processes | Medium | Low |
| Message Queues | Structured messages | Medium | Medium |
| Shared Memory | Large data, fast sharing | High | High |
| Semaphores | Synchronization | Medium | Medium |
| Signals | Simple notifications | Low | Low |
| Sockets | Network & local IPC | Medium | Medium |
Conclusion
Interprocess Communication is a vast and essential topic in systems programming. The choice of IPC mechanism depends on your specific requirements:
- Speed: Shared memory
- Structure: Message queues
- Simplicity: Pipes and FIFOs
- Synchronization: Semaphores
- Networking: Sockets
- Notifications: Signals
Understanding the strengths and limitations of each method allows you to design robust, efficient systems. The key is to choose the right tool for the job, implement proper synchronization, and always clean up resources. With the comprehensive examples and patterns provided, you're well-equipped to implement IPC in your own C projects.