Operating Systems

Unit 9: Inter Process Communication (IPC)

Master how processes talk to each other — from shared memory and pipes to message queues — with complete C code examples and real-world Indian industry applications.

⏱ Time to Complete: 6 hrs theory + 4 hrs lab  |  💰 Earning Potential: ₹8,000–₹20,000/month  |  📝 30 MCQs (Bloom's Mapped)

💌 Jobs this unlocks: Backend Engineer (₹6–12 LPA)  |  Systems Programmer (₹8–15 LPA)

Section A

Opening Hook — Indian Railways: The World's Largest IPC System

🚂 How Does Indian Railways Coordinate 13,000 Trains Every Day?

Picture New Delhi Railway Station at 8 AM. The station master needs to simultaneously communicate with the signal room (which track is free?), the loco pilot (reduce speed, platform 5 is ready), the ticket counter (Rajdhani Express is delayed by 20 minutes), and the platform announcer (announce the arrival on PA system). Each of these is a separate "process" — independent, running concurrently, with its own state and data.

The station master can't walk to each person individually. Instead, they use different communication mechanisms: a direct phone line to the signal room (like a pipe), a shared status board visible to everyone (like shared memory), an official memo sent through the system (like a message queue), and a broadcast announcement (like signals/events). This is Inter Process Communication (IPC) in real life.

Indian Railways runs 13,000+ trains daily, serving 23 million passengers, across 7,000+ stations. Their digital systems — CRIS (Centre for Railway Information Systems) — process millions of IPC operations per second: reservation updates, PNR status queries, signal coordination, and real-time tracking. Every time you check PNR status on IRCTC, multiple processes communicate behind the scenes to fetch your data.

Could YOU design this communication system? By the end of this unit, you'll know exactly how — using pipes, shared memory, message queues, and more. Let's build it.

🇮🇳 Indian Railways🇮🇳 Razorpay🇮🇳 PhonePe🇮🇳 IRCTC🇮🇳 Paytm🇮🇳 Ola
IRCTC's Tatkal booking system handles 25 million+ requests in minutes. Behind the scenes, multiple processes — payment verification, seat allocation, waitlist management, SMS notification — all communicate via IPC mechanisms. The system once crashed because the IPC message queue overflowed. They fixed it by switching to Kafka-based message passing.
Section B

Learning Outcomes — Bloom's Taxonomy Mapped

Bloom's LevelLearning Outcome
🔵 RememberList and define the major IPC mechanisms: pipes, named pipes, shared memory, message queues, and message passing
🔵 UnderstandExplain how shared memory and message passing differ in terms of speed, synchronization, and use cases with Indian examples
🟢 ApplyWrite complete C programs using pipe(), mkfifo(), shmget(), and mq_open() for inter-process communication
🟢 AnalyzeCompare and contrast IPC mechanisms across dimensions of speed, directionality, persistence, and process relationship requirements
🟠 EvaluateEvaluate which IPC mechanism is most appropriate for a given real-world scenario such as Indian Railways or Razorpay's payment system
🟠 CreateDesign a multi-process communication system prototype using IPC primitives for a real-world Indian application
Section C

Concept Explanation — Inter Process Communication from Scratch

1. IPC Need and Overview

In an operating system, processes are isolated by design — each has its own address space, its own stack, its own data. This isolation is great for security and stability (a crash in one process doesn't kill another). But real applications need processes to cooperate. A web server process needs to talk to a database process. A print spooler needs to accept jobs from multiple applications. A payment gateway needs its fraud-detection module to communicate with its transaction processor.

Why do processes need to communicate?

  • Data sharing: Multiple processes working on the same dataset (e.g., IRCTC reservation + waitlist processes sharing seat data)
  • Computation speedup: Divide a task among multiple processes and combine results
  • Modularity: Break a large system into smaller, independent processes (microservices at Razorpay)
  • Convenience: A user may run multiple tasks simultaneously — editor, compiler, browser — that need to share clipboard data

🔗 IPC Mechanisms — The Big Picture (Indian Railway Analogy)

Shared Memory = Shared Google Sheet

Everyone can see and edit the same document simultaneously. Fast, but you need rules (synchronization) to avoid conflicts. Like a shared status board at the railway station that the station master, signal operator, and announcer all read and update.

Message Queue = Official Memo System

Structured messages placed in an ordered queue. The sender writes a memo, drops it in the box; the receiver picks it up when ready. Like the official telegraph/message system between railway stations — messages are prioritized and processed in order.

Pipe = Walkie-Talkie

Direct, real-time, one-direction communication between two people. The station master talks, the loco pilot listens. Simple and fast, but only works between people who know each other (related processes).

Named Pipe (FIFO) = Public Notice Board with a Name

Any process can write to or read from it by name — even unrelated processes. Like a named notice board at the station that any department can use.

Message Passing = Formal Letter Exchange

Processes explicitly send and receive messages. Can be synchronous (wait for reply) or asynchronous (send and continue). Like sending an official letter between railway zones — structured, traceable, but slower than a phone call.

Think of IPC like departments in Indian Railways communicating. The Mechanical Department (maintains trains), Electrical Department (signals & power), Commercial Department (tickets & revenue), and Operating Department (scheduling) are all separate "processes." They MUST communicate to run a single train. If the Mechanical Dept doesn't tell Operating that a coach is under repair, you get the infamous "coach detached" message on IRCTC. That's an IPC failure in real life.

2. Shared Memory (System V IPC)

Plain English: Imagine two roommates sharing a whiteboard in their room. Either roommate can write on it or read from it at any time — no messenger needed. It's the fastest way to exchange information because the data is just there, in a memory region accessible to both processes.

Technical Definition: Shared memory is a region of memory that is mapped into the address space of two or more processes. Once set up, processes can read and write to this memory directly, without any kernel intervention for data transfer. This makes it the fastest IPC mechanism.

Key System Calls

FunctionPurposeAnalogy
shmget()Create or get a shared memory segmentBuying the whiteboard
shmat()Attach the segment to process address spaceHanging the whiteboard in your room
shmdt()Detach the segment from processTaking the whiteboard off the wall
shmctl()Control operations (destroy, get info)Throwing away the whiteboard

C Code — Shared Memory Writer Process

C
/* shm_writer.c — Writes data to shared memory segment */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    // Generate a unique key using ftok
    key_t key = ftok("shmfile", 65);

    // Create shared memory segment of 1024 bytes
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }

    // Attach to shared memory
    char *str = (char *)shmat(shmid, NULL, 0);
    if (str == (char *)-1) {
        perror("shmat failed");
        return 1;
    }

    // Write data to shared memory
    printf("Write data: ");
    fgets(str, 1024, stdin);

    printf("Data written to shared memory: %s\n", str);

    // Detach from shared memory
    shmdt(str);

    return 0;
}

C Code — Shared Memory Reader Process

C
/* shm_reader.c — Reads data from shared memory segment */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    // Generate the SAME key as writer
    key_t key = ftok("shmfile", 65);

    // Get the existing shared memory segment
    int shmid = shmget(key, 1024, 0666);
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }

    // Attach to shared memory
    char *str = (char *)shmat(shmid, NULL, 0);
    if (str == (char *)-1) {
        perror("shmat failed");
        return 1;
    }

    // Read data from shared memory
    printf("Data read from shared memory: %s\n", str);

    // Detach from shared memory
    shmdt(str);

    // Destroy the shared memory segment
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

Compilation & Execution

Bash
# First, create the key file
$ touch shmfile

# Compile both programs
$ gcc shm_writer.c -o shm_writer
$ gcc shm_reader.c -o shm_reader

# Run writer first (in Terminal 1)
$ ./shm_writer

# Run reader second (in Terminal 2)
$ ./shm_reader
$ ./shm_writer Write data: Hello from Indian Railways IPC! Data written to shared memory: Hello from Indian Railways IPC! $ ./shm_reader Data read from shared memory: Hello from Indian Railways IPC!
Shared memory is the fastest IPC mechanism but has NO built-in synchronization. If two processes write to the same memory location simultaneously, you get a race condition — corrupted data. You MUST use semaphores or mutexes alongside shared memory. Think of it like two people writing on the same whiteboard area at the same time — gibberish. In interviews, always mention synchronization when discussing shared memory.
Students forget to call shmdt() and shmctl(). If you don't detach and destroy the shared memory segment, it persists even after your program exits. Run ipcs -m to see orphaned segments and ipcrm -m <shmid> to clean them up. Leaked shared memory is a common issue in production systems.

