Skip to content

Inter-Process Communication (IPC)

Category: Operating System Fundamentals
Type: Operating System Concept
Generated on: 2025-07-10 03:01:40
For: System Administration, Development & Technical Interviews


Inter-Process Communication (IPC) Cheatsheet

Section titled “Inter-Process Communication (IPC) Cheatsheet”

1. Quick Overview

  • What is it? IPC is a set of mechanisms that allow different processes to communicate and synchronize their actions. Think of it as processes “talking” to each other.
  • Why is it important?
    • Resource sharing: Allows processes to share data and resources.
    • Modular design: Enables breaking down complex tasks into smaller, independent processes.
    • Concurrency: Facilitates parallel execution and improved performance.
    • Distributed systems: Essential for communication between processes on different machines.

2. Key Concepts

  • Process: An instance of a program in execution. Has its own memory space.
  • Address Space: The range of memory addresses that a process can access.
  • Synchronization: Coordinating the execution of processes to ensure data consistency and avoid race conditions.
  • Critical Section: A section of code that accesses shared resources and must be protected from concurrent access.
  • Race Condition: A situation where the outcome of a program depends on the unpredictable order in which multiple processes access shared resources.
  • Deadlock: A situation where two or more processes are blocked indefinitely, waiting for each other to release resources.
  • Mutual Exclusion: Ensuring that only one process can access a shared resource at a time.
  • Message Passing: A form of IPC where processes communicate by sending and receiving messages.
  • Shared Memory: A region of memory that is shared between multiple processes.

3. How It Works

Here are some common IPC mechanisms:

A. Pipes (Anonymous & Named)

  • Anonymous Pipes: Unidirectional communication between related processes (typically parent and child).

    • Created using pipe() system call.
    • Only accessible to processes with a common ancestor.
    #include <unistd.h>
    #include <stdio.h>
    int main() {
    int pipefd[2]; // pipefd[0] = read end, pipefd[1] = write end
    pid_t pid;
    char buf[20];
    if (pipe(pipefd) == -1) {
    perror("pipe");
    return 1;
    }
    pid = fork();
    if (pid == -1) {
    perror("fork");
    return 1;
    }
    if (pid == 0) { // Child process (reader)
    close(pipefd[1]); // Close write end
    read(pipefd[0], buf, sizeof(buf));
    printf("Child received: %s\n", buf);
    close(pipefd[0]);
    } else { // Parent process (writer)
    close(pipefd[0]); // Close read end
    write(pipefd[1], "Hello from parent", 18);
    close(pipefd[1]);
    }
    return 0;
    }
    +---------+ pipefd[1] (Write End) +---------+
    | Parent | ----------------------------> | Child |
    | Process | | Process |
    +---------+ pipefd[0] (Read End) +---------+
  • Named Pipes (FIFOs): Unidirectional communication between unrelated processes.

    • Created using mkfifo() system call.
    • Accessed through a file system path.
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    int main() {
    const char *fifo_path = "/tmp/myfifo";
    mkfifo(fifo_path, 0666); // Create the FIFO
    pid_t pid = fork();
    if (pid == 0) { // Child process (reader)
    int fd = open(fifo_path, O_RDONLY);
    char buf[80];
    read(fd, buf, sizeof(buf));
    printf("Child received: %s\n", buf);
    close(fd);
    } else { // Parent process (writer)
    int fd = open(fifo_path, O_WRONLY);
    write(fd, "Hello from parent", 18);
    close(fd);
    }
    return 0;
    }
    +---------+ FIFO (File System) +---------+
    | Process | ------------------------> | Process |
    +---------+ +---------+

B. Message Queues

  • Allow processes to exchange messages. Messages are placed in a queue and retrieved by other processes.

  • Provide message ordering.

  • Can be persistent or non-persistent.

  • System V message queues (older) and POSIX message queues (more modern).

    // Example using POSIX message queues (simplified)
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <mqueue.h>
    #include <fcntl.h>
    #include <unistd.h>
    #define QUEUE_NAME "/my_queue"
    int main() {
    pid_t pid = fork();
    if (pid == 0) { // Child process (receiver)
    mqd_t mq = mq_open(QUEUE_NAME, O_RDONLY | O_CREAT, 0666, NULL);
    if (mq == (mqd_t)-1) { perror("mq_open"); exit(1); }
    char buffer[1024];
    mq_receive(mq, buffer, sizeof(buffer), NULL);
    printf("Child received: %s\n", buffer);
    mq_close(mq);
    mq_unlink(QUEUE_NAME); // Remove queue (if created)
    } else { // Parent process (sender)
    mqd_t mq = mq_open(QUEUE_NAME, O_WRONLY | O_CREAT, 0666, NULL);
    if (mq == (mqd_t)-1) { perror("mq_open"); exit(1); }
    const char *message = "Hello from parent!";
    mq_send(mq, message, strlen(message) + 1, 0);
    mq_close(mq);
    }
    return 0;
    }
    +---------+ Message Queue +---------+
    | Sender | ------------------> | Receiver|
    +---------+ +---------+

