Exploring Operating Systems

Day 35: Inter-Process Communication (IPC)

Table of Contents

  1. Introduction to IPC
  2. Types of IPC Mechanisms
  3. Pipes in Detail
  4. Message Queues
  5. Advanced IPC Concepts
  6. Error Handling
  7. Performance Considerations
  8. Security Aspects
  9. Conclusion

1. Introduction to IPC

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.

Key Concepts:

2. Types of IPC Mechanisms

2.1 Pipes

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.

2.2 Message Queues

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.

3. Pipes in Detail

3.1 Anonymous Pipes Implementation

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;
}

Steps to Run the Code:

  1. Compile the Code: Save the code in a file named pipe_example.c and compile it using a C compiler, such as gcc:
    gcc -o pipe_example pipe_example.c
    
  2. Run the Executable: Execute the compiled program:
    ./pipe_example
    
  3. Expected Output: The child process will receive the message sent by the parent process and print it to the console:
    Child received: Hello from parent!
    

3.2 Named Pipes (FIFOs)

Named pipes, also known as FIFOs, allow communication between unrelated processes. The following code demonstrates how to create and use a named pipe.

Writer Process:

// 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;
}

Reader Process:

#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;
}

Steps to Run the Code:

  1. Compile the Code: Save the writer and reader code in separate files (fifo_writer.c and fifo_reader.c) and compile them:
    gcc -o fifo_writer fifo_writer.c
    gcc -o fifo_reader fifo_reader.c
    
  2. Run the Writer: Start the writer process:
    ./fifo_writer
    
  3. Run the Reader: In a separate terminal, start the reader process:
    ./fifo_reader
    
  4. Expected Output: The reader process will receive the message sent by the writer process and print it to the console:
    Received: Message through FIFO
    

4. Message Queues

4.1 POSIX Message Queue Implementation

The following code demonstrates how to create and use a POSIX message queue for communication between processes.

Sender Process:

#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;
}

Receiver Process:

// 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;
}

Steps to Run the Code:

  1. Compile the Code: Save the sender and receiver code in separate files (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
    
  2. Run the Sender: Start the sender process:
    ./mqueue_sender
    
  3. Run the Receiver: In a separate terminal, start the receiver process:
    ./mqueue_receiver
    
  4. Expected Output: The receiver process will receive the message sent by the sender process and print it to the console along with its priority:
    Received message: Test message (priority: 1)
    

5. Advanced IPC Concepts

5.1 Message Queue Attributes

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 */
};

5.2 Non-blocking Operations

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);

6. Error Handling

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");
}

7. Performance Considerations

8. Security Aspects

9. Conclusion

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.