3. Message Passing

Plain English: Instead of sharing a whiteboard, imagine two people exchanging letters. The sender writes a message, puts it in an envelope, and sends it. The receiver opens the envelope and reads it. There's no shared space — the message is copied from sender to receiver through the kernel.

Technical Definition: Message passing is an IPC mechanism where processes communicate by explicitly sending and receiving messages through the operating system kernel. The kernel manages the message buffer and ensures delivery.

📚 Direct vs Indirect Communication

Direct Communication

Processes must name each other explicitly. send(P, message) sends to process P. receive(Q, message) receives from process Q. Like calling someone directly on their phone — you must know their number. A link is established automatically between exactly two processes.

Indirect Communication (via Mailbox/Port)

Messages are sent to and received from mailboxes (also called ports). send(A, message) sends to mailbox A. receive(A, message) receives from mailbox A. Like dropping a letter in a post office box — anyone with the box key can read it. Multiple processes can share a mailbox.

⏱ Synchronous vs Asynchronous Message Passing

Synchronous (Blocking)

Blocking send: The sender is blocked until the message is received by the receiver. Like making a phone call — you wait until the other person picks up.

Blocking receive: The receiver blocks until a message is available. Like waiting at the door for the postman.

Asynchronous (Non-blocking)

Non-blocking send: The sender sends the message and continues immediately. Like sending a WhatsApp message — you don't wait for "blue ticks."

Non-blocking receive: The receiver either gets a valid message or a null/empty indication. Like checking your mailbox — if there's no letter, you move on.

FeatureDirect CommunicationIndirect Communication
NamingMust know exact process IDUses shared mailbox/port
CouplingTightly coupledLoosely coupled
FlexibilityOne-to-one onlyMany-to-many possible
AnalogyDirect phone callPost office box
Use CaseParent-child process communicationServer handling multiple client requests
PhonePe uses message passing extensively. When you make a UPI payment, the transaction process sends a message to the bank verification process, which sends a message to the NPCI (National Payments Corporation of India) process, which sends a message to the recipient's bank process. Each step is an asynchronous message pass — that's why you sometimes see "Processing..." for a few seconds. The entire UPI infrastructure is built on message passing between independent services.

4. Pipes (Unnamed Pipes)

Plain English: A pipe is like a water pipe — data flows in one direction. One process pours data in at one end, another process drinks from the other end. It's the simplest IPC mechanism in Unix/Linux.

Technical Definition: A pipe is a unidirectional communication channel that connects the standard output of one process to the standard input of another. It uses two file descriptors: fd[0] for reading and fd[1] for writing.

How pipe() Works

C
int fd[2];
pipe(fd);
// fd[0] = read end  (mouth of the pipe — drink from here)
// fd[1] = write end (top of the pipe — pour data here)

C Code — Parent Writes, Child Reads via Pipe

C
/* pipe_demo.c — Parent sends message to child via pipe */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    int fd[2];        // fd[0]=read, fd[1]=write
    pid_t pid;
    char write_msg[] = "Train 12301 Rajdhani: Platform 5 Ready!";
    char read_msg[100];

    // Create the pipe
    if (pipe(fd) == -1) {
        perror("Pipe creation failed");
        return 1;
    }

    pid = fork();   // Create child process

    if (pid < 0) {
        perror("Fork failed");
        return 1;
    }

    if (pid > 0) {
        // ===== PARENT PROCESS (Station Master) =====
        close(fd[0]);    // Close unused read end

        printf("[Station Master] Sending: %s\n", write_msg);
        write(fd[1], write_msg, strlen(write_msg) + 1);

        close(fd[1]);    // Close write end after writing
        wait(NULL);       // Wait for child to finish
    }
    else {
        // ===== CHILD PROCESS (Platform Announcer) =====
        close(fd[1]);    // Close unused write end

        read(fd[0], read_msg, sizeof(read_msg));
        printf("[Announcer] Received: %s\n", read_msg);
        printf("[Announcer] 📢 Attention passengers...\n");

        close(fd[0]);    // Close read end
    }

    return 0;
}
$ gcc pipe_demo.c -o pipe_demo $ ./pipe_demo [Station Master] Sending: Train 12301 Rajdhani: Platform 5 Ready! [Announcer] Received: Train 12301 Rajdhani: Platform 5 Ready! [Announcer] 📢 Attention passengers...

Shell Pipe Operator |

The shell pipe operator | is the most common use of unnamed pipes. When you type:

Bash
$ ls -l | grep ".c" | wc -l

The shell creates two pipes:

  • Pipe 1: Connects ls -l (stdout) → grep ".c" (stdin). Lists all files, grep filters only .c files.
  • Pipe 2: Connects grep ".c" (stdout) → wc -l (stdin). Counts the number of .c files.

Result: You get the count of C source files in the current directory. Three processes, two pipes, zero temporary files.

The Unix pipe was invented by Doug McIlroy at Bell Labs in 1973. It's so fundamental that Ken Thompson added it to Unix in a single night. The | symbol was chosen because it looks like a pipe connecting two things. This single invention shaped the entire Unix philosophy of "small tools that do one thing well, connected by pipes."

Limitations of Unnamed Pipes

LimitationExplanation
UnidirectionalData flows in only one direction. For two-way communication, you need two pipes.
Related processes onlyOnly works between parent-child or sibling processes (created by fork). Unrelated processes cannot use unnamed pipes.
No persistenceThe pipe exists only as long as both processes are alive. No file on disk.
Limited capacityPipes have a kernel buffer limit (typically 64 KB on Linux). Writing beyond this blocks the writer.

5. Named Pipes (FIFOs)

Plain English: A named pipe is like a pipe with a name tag on it, placed in a public area. Any process — not just parent-child — can use it, as long as they know the name. It appears as a special file in the filesystem.

Technical Definition: A FIFO (First In, First Out) is a special type of file that provides a named, persistent pipe. Unlike unnamed pipes, FIFOs have a name in the filesystem and can be used by unrelated processes. Created with the mkfifo() system call or the mkfifo command.

C Code — FIFO Writer (Unrelated Process 1)

C
/* fifo_writer.c — Writes to a named pipe (FIFO) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

int main() {
    char *fifo_path = "/tmp/railway_fifo";
    char message[256];

    // Create the FIFO (named pipe) if it doesn't exist
    mkfifo(fifo_path, 0666);

    printf("[Signal Room] FIFO Writer ready. Type messages:\n");

    // Open FIFO for writing
    int fd = open(fifo_path, O_WRONLY);

    printf("Enter message: ");
    fgets(message, 256, stdin);

    write(fd, message, strlen(message) + 1);
    printf("[Signal Room] Message sent: %s", message);

    close(fd);
    return 0;
}

C Code — FIFO Reader (Unrelated Process 2)

C
/* fifo_reader.c — Reads from a named pipe (FIFO) */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

int main() {
    char *fifo_path = "/tmp/railway_fifo";
    char buffer[256];

    // Open FIFO for reading (blocks until writer opens it)
    printf("[Station Master] Waiting for signal room message...\n");
    int fd = open(fifo_path, O_RDONLY);

    read(fd, buffer, 256);
    printf("[Station Master] Received: %s", buffer);

    close(fd);

    // Remove the FIFO file
    unlink(fifo_path);

    return 0;
}

Compilation & Execution

Bash
$ gcc fifo_writer.c -o fifo_writer
$ gcc fifo_reader.c -o fifo_reader

# Terminal 1: Start the reader first (it will block waiting)
$ ./fifo_reader
[Station Master] Waiting for signal room message...

# Terminal 2: Start the writer
$ ./fifo_writer
[Signal Room] FIFO Writer ready. Type messages:
Enter message: Track 3 clear for departure
[Signal Room] Message sent: Track 3 clear for departure

# Back in Terminal 1:
[Station Master] Received: Track 3 clear for departure

Unnamed Pipes vs Named Pipes (FIFOs)