C. Shared Memory

  • Allows processes to access the same region of memory.

  • Fastest form of IPC (no data copying).

  • Requires careful synchronization to avoid race conditions. Often used with semaphores or mutexes.

  • System V shared memory and POSIX shared memory.

    // Example using POSIX shared memory (simplified)
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #define SHM_NAME "/my_shm"
    #define SHM_SIZE 4096
    int main() {
    pid_t pid = fork();
    if (pid == 0) { // Child process (reader)
    int shm_fd = shm_open(SHM_NAME, O_RDWR, 0666);
    if (shm_fd == -1) { perror("shm_open"); exit(1); }
    void *shm_ptr = mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shm_ptr == MAP_FAILED) { perror("mmap"); exit(1); }
    printf("Child read: %s\n", (char*)shm_ptr);
    munmap(shm_ptr, SHM_SIZE);
    close(shm_fd);
    shm_unlink(SHM_NAME); // Remove shared memory object
    } else { // Parent process (writer)
    int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) { perror("shm_open"); exit(1); }
    ftruncate(shm_fd, SHM_SIZE);
    void *shm_ptr = mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (shm_ptr == MAP_FAILED) { perror("mmap"); exit(1); }
    const char *message = "Hello from parent!";
    strcpy((char*)shm_ptr, message);
    munmap(shm_ptr, SHM_SIZE);
    close(shm_fd);
    }
    return 0;
    }
    +---------+ Shared Memory Region +---------+
    | Process | <---------------------> | Process |
    +---------+ +---------+

D. Semaphores

  • Synchronization primitives used to control access to shared resources.

  • Maintain a counter that can be incremented (signal) or decremented (wait).

  • Binary semaphores (mutexes) act as locks, allowing only one process access to a resource.

  • Counting semaphores allow a limited number of processes to access a resource.

    // Example using POSIX semaphores (simplified)
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <semaphore.h>
    #include <unistd.h>
    sem_t semaphore;
    void *thread_function(void *arg) {
    sem_wait(&semaphore); // Acquire semaphore (wait if unavailable)
    printf("Thread entered critical section\n");
    sleep(2); // Simulate work in critical section
    printf("Thread exiting critical section\n");
    sem_post(&semaphore); // Release semaphore
    return NULL;
    }
    int main() {
    sem_init(&semaphore, 0, 1); // Initialize semaphore (binary: 1)
    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, thread_function, NULL);
    pthread_create(&thread2, NULL, thread_function, NULL);
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    sem_destroy(&semaphore);
    return 0;
    }
    +---------+ Semaphore +---------+
    | Process | <-------------> | Process |
    +---------+ +---------+

E. Sockets

  • Allow communication between processes on the same or different machines.

  • Use network protocols (TCP, UDP) for communication.

  • Client-server architecture.

    # Python socket example (simplified)
    # Server
    import socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('localhost', 12345))
    s.listen(1)
    conn, addr = s.accept()
    print('Connected by', addr)
    data = conn.recv(1024)
    print('Received', repr(data))
    conn.sendall(b'Hello from server')
    conn.close()
    # Client
    import socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('localhost', 12345))
    s.sendall(b'Hello from client')
    data = s.recv(1024)
    print('Received', repr(data))
    s.close()
    +---------+ Network +---------+
    | Client | <---------> | Server |
    +---------+ +---------+

F. Signals

  • A mechanism for notifying a process of an event.

  • Asynchronous communication.

  • Limited data transfer.

    #include <stdio.h>
    #include <signal.h>
    #include <unistd.h>
    #include <stdlib.h>
    void signal_handler(int signum) {
    printf("Caught signal %d\n", signum);
    exit(1);
    }
    int main() {
    signal(SIGINT, signal_handler); // Register signal handler
    while (1) {
    printf("Waiting for signal...\n");
    sleep(1);
    }
    return 0;
    }
    +---------+ Signal +---------+
    | Sender | ---------> | Receiver|
    +---------+ +---------+

