Inter-Process Communication (IPC) is a set of mechanisms that allow processes to communicate and synchronize their actions. IPC is fundamental to modern operating systems and distributed computing. It enables processes to share data, coordinate tasks, and manage resources efficiently.
Pipes are one of the simplest forms of IPC, providing a unidirectional communication channel between related processes (typically parent and child processes). Data flows in a First-In-First-Out (FIFO) manner, making pipes ideal for stream-based data transfer.
Message queues provide a more flexible form of IPC, allowing bidirectional communication between processes. Messages are stored in a queue and can be retrieved in order of priority, making message queues ideal for complex communication patterns.
The following code demonstrates how to create and use an anonymous pipe for communication between a parent and child process.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define BUFFER_SIZE 256
int main() {
int pipe_fd[2];
pid_t pid;
char buffer[BUFFER_SIZE];
if (pipe(pipe_fd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // Child process
close(pipe_fd[1]); // Close write end
// Read from pipe
ssize_t bytes_read = read(pipe_fd[0], buffer, BUFFER_SIZE);
if (bytes_read > 0) {
printf("Child received: %s\n", buffer);
}
close(pipe_fd[0]);
exit(EXIT_SUCCESS);
} else { // Parent process
close(pipe_fd[0]); // Close read end
const char *message = "Hello from parent!";
write(pipe_fd[1], message, strlen(message) + 1);
close(pipe_fd[1]);
wait(NULL); // Wait for child to finish
}
return 0;
}
pipe_example.c
and compile it using a C compiler, such as gcc
:
gcc -o pipe_example pipe_example.c
./pipe_example
Child received: Hello from parent!
Named pipes, also known as FIFOs, allow communication between unrelated processes. The following code demonstrates how to create and use a named pipe.
// fifo_writer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#define FIFO_PATH "/tmp/myfifo"
int main() {
mkfifo(FIFO_PATH, 0666);
printf("Opening FIFO for writing...\n");
int fd = open(FIFO_PATH, O_WRONLY);
const char *message = "Message through FIFO";
write(fd, message, strlen(message) + 1);
close(fd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define FIFO_PATH "/tmp/myfifo"
#define BUFFER_SIZE 256
int main() {
char buffer[BUFFER_SIZE];
printf("Opening FIFO for reading...\n");
int fd = open(FIFO_PATH, O_RDONLY);
read(fd, buffer, BUFFER_SIZE);
printf("Received: %s\n", buffer);
close(fd);
unlink(FIFO_PATH);
return 0;
}
fifo_writer.c
and fifo_reader.c
) and compile them:
gcc -o fifo_writer fifo_writer.c
gcc -o fifo_reader fifo_reader.c
./fifo_writer
./fifo_reader
Received: Message through FIFO
The following code demonstrates how to create and use a POSIX message queue for communication between processes.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#define QUEUE_NAME "/test_queue"
#define MAX_MSG_SIZE 256
#define MSG_PRIO 1
int main() {
mqd_t mq;
struct mq_attr attr;
// Set queue attributes
attr.mq_flags = 0;
attr.mq_maxmsg = 10;
attr.mq_msgsize = MAX_MSG_SIZE;
attr.mq_curmsgs = 0;
mq = mq_open(QUEUE_NAME, O_CREAT | O_WRONLY, 0644, &attr);
if (mq == (mqd_t)-1) {
perror("mq_open");
exit(EXIT_FAILURE);
}
const char *message = "Test message";
if (mq_send(mq, message, strlen(message) + 1, MSG_PRIO) == -1) {
perror("mq_send");
exit(EXIT_FAILURE);
}
mq_close(mq);
return 0;
}
// mqueue_receiver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#define QUEUE_NAME "/test_queue"
#define MAX_MSG_SIZE 256
int main() {
mqd_t mq;
char buffer[MAX_MSG_SIZE];
unsigned int prio;
mq = mq_open(QUEUE_NAME, O_RDONLY);
if (mq == (mqd_t)-1) {
perror("mq_open");
exit(EXIT_FAILURE);
}
ssize_t bytes_read = mq_receive(mq, buffer, MAX_MSG_SIZE, &prio);
if (bytes_read == -1) {
perror("mq_receive");
exit(EXIT_FAILURE);
}
printf("Received message: %s (priority: %u)\n", buffer, prio);
mq_close(mq);
mq_unlink(QUEUE_NAME);
return 0;
}
mqueue_sender.c
and mqueue_receiver.c
) and compile them:
gcc -o mqueue_sender mqueue_sender.c -lrt
gcc -o mqueue_receiver mqueue_receiver.c -lrt
./mqueue_sender
./mqueue_receiver
Received message: Test message (priority: 1)
Message queues have several attributes that can be configured to control their behavior. These attributes include the maximum number of messages, the maximum message size, and the current number of messages in the queue.
struct mq_attr {
long mq_flags; /* Message queue flags */
long mq_maxmsg; /* Maximum number of messages */
long mq_msgsize; /* Maximum message size */
long mq_curmsgs; /* Number of messages currently queued */
};
Message queues can be configured to operate in non-blocking mode, allowing processes to continue execution even if no messages are available.
mqd_t mq = mq_open(QUEUE_NAME, O_RDONLY | O_NONBLOCK);
Proper error handling is essential when working with IPC mechanisms. The following code demonstrates a common error handling pattern.
#include <errno.h>
void handle_error(const char *msg) {
perror(msg);
exit(EXIT_FAILURE);
}
if (mq_send(mq, message, strlen(message) + 1, prio) == -1) {
handle_error("mq_send");
}
IPC mechanisms, particularly pipes and message queues, are essential tools for inter-process communication in Unix-like systems. Understanding these mechanisms and their proper implementation is crucial for developing robust and efficient multi-process applications.