FeatureUnnamed PipeNamed Pipe (FIFO)
Created bypipe() system callmkfifo() or mkfifo command
Filesystem presenceNo file — exists only in kernelYes — appears as a special file
Process relationshipOnly related (parent-child)Any processes (related or unrelated)
PersistenceDestroyed when processes exitPersists until explicitly deleted (unlink)
DirectionUnidirectionalUnidirectional (but can open two FIFOs for bidirectional)
NamingNo name — identified by file descriptorsHas a pathname in the filesystem
Use caseShell commands: ls | grepClient-server communication, logging

6. popen() and pclose()

Plain English: popen() is a convenience function that does three things in one call: creates a pipe, forks a child process, and execs a shell command. It's like having a helper who runs a command for you and hands you the output through a pipe. pclose() waits for the command to finish and cleans up.

Technical Definition: popen() opens a process by creating a pipe, forking, and invoking the shell. It returns a FILE pointer that can be used for reading (capturing command output) or writing (sending input to command).

C Code — Using popen() to Capture Command Output

C
/* popen_demo.c — Execute a command and read its output */
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *fp;
    char buffer[256];

    printf("=== System Information via popen() ===\n\n");

    // Execute "uname -a" and read output
    fp = popen("uname -a", "r");
    if (fp == NULL) {
        perror("popen failed");
        return 1;
    }

    printf("OS Info: ");
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }
    pclose(fp);

    // Execute "ls -la *.c" and read output
    fp = popen("ls -la *.c 2>/dev/null", "r");
    if (fp != NULL) {
        printf("\nC source files:\n");
        while (fgets(buffer, sizeof(buffer), fp) != NULL) {
            printf("  %s", buffer);
        }
        pclose(fp);
    }

    // Execute "date" to get current time
    fp = popen("date", "r");
    if (fp != NULL) {
        printf("\nCurrent time: ");
        fgets(buffer, sizeof(buffer), fp);
        printf("%s", buffer);
        pclose(fp);
    }

    return 0;
}
$ gcc popen_demo.c -o popen_demo $ ./popen_demo === System Information via popen() === OS Info: Linux railway-server 5.15.0-generic #1 SMP x86_64 GNU/Linux C source files: -rw-r--r-- 1 user user 524 Jun 23 pipe_demo.c -rw-r--r-- 1 user user 412 Jun 23 shm_writer.c -rw-r--r-- 1 user user 398 Jun 23 shm_reader.c Current time: Mon Jun 23 12:30:45 IST 2025
popen() is perfect for quick scripting tasks in C. Need to check disk space, list files, or get system info from within your C program? Use popen(). But be careful — it invokes the shell (/bin/sh), so it's vulnerable to shell injection attacks if you pass unsanitized user input. Never do popen(user_input, "r") in production code.

7. Message Queues (POSIX)

Plain English: A message queue is like a post office box system. Process A drops a letter (message) into a specific numbered box. Process B opens that box and picks up the letter whenever it's ready. Messages have priorities — urgent telegrams get picked up before regular letters.

Technical Definition: POSIX message queues provide a prioritized, asynchronous messaging mechanism between processes. Messages are stored in the kernel and can be retrieved by priority. Unlike pipes, message queues preserve message boundaries — each message is an independent unit.

Key System Calls

FunctionPurposeAnalogy
mq_open()Create or open a message queueGetting a post office box
mq_send()Send a message to the queueDropping a letter in the box
mq_receive()Receive a message from the queuePicking up a letter from the box
mq_close()Close the message queue descriptorWalking away from the post office
mq_unlink()Remove the message queueCancelling the post office box

C Code — Message Queue Sender

C
/* mq_sender.c — Sends messages to a POSIX message queue */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

#define QUEUE_NAME  "/railway_queue"
#define MAX_SIZE    1024
#define MSG_STOP    "exit"

int main() {
    mqd_t mq;
    char buffer[MAX_SIZE];

    // Set queue attributes
    struct mq_attr attr;
    attr.mq_flags   = 0;
    attr.mq_maxmsg  = 10;       // Max 10 messages in queue
    attr.mq_msgsize = MAX_SIZE;  // Max message size
    attr.mq_curmsgs = 0;

    // Create the message queue
    mq = mq_open(QUEUE_NAME, O_CREAT | O_WRONLY, 0644, &attr);
    if (mq == (mqd_t)-1) {
        perror("mq_open failed");
        return 1;
    }

    printf("[Ticket Counter] Message Queue Sender ready.\n");
    printf("Type messages (type 'exit' to quit):\n\n");

    do {
        printf("Send> ");
        fgets(buffer, MAX_SIZE, stdin);
        buffer[strcspn(buffer, "\n")] = 0;  // Remove newline

        // Send with priority 0
        if (mq_send(mq, buffer, strlen(buffer) + 1, 0) == -1) {
            perror("mq_send failed");
        }
    } while (strcmp(buffer, MSG_STOP) != 0);

    // Cleanup
    mq_close(mq);
    printf("[Ticket Counter] Sender closed.\n");

    return 0;
}

C Code — Message Queue Receiver

C
/* mq_receiver.c — Receives messages from a POSIX message queue */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>

#define QUEUE_NAME  "/railway_queue"
#define MAX_SIZE    1024
#define MSG_STOP    "exit"

int main() {
    mqd_t mq;
    char buffer[MAX_SIZE + 1];
    unsigned int priority;
    ssize_t bytes_read;

    // Open existing message queue for reading
    mq = mq_open(QUEUE_NAME, O_RDONLY);
    if (mq == (mqd_t)-1) {
        perror("mq_open failed");
        return 1;
    }

    printf("[Display Board] Receiver ready. Waiting for messages...\n\n");

    do {
        bytes_read = mq_receive(mq, buffer, MAX_SIZE + 1, &priority);
        if (bytes_read >= 0) {
            buffer[bytes_read] = '\0';
            printf("Recv [priority=%u]: %s\n", priority, buffer);
        }
    } while (strcmp(buffer, MSG_STOP) != 0);

    // Cleanup
    mq_close(mq);
    mq_unlink(QUEUE_NAME);
    printf("\n[Display Board] Queue removed. Receiver closed.\n");

    return 0;
}

Compilation & Execution

Bash
# Note: POSIX message queues need -lrt flag
$ gcc mq_sender.c -o mq_sender -lrt
$ gcc mq_receiver.c -o mq_receiver -lrt

# Terminal 1: Start receiver
$ ./mq_receiver

# Terminal 2: Start sender
$ ./mq_sender
# Terminal 2 (Sender): [Ticket Counter] Message Queue Sender ready. Type messages (type 'exit' to quit): Send> Rajdhani Express delayed 15 mins Send> Platform change: 12302 to Platform 7 Send> exit [Ticket Counter] Sender closed. # Terminal 1 (Receiver): [Display Board] Receiver ready. Waiting for messages... Recv [priority=0]: Rajdhani Express delayed 15 mins Recv [priority=0]: Platform change: 12302 to Platform 7 Recv [priority=0]: exit [Display Board] Queue removed. Receiver closed.
Message queues support priority-based retrieval. When you call mq_send(mq, buffer, len, priority), higher priority numbers get retrieved first by mq_receive(). In a railway system, "EMERGENCY: Track fault" (priority 9) would be retrieved before "Tea stall closed" (priority 1). This makes message queues ideal for systems where some messages are more urgent than others.

8. IPC Mechanism Comparison Table

FeatureShared MemoryMessage PassingPipeNamed Pipe (FIFO)Message Queue
Speed⚡ Fastest (no kernel copy)Moderate (kernel involvement)FastFastModerate
DirectionBidirectionalBidirectionalUnidirectionalUnidirectionalBidirectional
Process RelationshipAny (related/unrelated)AnyRelated only (parent-child)Any (related/unrelated)Any (related/unrelated)
PersistenceUntil explicitly destroyedUntil consumedNo — dies with processesFilesystem file (persistent)Until explicitly destroyed
Synchronization⚠ Manual (semaphores needed)Built-in (kernel manages)Built-in (blocking read/write)Built-in (blocking read/write)Built-in (blocking + priority)
Message BoundariesNo (byte stream)Yes (discrete messages)No (byte stream)No (byte stream)Yes (discrete messages)
ComplexityHigh (sync + setup)ModerateLowLow-MediumMedium
Best Use CaseHigh-speed large data transfer (video, databases)Distributed systems, microservicesShell commands, simple parent-childClient-server, loggingTask queues, priority messaging
Indian ExampleIRCTC seat availability (shared across queries)UPI payment verification chainls | grep in terminalLog aggregation at FlipkartRazorpay payment processing queue
Students mix up "message passing" (OS concept) with "message queue" (specific IPC mechanism). Message passing is a paradigm — it means any communication where data is explicitly sent/received. Message queues are a specific implementation of message passing where messages are stored in a kernel-managed queue. Pipes also implement message passing, but they're a different mechanism. In exams, be precise.

9. IPC in Modern Systems

The IPC mechanisms we studied (pipes, shared memory, message queues) are low-level, OS-provided primitives. In modern production systems, they've evolved into high-level abstractions:

Low-Level IPCModern EquivalentUsed By
Message QueueRabbitMQ, Apache Kafka, Amazon SQSRazorpay, Swiggy, Netflix
Shared MemoryRedis (in-memory data store), MemcachedFlipkart, Instagram, Twitter
Message PassinggRPC, REST APIs, GraphQLGoogle, PhonePe, Uber
PipesUnix domain sockets, streaming APIsDocker, Kubernetes, Nginx
Named PipesWebSockets, Server-Sent EventsSlack, Discord, real-time dashboards

Microservices = High-Level IPC

In a microservices architecture, each service is essentially a separate process. The Payment Service at Razorpay needs to talk to the Fraud Detection Service, which needs to talk to the Bank Gateway Service, which needs to talk to the Notification Service. This is IPC at scale — across machines, across data centres, across continents.

How Razorpay processes a ₹500 UPI payment — IPC in action:
1. API Gateway receives your payment request (REST API = message passing)
2. Request is pushed to Kafka message queue (message queue IPC)
3. Payment Processor picks it from queue, validates merchant (service-to-service gRPC = message passing)
4. Fraud Detection Service checks transaction patterns using Redis cache (shared memory equivalent)
5. Bank Gateway sends request to NPCI/bank via API (message passing)
6. Response flows back through the same chain
7. Notification Service sends SMS/email via queue (message queue IPC)

Total time: ~2 seconds. Total IPC calls: ~12. All happening concurrently across distributed systems. Every concept you learned in this unit is used here.
Apache Kafka was originally built by LinkedIn and is now used by 80% of Fortune 100 companies. It can handle 2 million messages per second. In India, Swiggy uses Kafka to process food orders — when you place an order, a Kafka message triggers the restaurant notification, delivery partner assignment, ETA calculation, and payment processing — all as separate processes communicating via message queues.
Section D

Learn by Doing — 3-Tier Lab Structure

🟢 Tier 1 — GUIDED TASK: Pipe & FIFO Communication in C

⏱ 60–90 minutesBeginnerZero prior knowledge assumed

Part A: Unnamed Pipe — Parent-Child Communication

Step 1: Create the Source File

Open a terminal and create a new file:

Bash
$ mkdir -p ~/ipc_labs && cd ~/ipc_labs
$ nano pipe_lab.c
Step 2: Type the Complete Code
C
/* pipe_lab.c — Bidirectional parent-child communication */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    int pipe1[2];  // Parent → Child
    int pipe2[2];  // Child → Parent
    pid_t pid;
    char parent_msg[] = "Station Master: Is Platform 3 clear?";
    char child_msg[]  = "Signal Room: Yes, Platform 3 is clear!";
    char buffer[256];

    // Create both pipes
    if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
        perror("Pipe creation failed");
        return 1;
    }

    pid = fork();

    if (pid > 0) {
        // PARENT (Station Master)
        close(pipe1[0]);  // Close read end of pipe1
        close(pipe2[1]);  // Close write end of pipe2

        // Send question to child
        write(pipe1[1], parent_msg, strlen(parent_msg) + 1);
        printf("[Parent] Sent: %s\n", parent_msg);

        // Read reply from child
        read(pipe2[0], buffer, 256);
        printf("[Parent] Received reply: %s\n", buffer);

        close(pipe1[1]);
        close(pipe2[0]);
        wait(NULL);
    }
    else {
        // CHILD (Signal Room)
        close(pipe1[1]);  // Close write end of pipe1
        close(pipe2[0]);  // Close read end of pipe2

        // Read question from parent
        read(pipe1[0], buffer, 256);
        printf("[Child] Received: %s\n", buffer);

        // Send reply to parent
        write(pipe2[1], child_msg, strlen(child_msg) + 1);
        printf("[Child] Sent reply: %s\n", child_msg);

        close(pipe1[0]);
        close(pipe2[1]);
    }

    return 0;
}
Step 3: Compile and Run
Bash
$ gcc pipe_lab.c -o pipe_lab
$ ./pipe_lab
[Parent] Sent: Station Master: Is Platform 3 clear? [Child] Received: Station Master: Is Platform 3 clear? [Child] Sent reply: Signal Room: Yes, Platform 3 is clear! [Parent] Received reply: Signal Room: Yes, Platform 3 is clear!

Part B: Named Pipe (FIFO) — Two Unrelated Processes

Step 4: Create FIFO Writer
C
/* fifo_lab_writer.c */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

int main() {
    char *fifo = "/tmp/ipc_lab_fifo";
    char msg[200];

    mkfifo(fifo, 0666);
    printf("[Writer] Opening FIFO for writing...\n");
    int fd = open(fifo, O_WRONLY);

    printf("Enter train announcement: ");
    fgets(msg, 200, stdin);

    write(fd, msg, strlen(msg) + 1);
    printf("[Writer] Sent!\n");

    close(fd);
    return 0;
}
Step 5: Create FIFO Reader
C
/* fifo_lab_reader.c */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    char *fifo = "/tmp/ipc_lab_fifo";
    char buf[200];

    printf("[Reader] Waiting for announcement...\n");
    int fd = open(fifo, O_RDONLY);

    read(fd, buf, 200);
    printf("[Reader] Announcement: %s", buf);

    close(fd);
    unlink(fifo);
    return 0;
}
Step 6: Compile and Run in Two Terminals
Bash
$ gcc fifo_lab_writer.c -o fifo_writer
$ gcc fifo_lab_reader.c -o fifo_reader

# Terminal 1:
$ ./fifo_reader

# Terminal 2:
$ ./fifo_writer

🎉 Congratulations! You've built both pipe and FIFO communication systems. Take screenshots — these are portfolio-worthy lab programs.

🟡 Tier 2 — SEMI-GUIDED TASK: POSIX Shared Memory Writer + Reader

⏱ 90–120 minutesIntermediateHints provided, you fill the gaps

Your Mission:

Create two separate C programs that communicate using System V shared memory. The writer stores a struct containing train information; the reader displays it.

Hints:

  1. Define a struct: Create struct TrainInfo { int train_no; char name[50]; char platform[10]; char status[20]; }
  2. Writer program:
    • Use ftok() with a common key file
    • Use shmget() with sizeof(struct TrainInfo)
    • Use shmat() to get a pointer
    • Cast the pointer: struct TrainInfo *train = (struct TrainInfo *)shmat(...)
    • Fill in train details and detach with shmdt()
  3. Reader program:
    • Use the same ftok() key
    • Attach to existing segment (no IPC_CREAT flag)
    • Read and print the struct fields
    • Detach and destroy with shmctl(shmid, IPC_RMID, NULL)
  4. Compile: gcc shm_train_writer.c -o shm_writer and gcc shm_train_reader.c -o shm_reader
  5. Key file: Remember to touch shmfile first!
Stretch Goal: Add a ready flag to the struct. The writer sets ready = 1 after writing. The reader busy-waits until ready == 1 before reading. This simulates basic synchronization without semaphores.

🔎 Tier 3 — OPEN CHALLENGE: Python Multi-Process Chat System

⏱ 2–3 hoursAdvancedNo instructions — real-world mini-project

Part A: Quick Start — Python multiprocessing.Queue

Python
# queue_demo.py — Basic multiprocessing Queue demo
from multiprocessing import Process, Queue
import time

def sender(q):
    messages = ["Train arriving", "Platform clear", "Departure in 5 min"]
    for msg in messages:
        print(f"[Sender] Sending: {msg}")
        q.put(msg)
        time.sleep(1)
    q.put("STOP")

def receiver(q):
    while True:
        msg = q.get()
        if msg == "STOP":
            break
        print(f"[Receiver] Got: {msg}")