4. Real-World Examples

  • Web Server: A web server might use multiple processes to handle incoming requests concurrently. These processes could use shared memory to access a common cache of frequently requested files.
  • Database Server: A database server uses shared memory to store data and allow multiple client processes to access it. Semaphores or mutexes are used to ensure data consistency.
  • Microservices Architecture: Different microservices communicate with each other using message queues (e.g., RabbitMQ, Kafka) or REST APIs (which use sockets).
  • Operating System Shell: When you pipe commands in the shell (e.g., ls | grep "file"), the shell uses pipes to connect the output of ls to the input of grep.
  • Image Processing: An image processing application might divide an image into smaller chunks and process them in parallel using multiple processes. Shared memory could be used to store the original image and the processed chunks.

5. Common Issues

  • Race Conditions: Occur when multiple processes access shared data concurrently without proper synchronization. Use mutexes, semaphores, or atomic operations to prevent them.
  • Deadlock: Occurs when two or more processes are blocked indefinitely, waiting for each other to release resources. Avoid circular dependencies in resource allocation.
  • Starvation: Occurs when a process is repeatedly denied access to a resource, even though it is available. Use fair scheduling algorithms to prevent it.
  • Memory Leaks (Shared Memory): If shared memory is not properly unmapped and released, it can lead to memory leaks. Always unmap shared memory regions when they are no longer needed.
  • Security: Ensure that IPC mechanisms are properly secured to prevent unauthorized access and data corruption. Use appropriate permissions and authentication mechanisms.
  • Complexity: IPC can add complexity to your code. Carefully consider the trade-offs between performance and complexity when choosing an IPC mechanism.
  • Debugging: Debugging IPC-related issues can be challenging. Use logging, tracing, and debugging tools to identify and resolve problems.

Troubleshooting Tips:

  • Use a debugger: Tools like gdb can help you step through your code and inspect the state of processes and shared resources.
  • Add logging: Log important events, such as process creation, data access, and synchronization operations.
  • Use system monitoring tools: Tools like top, htop, and vmstat can help you monitor the performance of your processes and identify resource contention.
  • Check error codes: Always check the return values of system calls for errors.
  • Simplify your code: If you are having trouble debugging your IPC code, try simplifying it to isolate the problem.
  • Use a code analyzer: Static code analysis tools can help you identify potential race conditions and other IPC-related issues.

6. Interview Questions

  • What is Inter-Process Communication (IPC)? Why is it important? (See Section 1)
  • Explain different IPC mechanisms and their pros and cons. (See Section 3)
  • What are pipes? What are the differences between anonymous and named pipes? (See Section 3.A)
  • What is shared memory? What are the advantages and disadvantages of using shared memory for IPC? (See Section 3.C)
  • What are semaphores? How are they used for synchronization? (See Section 3.D)
  • What is a race condition? How can it be prevented in IPC? (See Section 2, 5)
  • What is deadlock? How can it be avoided in IPC? (See Section 2, 5)
  • Describe a scenario where you would use shared memory over message queues. (Shared memory is faster when large amounts of data need to be shared frequently, but requires careful synchronization. Message queues are simpler for smaller messages and provide message ordering.)
  • Explain the difference between System V IPC and POSIX IPC. (POSIX IPC is generally considered more modern and portable. System V IPC is older but still widely used.)
  • How do sockets enable IPC? (Sockets allow communication between processes, potentially on different machines, using network protocols.) (See Section 3.E)
  • What is a message queue and how does it work? (See Section 3.B)

Example Answer (Race Condition):

“A race condition occurs when multiple processes or threads access and modify shared data concurrently, and the final outcome depends on the unpredictable order in which they execute. For example, if two processes try to increment a shared counter without proper synchronization, the final value might be incorrect. To prevent race conditions, we can use synchronization mechanisms like mutexes or semaphores to ensure that only one process can access the shared data at a time.”

7. Further Reading

  • Operating System Concepts (Silberschatz, Galvin, Gagne): A classic textbook on operating systems.
  • Advanced Programming in the UNIX Environment (Stevens and Rago): A comprehensive guide to UNIX system programming.
  • Beej’s Guide to Network Programming: A good resource for learning about sockets and network programming (available online).
  • Linux man pages: Use man to learn about specific system calls and IPC mechanisms (e.g., man pipe, man shm_open, man sem_init).
  • POSIX standard: The official POSIX standard defines the interfaces for various IPC mechanisms.
  • Tutorialspoint.com: Offers concise explanations and tutorials on IPC mechanisms.
  • GeeksForGeeks.org: A comprehensive resource for computer science topics, including IPC.