google-site-verification: google61fe8ba583a51912.html
Bridging Processes: A Complete Guide to Interprocess Communication in C

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

MethodSpeedComplexityUse Case
PipesMediumLowParent-child communication
FIFOsMediumLowUnrelated process communication
Message QueuesMediumMediumStructured message passing
Shared MemoryHighHighFast data sharing
SemaphoresMediumMediumSynchronization
SignalsLowLowSimple notifications
SocketsMediumHighNetwork 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 MethodBest ForPerformanceComplexity
PipesParent-child communicationMediumLow
FIFOsUnrelated processesMediumLow
Message QueuesStructured messagesMediumMedium
Shared MemoryLarge data, fast sharingHighHigh
SemaphoresSynchronizationMediumMedium
SignalsSimple notificationsLowLow
SocketsNetwork & local IPCMediumMedium

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.

Complete C Programming Guide + Compilers Collection


1. C srand() Function – Understanding Seed Initialization

https://macronepal.com/understanding-the-c-srand-function
Explains how srand() initializes the pseudo-random number generator in C by setting a seed value. Using the same seed produces the same sequence, while time(NULL) gives different results each run.


2. C rand() Function Mechanics and Limitations

https://macronepal.com/c-rand-function-mechanics-and-limitations
Explains how rand() generates pseudo-random numbers between 0 and RAND_MAX, its deterministic nature, and limitations for security use cases.


3. C log() Function

https://macronepal.com/c-log-function-2
Covers natural logarithm calculation using <math.h> and its applications.


4. Mastering Date and Time in C

https://macronepal.com/mastering-date-and-time-in-c
Explains <time.h> functions like time(), clock(), difftime(), and struct tm.


5. Mastering time_t Type in C

https://macronepal.com/mastering-the-c-time_t-type-for-time-management
Explains time representation as seconds since Unix epoch and conversion functions.


6. C exp() Function

https://macronepal.com/c-exp-function-mechanics-and-implementation
Explains exponential function exp(x) and its scientific applications.


7. C log() Function (Alternate Guide)

https://macronepal.com/c-log-function
Comparison of log() and log10() with usage examples.


8. C log10() Function

https://macronepal.com/mastering-the-log10-function-in-c
Explains base-10 logarithm for engineering and scientific applications.


9. C tan() Function

https://macronepal.com/understanding-the-c-tan-function
Explains tangent function and radian-based calculations.


10. Random Numbers in C (Secure vs Predictable)

https://macronepal.com/mastering-c-random-numbers-for-secure-and-predictable-applications
Explains difference between rand() and secure randomness methods.


11. Free Online C Compiler

https://macronepal.com/free-online-c-code-compiler-2
Browser-based compiler for testing C programs instantly.


C Functions, Arguments, Parameters & Flow

Mastering Functions in C – Complete Guide

https://macronepal.com/c/mastering-functions-in-c-a-complete-guide/
Covers function structure, modular programming, and real-world usage.


Function Arguments in C

https://macronepal.com/c-function-arguments/
Explains how arguments are passed and used in function calls.


Function Parameters in C

https://macronepal.com/c-function-parameters/
Explains defining inputs for functions and matching them with arguments.


Function Declarations in C

https://macronepal.com/c-function-declarations-syntax-rules-and-best-practices/
Covers prototypes, syntax rules, and best practices.


Function Calls in C

https://macronepal.com/understanding-function-calls-in-c-syntax-mechanics-and-best-practices/
Explains execution flow and parameter handling during function calls.


Void Functions in C

https://macronepal.com/understanding-void-functions-in-c-syntax-patterns-and-best-practices/
Explains functions that do not return values.


Return Values in C

https://macronepal.com/c-return-values-mechanics-types-and-best-practices/
Explains different return types and how functions return results.


Pass-by-Value in C

https://macronepal.com/aws/understanding-pass-by-value-in-c-mechanics-implications-and-best-practices/
Explains how copies of variables are passed into functions.


Pass-by-Reference in C

https://macronepal.com/c/understanding-pass-by-reference-in-c-pointers-semantics-and-safe-practices/
Explains using pointers to modify original variables.


C strstr() Function

https://macronepal.com/aws/c-strstr-function/
Explains substring search inside strings in C.


C Preprocessor & Macros

https://macronepal.com/mastering-c-variadic-macros-for-flexible-debugging/
https://macronepal.com/mastering-the-stdc-macro-in-c/
https://macronepal.com/c-time-macro-mechanics-and-usage/
https://macronepal.com/understanding-the-c-date-macro/
https://macronepal.com/c-file-type/
https://macronepal.com/mastering-c-line-macro-for-debugging-and-diagnostics/
https://macronepal.com/mastering-predefined-macros-in-c/
https://macronepal.com/c-error-directive-mechanics-and-usage/
https://macronepal.com/understanding-the-c-pragma-directive/
https://macronepal.com/c-include-directive/


C Structures, Memory, Scope & Linkage

