Inter-Process Communication

Processes have separate address spaces (Module 10), so they need kernel-provided mechanisms to cooperate:

Concept

Processes have separate address spaces (Module 10), so they need kernel-provided mechanisms to cooperate:

One-way byte stream between related processes pipe(); shell a | b FIFO (named pipe) Pipe with a name in the file system mkfifo; unrelated processes Message queue Discrete messages with priorities POSIX mq_open, System V msgget Shared memory

Same physical pages mapped into several processes Fastest (no copying) but needs synchronization; shm_open + mmap, or mmap(MAP_SHARED) before fork Synchronization between processes sem_open (named) or in shared memory

Byte streams or datagrams, local (Unix domain) or network Same API for local and remote Memory-mapped files Share a file's contents mmap of a file Higher level

D-Bus, gRPC, SOME/IP (automotive), message brokers Built on the mechanisms above

Message passing (pipes, queues, sockets): the kernel copies data; simple to synchronize, works across machines.

Shared memory: no copying; you must synchronize access yourself.

Why It Matters

Shells, databases (PostgreSQL uses shared memory), browsers (process per tab + IPC), Android (Binder), automotive middleware (SOME/IP, AUTOSAR communication) and microservices are all built on IPC.

Syntax

int fd[2]; pipe(fd);                         /* fd[0] read end, fd[1] write end */
void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
int sv[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

Example

17.1 Pipe, shared memory and a socket pair between parent and child 17.2 A named pipe from the shell

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdatomic.h>

int main(void)
{
    /* 1. pipe: a one-way byte stream, parent -> child */
    int fd[2];
    pipe(fd);
    fflush(stdout);
    if (fork() == 0) {
        close(fd[1]);
        char buf[64] = {0};
        ssize_t n = read(fd[0], buf, sizeof buf - 1);
        printf("pipe  : child read %zd bytes: \"%s\"\n", n, buf);
        fflush(stdout);                     /* _exit() does not flush stdio buffers */
        _exit(0);
    }
    close(fd[0]);
    write(fd[1], "22 F1 90", 8);
    close(fd[1]);
    wait(NULL);

    /* 2. shared memory: both processes see the same physical pages */
    struct { atomic_int ready; int value; } *shm =
        mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    shm->ready = 0;
    if (fork() == 0) {
        shm->value = 12600;                                  /* written by the child ... */
        atomic_store(&shm->ready, 1);
        _exit(0);
    }
    wait(NULL);
    printf("shm   : parent sees value %d (ready=%d) written by the child\n", shm->value, atomic
…
$ mkfifo telemetry && ls -l telemetry | cut -c1-10
prw-r--r--
$ ( echo "speed=88" > telemetry & ) ; cat telemetry
speed=88
$ echo "12.6 12.4 12.5" | tr ' ' '\n' | sort -n | head -1
12.4

Expected Result

The pipe carried the 8 bytes of a UDS request from parent to child.

The child's write to the shared mapping was visible to the parent — unlike ordinary memory after fork (Module 10), because the mapping is MAP_SHARED.

The socket pair worked in both directions: request and response.

The p at the start of the permissions marks a FIFO; the writer blocked until a reader opened it.

pipe  : child read 8 bytes: "22 F1 90"
shm   : parent sees value 12600 (ready=1) written by the child
socket: request PING, response ACK:PING
$ mkfifo telemetry && ls -l telemetry | cut -c1-10
prw-r--r--
$ ( echo "speed=88" > telemetry & ) ; cat telemetry
speed=88
$ echo "12.6 12.4 12.5" | tr ' ' '\n' | sort -n | head -1
12.4

Common Mistakes

Not closing unused pipe ends → the reader never sees end-of-file.

Shared memory without synchronization (races between processes, Module 15).

Assuming one write equals one read on a stream (a stream has no message boundaries; frame your messages).

Forgetting to remove named IPC objects (shm_unlink, mq_unlink, FIFO files).

Best Practices

Use sockets or message queues by default; switch to shared memory for large, high-rate data with clear synchronization (e.g. a ring buffer with atomics).

Frame messages with a length prefix or delimiter.

Set permissions on named IPC objects; validate everything received from another process.

Interview Relevance

High: "list IPC mechanisms and compare them", "pipe vs FIFO?", "why is shared memory fastest?", "message passing vs shared memory?", "how does ls | wc -l work?".

Practice Questions

Implement ls | wc -l in C with pipe, fork, dup2 and exec.

Build a ring buffer in shared memory with a producer and a consumer process.

Send a message with POSIX mq_send and receive it in another process (link with -lrt).

Measure throughput of pipe vs shared memory for 1 GB of data.

Quick Quiz

Q1. (Multiple choice) Which IPC mechanism avoids copying data through the kernel? A) Pipe B) Socket C) Shared memory D) Message queue Answer: C.

Q2. (Predict the output) What does ls -l show as the first character for a FIFO?

Answer: p (verified: prw-r--r--).

Q3. (True/False) A pipe is bidirectional.

Answer: False. Use two pipes or a socket pair for two-way communication.

Q4. (Find the bug) The parent writes to a pipe and waits for the child reading until EOF, but the child hangs forever.

Answer: Some process (often the child itself) still holds the write end open; close all unused write ends so read can return 0.

Coding Challenge

Implement the shell pipeline echo hello world | wc -w in C: create a pipe, fork two children, connect them with dup2, exec the two programs and wait for both.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    int fd[2];
    if (pipe(fd) < 0) { perror("pipe"); return 1; }
    fflush(stdout);
    if (fork() == 0) {                      /* left side: stdout -> pipe */
        dup2(fd[1], STDOUT_FILENO);
        close(fd[0]); close(fd[1]);
        execlp("echo", "echo", "hello", "world", (char *)NULL);
        _exit(127);
    }
    if (fork() == 0) {                      /* right side: stdin <- pipe */
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]); close(fd[1]);
        execlp("wc", "wc", "-w", (char *)NULL);
        _exit(127);
    }
    close(fd[0]); close(fd[1]);             /* parent must close both ends or wc never sees EOF */
    wait(NULL); wait(NULL);
    printf("pipeline finished\n");
    return 0;
}