if __name__ == "__main__":
    q = Queue()
    p1 = Process(target=sender, args=(q,))
    p2 = Process(target=receiver, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print("Done!")

Part B: The Challenge — Build a Mini Chat System

Build a terminal-based chat system where two processes communicate via multiprocessing.Queue:

  1. Two processes: "Station Master" and "Signal Room"
  2. Two queues: One for each direction (bidirectional communication)
  3. Interactive input: Each process should accept user input and send it to the other
  4. Timestamps: Add timestamps to each message
  5. Graceful exit: Typing "quit" should end both processes

Deliverable: A working Python chat system. This demonstrates real IPC using Python's multiprocessing module.

This chat system pattern is the foundation of message broker systems. Companies like Razorpay and Swiggy use this exact pattern (with Kafka/RabbitMQ instead of Python queues) for their microservice communication. Understanding this makes you ready for backend engineering interviews.
Section E

Industry Spotlight — A Day in the Life

👩‍💻 Priya Mehta, 27 — Backend Engineer at Razorpay, Pune

Background: B.Tech Computer Science from Savitribai Phule Pune University. Learned C and Linux systems programming in 3rd year. Built a multi-process chat application as a mini-project using pipes and shared memory. Interned at a Pune-based fintech startup where she worked on message queue systems. Joined Razorpay through their hiring challenge.

A Typical Day:

9:30 AM — Morning standup with the Payment Processing team. Discuss yesterday's Kafka consumer lag — some payment notifications were delayed by 3 seconds.

10:00 AM — Debug a race condition in the shared cache (Redis) where two payment processes were reading stale data. The fix? Implement distributed locking using Redis SETNX — directly inspired by semaphore concepts from OS class.

11:30 AM — Design a new gRPC service for merchant onboarding. Define protobuf message schemas — essentially message passing with strongly-typed messages.

1:00 PM — Lunch with the team. Discuss how to handle the upcoming IPL season traffic — 10x normal payment volume. Need to scale Kafka partitions.

2:30 PM — Write integration tests for the payment retry queue. When a bank API times out, the payment is pushed to a retry message queue with exponential backoff.

4:00 PM — Code review a junior engineer's pull request on the notification service. Their code wasn't properly closing message queue connections — classic resource leak.

5:30 PM — Learning hour — studying the internals of Apache Kafka's log-structured message storage. Understanding how it achieves 2M messages/second.

DetailInfo
Tools Used DailyGo, Kafka, gRPC, Redis, PostgreSQL, Docker, Kubernetes, Git
IPC Concepts UsedMessage queues (Kafka), shared memory (Redis), message passing (gRPC), pipes (Unix domain sockets)
Entry Salary (2024)₹6–12 LPA + ESOPs
Mid-Level (3–5 yrs)₹15–25 LPA
Senior (7+ yrs)₹30–55 LPA
Companies HiringRazorpay, PhonePe, Zerodha, CRED, Paytm, Flipkart, Google India, Amazon India, Atlassian
Section F

Earn With It — Freelance & Income Roadmap

💰 Your Earning Path After This Unit

Portfolio Piece: "Multi-Process Communication System in C" — a GitHub repo with working pipe, FIFO, shared memory, and message queue programs + a Python chat system.

Earning Ideas:

• Build IPC-based backend prototypes for early-stage startups — ₹8,000–₹15,000/project

• Create microservice communication demos for college tech fests — ₹3,000–₹8,000

• Write technical blog posts explaining IPC concepts — ₹2,000–₹5,000/article on Medium/GeeksforGeeks

• Develop inter-process automation scripts for small IT firms — ₹5,000–₹20,000/project

PlatformBest ForTypical Rate
Toptal / UpworkBackend engineering projects (IPC, messaging)$25–$60/hour
InternshalaIndian startup backend internships₹8,000–₹20,000/month
FiverrQuick C/Linux system programming gigs$20–$100/gig
LinkedInDirect outreach to Indian fintech startups₹10,000–₹25,000/project
GitHub + BlogBuild reputation → attract offersIndirect — leads to job offers

⏱ Time to First Earning: 3–4 weeks (if you complete all 3 lab tiers and build a GitHub portfolio)

The fastest path to earning from IPC knowledge: Learn Docker + message queues (RabbitMQ or Kafka). Indian startups are desperate for engineers who can set up microservice communication. A 2-page LinkedIn post explaining "How I built a message queue system from scratch" will get you noticed by recruiters at companies like Razorpay, Zerodha, and CRED.
Section G

MCQ Assessment Bank — 30 Questions (Bloom's Mapped)

Remember / Identify (Q1–Q5)

Q1

Which system call is used to create an unnamed pipe in C?

  1. mkfifo()
  2. pipe()
  3. socket()
  4. mq_open()
Remember
✅ Answer: (B) pipe() — The pipe() system call creates an unnamed pipe and returns two file descriptors: fd[0] for reading and fd[1] for writing.
Q2

In shared memory IPC, which function attaches a shared memory segment to the process address space?

  1. shmget()
  2. shmctl()
  3. shmat()
  4. shmdt()
Remember
✅ Answer: (C) shmat() — shmat() (shared memory attach) maps the shared memory segment into the calling process's virtual address space so it can read/write to it.
Q3

FIFO in named pipes stands for:

  1. File Input/File Output
  2. First In First Out
  3. Fast Inter-process File Operation
  4. Filesystem IPC Function Object
Remember
✅ Answer: (B) First In First Out — Named pipes follow FIFO order: the first data written is the first data read, just like a queue.
Q4

Which of the following is NOT an IPC mechanism?

  1. Shared Memory
  2. Message Queue
  3. Thread Pool
  4. Named Pipe
Remember
✅ Answer: (C) Thread Pool — A thread pool is a concurrency management pattern, not an IPC mechanism. Threads within the same process share memory by default and don't need IPC.
Q5

The popen() function in C combines which three operations?

  1. open(), read(), close()
  2. pipe(), fork(), exec()
  3. socket(), bind(), listen()
  4. malloc(), write(), free()
Remember
✅ Answer: (B) pipe(), fork(), exec() — popen() creates a pipe, forks a child process, and executes a shell command in the child, returning a FILE pointer to the parent.

Understand / Explain (Q6–Q10)

Q6

Why does shared memory require external synchronization (like semaphores) while pipes do not?

  1. Shared memory is slower than pipes
  2. Pipes are bidirectional while shared memory is unidirectional
  3. Shared memory allows simultaneous read/write access without built-in blocking, creating race conditions
  4. Semaphores are part of the shared memory API
Understand
✅ Answer: (C) — Shared memory provides raw access to a memory region. Multiple processes can read/write simultaneously without any kernel-mediated ordering, leading to race conditions. Pipes, by contrast, have built-in blocking: a read on an empty pipe blocks until data is available, and writes block when the buffer is full.
Q7

What is the key difference between direct and indirect message passing?

  1. Direct is faster than indirect
  2. In direct communication, processes must name each other; in indirect, they communicate through a shared mailbox
  3. Direct supports multiple receivers; indirect supports only one
  4. Indirect message passing uses shared memory internally
Understand
✅ Answer: (B) — In direct communication, the sender must explicitly name the recipient process: send(P, msg). In indirect communication, messages are sent to/received from a mailbox or port, allowing decoupled communication where multiple processes can share a mailbox.
Q8

Why can unnamed pipes only be used between related processes (parent-child)?

  1. The kernel restricts pipe access to the same user
  2. Unnamed pipes have no filesystem path; file descriptors are only inherited via fork()
  3. Unnamed pipes are too slow for unrelated processes
  4. The pipe() system call requires a parent PID as argument
Understand
✅ Answer: (B) — Unnamed pipes exist only as file descriptors in the kernel. Since they have no name in the filesystem, the only way another process can access them is by inheriting the file descriptors through fork(). Unrelated processes have no way to discover or open the pipe.
Q9

In the context of message queues, what does "message boundary preservation" mean?

  1. Messages cannot exceed 1 KB
  2. Each message sent is received as a complete, discrete unit — not split or merged with other messages
  3. Messages are encrypted at the boundary between processes
  4. Only the sender can delete messages from the queue
Understand
✅ Answer: (B) — Unlike pipes (which are byte streams where data can be split/merged), message queues treat each mq_send() call as one complete message. The receiver's mq_receive() gets exactly one complete message per call, preserving the sender's intended boundaries.
Q10

What happens when a process tries to read from an empty pipe (with no data and at least one writer still open)?

  1. It returns an error immediately
  2. It returns 0 bytes
  3. It blocks (waits) until data becomes available
  4. The process is terminated by the kernel
Understand
✅ Answer: (C) — By default, read() on an empty pipe blocks the calling process until the writer puts data into the pipe. This is a built-in synchronization feature of pipes. The read returns 0 only when ALL write ends of the pipe are closed.

Apply / Implement (Q11–Q15)

Q11

A process calls pipe(fd) and then fork(). The parent wants to send data to the child. Which file descriptors should the parent close?

  1. fd[0] and fd[1]
  2. fd[1] only
  3. fd[0] only (the read end)
  4. Neither — keep both open
Apply
✅ Answer: (C) fd[0] only — The parent is the writer, so it should close the read end fd[0] (which it doesn't need) and keep fd[1] open for writing. The child should close fd[1] and read from fd[0]. Closing unused ends is essential to ensure proper EOF detection.
Q12

You need two unrelated processes running on the same machine to exchange data. Which IPC mechanism would you use?

  1. Unnamed pipe
  2. Named pipe (FIFO) or Message Queue
  3. fork() with shared variables
  4. Thread synchronization
Apply
✅ Answer: (B) — Unnamed pipes only work between related processes (parent-child via fork). For unrelated processes, you need Named Pipes (FIFOs), Message Queues, or Shared Memory — all of which can be accessed by any process via a name/key.
Q13

What is the correct compilation command for a C program using POSIX message queues?

  1. gcc mq_prog.c -o mq_prog
  2. gcc mq_prog.c -o mq_prog -lpthread
  3. gcc mq_prog.c -o mq_prog -lrt
  4. gcc mq_prog.c -o mq_prog -lmq
Apply
✅ Answer: (C) -lrt — POSIX message queue functions (mq_open, mq_send, etc.) are part of the real-time library, so you must link with -lrt. Forgetting this flag is the most common compilation error for message queue programs.
Q14

In the shell command cat file.txt | grep "error" | wc -l, how many pipes are created?

  1. 1
  2. 2
  3. 3
  4. 0
Apply
✅ Answer: (B) 2 — Each | operator creates one pipe. Here, Pipe 1 connects cat's stdout to grep's stdin, and Pipe 2 connects grep's stdout to wc's stdin. Three processes, two pipes.
Q15

You called shmget() to create a shared memory segment but forgot to call shmctl(shmid, IPC_RMID, NULL). What happens after your program exits?

  1. The segment is automatically destroyed
  2. The segment persists in kernel memory until the system reboots or is manually removed
  3. The kernel throws an error on next boot
  4. Other processes can no longer access shared memory
Apply
✅ Answer: (B) — System V shared memory segments persist in the kernel even after all attached processes exit. They must be explicitly destroyed using shmctl() with IPC_RMID, or removed manually via the ipcrm command. Use ipcs -m to list orphaned segments.

Analyze / Compare (Q16–Q20)

Q16

A high-frequency trading system needs to share price data between a data feed process and 10 analysis processes with minimal latency. Which IPC mechanism is most appropriate?

  1. Named Pipes (one for each analysis process)
  2. Shared Memory with read-write locks
  3. POSIX Message Queues
  4. popen() calls
Analyze
✅ Answer: (B) — Shared memory provides zero-copy data sharing, making it the fastest option. For one writer and multiple readers, read-write locks allow concurrent reads while ensuring exclusive writes. Pipes would require 10 separate connections, and message queues add kernel copy overhead — unacceptable for microsecond-sensitive trading.
Q17

Why might a developer choose message queues over shared memory for an e-commerce order processing system?

  1. Message queues are always faster
  2. Message queues provide automatic ordering, priority, and persistence without manual synchronization
  3. Shared memory cannot handle more than 2 processes
  4. Message queues use less kernel memory
Analyze
✅ Answer: (B) — Order processing requires guaranteed delivery, FIFO ordering, and priority handling (rush orders first). Message queues provide all of this built-in. Shared memory would require the developer to implement their own queue, ordering, priority system, and synchronization — complex and error-prone.
Q18

Comparing pipes and message queues: which statement is true?

  1. Pipes preserve message boundaries; message queues do not
  2. Both pipes and message queues are unidirectional only
  3. Pipes are byte streams; message queues preserve discrete messages
  4. Message queues only work between related processes
Analyze
✅ Answer: (C) — Pipes transmit a continuous byte stream without message boundaries. If you write "hello" and "world" separately, a single read might get "helloworld". Message queues preserve each message as a discrete unit — each mq_receive() gets exactly one complete message.
Q19

In a microservice architecture, which low-level IPC concept is most closely analogous to Apache Kafka?

  1. Unnamed Pipe
  2. Shared Memory
  3. Message Queue
  4. Semaphore
Analyze
✅ Answer: (C) Message Queue — Kafka is essentially a distributed, persistent, replicated message queue at scale. Producers send messages to topics (queues), consumers read from them. It adds features like partitioning, consumer groups, and log compaction, but the core concept is the same message queue IPC pattern.
Q20

A Named Pipe (FIFO) file shows a size of 0 bytes on disk even while processes are actively communicating through it. Why?

  1. The filesystem driver has a bug
  2. FIFOs store data in kernel buffers, not on disk — the filesystem entry is just a reference point
  3. The data is compressed to zero size
  4. Named pipes can only transfer empty signals, not data
Analyze
✅ Answer: (B) — A FIFO's filesystem entry is a special file that acts as a rendezvous point. The actual data passes through kernel buffers in memory, never touching the disk. The file size remains 0 because no data is stored in the filesystem — it's transient, kernel-buffered communication.

Evaluate / Justify (Q21–Q25)

Q21

A startup's payment system uses shared memory (without semaphores) for communication between the payment processor and fraud detection service. After launch, they notice occasional corrupted transaction data. What is the most likely cause?

  1. Shared memory segment is too small
  2. The ftok() key is generating collisions
  3. Race condition: both processes read/write shared memory simultaneously without synchronization
  4. The operating system doesn't support shared memory for financial applications
Evaluate
✅ Answer: (C) — Without synchronization primitives (semaphores, mutexes), two processes accessing shared memory can interleave reads and writes, causing partial updates and data corruption. This is the classic race condition problem — and exactly why the concept section emphasized that shared memory MUST be paired with synchronization.
Q22

An IRCTC architect proposes using unnamed pipes for communication between the web server process and the database process. Evaluate this decision.

  1. Good choice — pipes are fast and simple
  2. Bad choice — the web server and database are typically unrelated processes that cannot share pipe file descriptors
  3. Good choice — pipes work for all process types
  4. Bad choice — pipes don't work on Linux servers
Evaluate
✅ Answer: (B) — Web servers (like Nginx/Apache) and database servers (like PostgreSQL/MySQL) are independently started, unrelated processes. Unnamed pipes require a parent-child relationship via fork(). The correct choices would be Unix domain sockets, TCP sockets, or shared memory with proper naming.
Q23

A team switches from synchronous message passing to asynchronous message passing in their IPC design. What trade-off do they make?

  1. Lower throughput but guaranteed delivery
  2. Higher throughput and responsiveness but need to handle out-of-order delivery and buffer management
  3. No trade-off — asynchronous is always better
  4. Asynchronous message passing is slower but more secure
Evaluate
✅ Answer: (B) — Asynchronous message passing allows the sender to continue without waiting, improving throughput and responsiveness. However, the system must now handle message buffering (what if the buffer fills up?), potential out-of-order delivery, and more complex error handling. It's the classic latency-vs-complexity trade-off.
Q24

Which IPC mechanism would you recommend for a logging service where multiple application processes need to write log entries to a central logger process?

  1. Shared memory — fastest option
  2. Unnamed pipes — simplest option
  3. Named pipe (FIFO) or message queue — supports multiple unrelated writers to one reader
  4. popen() — easiest to implement
Evaluate
✅ Answer: (C) — A logging service has many-to-one communication (multiple writers, one reader). Named pipes allow multiple processes to open and write to the same FIFO by name. Message queues also work well and add the benefit of message boundaries (each log entry is a complete message). Shared memory would require complex locking for multiple writers.
Q25

A system uses message queues with priorities for a hospital emergency system. Emergency cases get priority 9, routine cases get priority 1. Why is this better than a simple FIFO pipe?

  1. Message queues are faster than pipes
  2. Pipes cannot transfer structured data
  3. Message queues allow urgent messages to be retrieved first regardless of arrival order, potentially saving lives
  4. Pipes crash when handling medical data
Evaluate
✅ Answer: (C) — With priority-based message queues, an emergency case added to the queue at 10:05 AM is processed before a routine case that arrived at 10:00 AM. A FIFO pipe would process the routine case first simply because it arrived earlier. In time-critical systems like hospitals or emergency response, priority ordering can be life-saving.

Create / Design (Q26–Q30)

Q26

You're designing a system where Process A generates video frames, Process B applies filters, and Process C displays the result. All on the same machine. What IPC design would you use?

  1. Three unnamed pipes in a pipeline: A → pipe → B → pipe → C
  2. Shared memory with A writing frames, B reading + processing + writing filtered frames, C reading filtered frames
  3. Message queues for all communication
  4. Named pipes with three separate FIFOs
Create
✅ Answer: (B) — Video processing involves large data (megabytes per frame at 30+ fps). Shared memory avoids the kernel copy overhead that pipes and message queues would introduce. Process A writes frames to shared memory region 1, Process B reads from region 1, processes, and writes to region 2, Process C reads from region 2. Semaphores coordinate access.
Q27

Design an IPC system for an ATM machine. The ATM software has separate processes for: card reader, PIN verification, balance inquiry, cash dispensing, and receipt printing. Which IPC architecture would you choose?

  1. A central message queue where all processes send/receive messages
  2. Unnamed pipes connecting each process to the next in sequence
  3. Shared memory for all processes with no synchronization
  4. Each process writes to a separate log file
Create
✅ Answer: (A) — A central message queue provides ordered, prioritized message passing between all components. The card reader sends "card inserted" message, PIN verification listens for it, verifies, sends "PIN OK" message, balance inquiry picks it up, and so on. This architecture is modular, loosely coupled, and handles errors gracefully (if cash dispensing fails, it sends an error message back).
Q28

You need to build a real-time stock ticker that receives price updates from an exchange process and distributes them to 50 client display processes. What combination of IPC would you design?

  1. 50 separate unnamed pipes — one for each client
  2. Shared memory for price data + semaphores for synchronization + signals for update notifications
  3. 50 message queues — one for each client
  4. Named pipes with round-robin reading
Create
✅ Answer: (B) — Shared memory is ideal for one-to-many broadcasting. The exchange process writes the latest price to a shared memory segment. All 50 clients can read from it simultaneously (multiple concurrent readers are safe). A semaphore ensures the writer finishes before readers access the data. A signal (SIGUSR1) can notify all clients that new data is available.
Q29

Design a producer-consumer system where 3 producer processes generate sensor data and 2 consumer processes analyze it. Data must not be lost even if a consumer crashes. Which IPC design is best?

  1. Unnamed pipes from each producer to each consumer
  2. Shared memory with a circular buffer
  3. Persistent message queue with acknowledgement — consumers confirm processing before messages are removed
  4. Named pipes with multiple readers
Create
✅ Answer: (C) — The requirement "data must not be lost even if a consumer crashes" demands persistence and acknowledgement. A message queue where messages remain until explicitly acknowledged by the consumer ensures no data loss. If a consumer crashes mid-processing, the message remains in the queue for another consumer or the restarted consumer to process. This is exactly how Kafka works in production.
Q30

You're tasked with designing the IPC architecture for Indian Railways' next-generation station management system. The system needs: real-time train tracking (high speed), passenger announcements (broadcast), ticket booking updates (persistent, ordered), and emergency alerts (highest priority). Which combination of IPC mechanisms would you architect?

  1. Shared memory for everything — it's the fastest
  2. Shared memory for tracking + message queue with priorities for emergencies + named pipe for announcements + persistent message queue for bookings
  3. Message queues for everything — they're the most versatile
  4. Pipes for everything — they're the simplest
Create
✅ Answer: (B) — Each requirement maps to a different IPC strength: (1) Shared memory for real-time tracking — highest speed, constant updates to position data. (2) Priority message queue for emergencies — emergency alert at priority 9 jumps ahead of everything. (3) Named pipe for announcements — one-directional broadcast from control room to PA system. (4) Persistent message queue for bookings — messages must survive crashes, maintain order, and handle priority (Tatkal vs normal). This is a real-world systems design answer.
Section H

Short Answer Questions

Q1: Explain the difference between shared memory and message passing IPC. When would you use each?

Shared Memory: A common memory region is mapped into multiple processes' address spaces. Processes read/write directly to this memory without kernel involvement for data transfer. It's the fastest IPC method but requires explicit synchronization (semaphores/mutexes) to prevent race conditions.

Message Passing: Processes communicate by explicitly sending and receiving messages through the kernel. The kernel copies data from sender's buffer to receiver's buffer. It's slower than shared memory but provides built-in synchronization and message boundaries.

Use Shared Memory when: High-speed, large data transfer is needed (video processing, real-time databases, shared caches). Example: IRCTC's seat availability data shared across multiple query processes.

Use Message Passing when: You need structured, ordered, priority-based communication between loosely coupled processes. Example: UPI payment processing where each step sends a message to the next service.

Q2: What are the four key system calls for System V shared memory? Explain each briefly.

shmget(key, size, flags): Creates a new shared memory segment or retrieves an existing one. Returns a shared memory ID (shmid). The key identifies the segment; size specifies bytes; flags include permissions and IPC_CREAT.

shmat(shmid, addr, flags): Attaches the shared memory segment to the calling process's address space. Returns a pointer to the attached memory. The process can then read/write through this pointer.

shmdt(addr): Detaches the shared memory segment from the process. The segment still exists in the kernel but is no longer accessible to this process.

shmctl(shmid, cmd, buf): Performs control operations. Most commonly used with IPC_RMID to mark the segment for destruction (it's actually destroyed when the last process detaches).

Q3: Explain with a diagram how an unnamed pipe works between parent and child processes.

Step 1: Parent calls pipe(fd) → creates fd[0] (read) and fd[1] (write).

Step 2: Parent calls fork() → child inherits both file descriptors.

Step 3: Parent closes fd[0] (read end), keeps fd[1] for writing. Child closes fd[1] (write end), keeps fd[0] for reading.

Data flow: Parent writes to fd[1] → data enters kernel pipe buffer → Child reads from fd[0].

Diagram
Parent Process          Kernel Pipe Buffer          Child Process
┌──────────┐          ┌──────────────┐          ┌──────────┐
│ fd[1]────│─────────→│  ■ ■ ■ ■ ■   │─────────→│────fd[0] │
│ (write)  │          │  (FIFO data) │          │  (read)  │
│ fd[0] ✗  │          └──────────────┘          │  fd[1] ✗ │
└──────────┘           (kernel space)           └──────────┘

Key insight: Closing unused ends is crucial. If the parent doesn't close fd[0], the child's read() will never get EOF because there's still a potential writer (the parent) holding the read end open.

Q4: What are the advantages of named pipes (FIFOs) over unnamed pipes?

1. Unrelated process communication: Any process can open a FIFO by its filesystem path. Unnamed pipes only work between parent-child (fork-inherited file descriptors).

2. Filesystem persistence: FIFOs persist as special files until explicitly deleted (unlink). They survive process restarts — a new process can connect to the same FIFO name.

3. Named identification: FIFOs have a meaningful path (e.g., /tmp/my_app_fifo), making them easy to discover and manage. Unnamed pipes are just integer file descriptors.

4. Server-client pattern: Multiple clients can open and write to the same FIFO, while one server reads from it — enabling many-to-one communication.

Trade-off: FIFOs are still unidirectional and still byte streams (no message boundaries). For bidirectional communication, you need two FIFOs.

Q5: How does IPC manifest in modern microservice architectures? Give an Indian industry example.

In microservices, each service runs as an independent process (or container), making IPC essential. Modern IPC mechanisms include:

Message Queues → Apache Kafka / RabbitMQ: Services produce and consume messages asynchronously. Razorpay uses Kafka for payment event streaming — when a payment succeeds, a Kafka message triggers invoice generation, notification, and analytics updates simultaneously.

Shared Memory → Redis / Memcached: In-memory data stores act as shared memory across services. Swiggy uses Redis to cache restaurant menus — the menu service writes, the search service and order service read from the same Redis instance.

Message Passing → gRPC / REST APIs: Services communicate via structured request-response messages. PhonePe uses gRPC between its payment gateway and bank connector services for low-latency, strongly-typed communication.

Indian Example: When you order food on Swiggy: (1) Order service writes to Kafka queue → (2) Restaurant notification service reads from queue → (3) Delivery assignment service reads from queue → (4) ETA calculation service reads restaurant location from Redis cache → (5) Push notification service sends update via message passing (API call to Firebase). All of these are IPC patterns at scale.

Section I

Case Studies

📋 Case Study 1: Indian Railways Station Communication System — Modeling IPC

Background

New Delhi Railway Station handles 350+ trains daily, serving 500,000 passengers. The station operates through multiple independent systems that must communicate in real-time: the Central Control Room, Signal Interlocking System, Platform Announcement System, Display Board System, Ticket Reservation System, and Catering Management.

The IPC Model

Station SystemIPC MechanismWhy This Mechanism?
Train Position Tracking
(GPS data updated every 5 seconds)
Shared MemoryMultiple systems read the same position data simultaneously. Speed is critical — no copying overhead. Semaphores protect concurrent updates.
Platform Announcements
(Control → PA System)
Named Pipe (FIFO)One-directional broadcast. Control room writes announcement text; PA system reads and plays it. FIFO ensures order: "Arrival" announcement plays before "Departure."
Emergency Alerts
(Any dept → All depts)
Priority Message QueueEmergency alert (priority 9) must be processed before routine messages (priority 1). Message queue ensures the bomb threat alert reaches all departments before the tea stall closure notice.
Ticket Booking Updates
(IRCTC → Local Counter)
Persistent Message QueueBooking confirmations must not be lost. If the local counter system is temporarily down, messages wait in the queue. Guaranteed delivery with acknowledgement.
Coach-to-Locomotive Signal
(Guard → Loco Pilot)
Unnamed PipeDirect parent-child style communication. The guard process signals "all clear" to the loco pilot process. Simple, fast, one-shot.

Questions for Discussion

  1. If the shared memory segment for train tracking crashes, what happens to the display boards? How would you design a fallback?
  2. Why is an unnamed pipe suitable for guard-to-loco-pilot communication but not for control-room-to-all-platforms?
  3. Design a priority scheme for the emergency message queue. What priority levels would you define?
Real-world insight: CRIS (Centre for Railway Information Systems) actually uses a combination of TCP sockets, message queues, and shared databases for inter-system communication. Their FOIS (Freight Operations Information System) processes 200,000+ transactions daily using message-oriented middleware — a production-grade implementation of the message queue pattern.

📋 Case Study 2: Razorpay Payment Processing — IPC in Microservices

Background

Razorpay processes over 500 million transactions annually for 8 million+ businesses across India. Their architecture consists of 200+ microservices, each running as an independent process (containerized in Kubernetes). These services must communicate reliably, quickly, and at massive scale.

IPC Architecture

When a customer pays ₹999 on an e-commerce site using Razorpay, here's the IPC journey:

StepServiceIPC UsedTechnical Detail
1API Gateway → Payment ServicegRPC (Message Passing)Strongly-typed protobuf message with payment amount, merchant ID, payment method. Synchronous call — gateway waits for response.
2Payment Service → KafkaMessage Queue (Kafka)Payment event published to "payments" topic. Asynchronous — payment service continues without waiting for consumers.
3Fraud Detection → Redis CacheShared Memory (Redis)Fraud service checks customer's transaction history cached in Redis. Sub-millisecond read. Pattern: "Has this card been used 5+ times in 10 minutes?"
4Bank Gateway → NPCI/BankREST API (Message Passing)HTTP request to bank's API with encrypted payment details. Synchronous — must wait for bank's approval/decline response.
5Notification Service (from Kafka)Message Queue (Kafka Consumer)Consumes "payment_success" event from Kafka. Sends SMS via Twilio API, email via SendGrid API. Asynchronous — runs independently.
6Analytics Service (from Kafka)Message Queue (Kafka Consumer)Consumes same event for dashboard analytics. Updates real-time merchant dashboard. Non-blocking.

Key Design Decisions

  • Why Kafka instead of POSIX MQ? Scale (millions of messages/sec), persistence (messages stored on disk for replay), consumer groups (multiple consumers independently process the same message stream).
  • Why Redis instead of System V shared memory? Network-accessible (works across machines), built-in data structures (hash maps, sorted sets), automatic expiry (TTL).
  • Why gRPC instead of REST for inter-service calls? Binary serialization (protobuf) is 10x faster than JSON. Streaming support. Strong typing prevents bugs.

Questions for Discussion

  1. If Kafka goes down, what happens to payment notifications? How would you design a fallback?
  2. Why does Razorpay use synchronous IPC (gRPC) for bank communication but asynchronous IPC (Kafka) for notifications?
  3. Design an IPC architecture for a new "refund processing" service that must guarantee no refund is processed twice.
Razorpay processes over ₹7 lakh crore ($80B+) annually. Every rupee flows through the IPC architecture described above. A single message queue failure during a Diwali sale could delay thousands of payments. Their engineering team — many of whom are 24–28-year-old engineers from Indian universities — designs these systems using the exact IPC concepts taught in OS courses. The difference between a classroom concept and a ₹10,000 crore system is just scale and reliability engineering.
Section J

Chapter Summary

📝 Key Takeaways — Unit 9: Inter Process Communication

  • IPC is essential because OS processes are isolated by design. They need explicit mechanisms to share data, coordinate actions, and communicate.
  • Shared Memory (shmget, shmat, shmdt, shmctl) is the fastest IPC mechanism — zero kernel copy — but requires manual synchronization (semaphores/mutexes) to prevent race conditions.
  • Message Passing uses send/receive primitives. It can be direct (name the process) or indirect (via mailbox/port). It can be synchronous (blocking) or asynchronous (non-blocking).
  • Unnamed Pipes (pipe()) provide simple, unidirectional, byte-stream communication between parent-child processes. The shell | operator uses this.
  • Named Pipes (FIFOs) (mkfifo()) extend pipes to unrelated processes by providing a filesystem name. They persist until explicitly removed.
  • popen()/pclose() combine pipe + fork + exec into one convenient function for capturing command output in C programs.
  • POSIX Message Queues (mq_open, mq_send, mq_receive) provide prioritized, boundary-preserving message passing. Compile with -lrt.
  • Choose the right mechanism: Speed → Shared Memory. Simplicity → Pipes. Priority + Ordering → Message Queues. Unrelated processes → FIFO or MQ.
  • Modern IPC: Kafka = distributed message queue. Redis = network shared memory. gRPC = typed message passing. The concepts are the same — the scale is different.
Section K

Earning Checkpoint

Skill LearnedTool/MethodPortfolio ArtifactEarning Ready?
Unnamed PipesC, pipe(), fork()Parent-child pipe communication program✅ Yes — demonstrates systems programming skill
Named Pipes (FIFOs)C, mkfifo()Two-process FIFO communication program✅ Yes — shows IPC between independent processes
Shared MemoryC, shmget/shmatWriter-reader shared memory program✅ Yes — key for backend engineering roles
Message QueuesC, POSIX MQPriority message queue sender/receiver✅ Yes — directly applicable to Kafka/RabbitMQ
popen/pcloseCSystem info capture program✅ Yes — useful for DevOps/automation scripts
Python MultiprocessingPython Queue, ProcessMulti-process chat system✅ Yes — immediately usable for startup prototypes
IPC Architecture DesignConceptual + Case StudiesRailway/Razorpay IPC design analysis✅ Yes — system design interview ready
Minimum Viable Earning Setup after this unit: A GitHub repository with 6+ working IPC programs (C + Python) + a blog post explaining "How IPC Powers India's Payment Systems" + LinkedIn profile mentioning systems programming skills = you can apply for backend engineering internships at ₹8,000–₹20,000/month at companies like Razorpay, PhonePe, Zerodha, and CRED.

✅ Unit 9 complete. MCQs: 30. Ready for Unit 10: Capstone!

[QR: Link to EduArtha video tutorial — Inter Process Communication]