https://macronepal.com/mastering-structures-in-c/
https://macronepal.com/c-structure-declaration-mechanics-and-usage/
https://macronepal.com/c-structure-initialization-mechanics-and-best-practices/
https://macronepal.com/mastering-c-structure-member-access-for-reliable-data-handling/
https://macronepal.com/c-nested-structures/
https://macronepal.com/mastering-arrays-of-structures-in-c/
https://macronepal.com/c-structure-pointers-mechanics-and-implementation/
https://macronepal.com/understanding-c-structure-parameter-passing-mechanics/
https://macronepal.com/mastering-c-returning-structures-for-efficient-data-flow/
https://macronepal.com/c-self-referential-structures/
https://macronepal.com/mastering-structure-alignment-in-c/
https://macronepal.com/c-structure-padding-mechanics-and-optimization/
https://macronepal.com/understanding-c-flexible-array-members-mechanics-and-usage/
https://macronepal.com/mastering-c-anonymous-structures-for-flattened-data-layouts/
https://macronepal.com/c-unions/
https://macronepal.com/mastering-c-name-mangling-and-symbol-decoration/
https://macronepal.com/c-no-linkage-mechanics-and-scope-isolation/
https://macronepal.com/understanding-c-internal-linkage-mechanics-and-architecture/


C Scope, Storage Classes & Typedef

https://macronepal.com/mastering-function-prototype-scope-in-c/
https://macronepal.com/c-function-scope-mechanics-and-visibility/
https://macronepal.com/understanding-c-file-scope-mechanics-and-architecture/
https://macronepal.com/mastering-c-scope-rules-for-predictable-name-resolution/
https://macronepal.com/c-scope-rules/
https://macronepal.com/mastering-c-register-storage-class-for-historical-context-and-modern-alternatives/
https://macronepal.com/mastering-_thread_local-in-c/
https://macronepal.com/c-extern-storage-class-mechanics-and-usage/
https://macronepal.com/understanding-the-c-static-storage-class-mechanics-and-usage/
https://macronepal.com/c-auto-storage-class/
https://macronepal.com/c-typedef-with-pointers/


Extra Articles

https://macronepal.com/13757-2/
https://macronepal.com/13748-2/
https://macronepal.com/13747-2/
https://macronepal.com/13746-2/
https://macronepal.com/13745-2/
https://macronepal.com/13708-2/
https://macronepal.com/13707-2/
https://macronepal.com/13702-2/


Online Compilers

https://macronepal.com/free-html-online-code-compiler/
https://macronepal.com/free-online-python-code-compiler/
https://macronepal.com/free-online-python2-code-compiler/
https://macronepal.com/free-online-java-code-compiler/
https://macronepal.com/free-online-javascript-code-compiler/
https://macronepal.com/free-online-node-js-code-compiler/
https://macronepal.com/free-online-c-code-compiler/
https://macronepal.com/free-online-c-code-compiler-2/
https://macronepal.com/free-online-c-code-compiler-3/
https://macronepal.com/free-online-php-code-compiler/
https://macronepal.com/free-online-ruby-code-compiler/
https://macronepal.com/free-online-perl-code-compiler/
https://macronepal.com/free-online-lua-code-compiler/
https://macronepal.com/free-online-tcl-code-compiler/
https://macronepal.com/free-online-groovy-code-compiler/
https://macronepal.com/free-online-j-shell-code-compiler/
https://macronepal.com/free-online-haskell-code-compiler/
https://macronepal.com/free-online-scala-code-compiler/
https://macronepal.com/free-online-common-lisp-code-compiler/
https://macronepal.com/free-online-d-code-compiler/
https://macronepal.com/free-online-ada-code-compiler/
https://macronepal.com/free-erlang-code-compiler/
https://macronepal.com/free-online-assembly-code-compiler/

Complete Guide to Advanced C Programming, Systems, GPU, Kernel & Parallel Computing

https://macronepal.com/bash/the-building-blocks-of-c-a-complete-guide-to-variables-and-data-types/
Explains variables and data types in C, including how different types define memory usage, value range, and program data handling.

https://macronepal.com/bash/hello-world-in-c-the-program-that-started-it-all/
Explains the basic “Hello World” program in C, covering program structure, main function, and output printing.

https://macronepal.com/bash/advanced-i-o-operations-in-c-mastering-input-output-for-high-performance-applications/
Explains advanced input/output operations in C, including efficient data handling and file-based I/O for high-performance applications.

https://macronepal.com/bash/harnessing-massive-parallelism-a-complete-guide-to-gpu-programming-in-c/
Explains GPU programming in C for massive parallel processing, enabling faster computation for complex workloads.

https://macronepal.com/bash/understanding-the-unseen-a-complete-guide-to-reverse-engineering-c-programs/
Explains reverse engineering of C programs by analyzing compiled binaries to understand program behavior and logic.

https://macronepal.com/bash/protecting-intellectual-property-a-complete-guide-to-code-obfuscation-in-c/
Explains code obfuscation techniques in C to protect source code and make reverse engineering difficult.

https://macronepal.com/bash/kernel-programming-in-c-a-comprehensive-guide-to-linux-kernel-development/
Explains Linux kernel programming in C, focusing on low-level system operations and kernel module development.

https://macronepal.com/bash/kernel-programming-a-complete-guide-to-writing-device-drivers-in-c/
Explains how to write device drivers in C for Linux, enabling communication between hardware and the operating system.

https://macronepal.com/bash/parallel-data-processing-a-complete-guide-to-simd-programming-in-c/
Explains SIMD programming in C for processing multiple data points in parallel using a single instruction.

https://macronepal.com/bash/harnessing-multi-core-power-a-complete-guide-to-parallel-programming-in-c/
Explains multi-core parallel programming in C by distributing tasks across threads to improve performance and efficiency.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper