Introduction

Processes are isolated by design: each has its own address space, and one cannot read another's variables. Yet many tasks need cooperating processes: a shell pipeline passes output from one program to the next, a database server answers queries from clients, a browser splits work among several processes. Inter-process communication (IPC) is the set of mechanisms the OS provides for such processes to exchange data and coordinate.

There are two fundamental models. In shared memory, the kernel maps the same physical memory into two address spaces, and the processes then read and write it directly. In message passing, processes call send and receive, and the kernel carries the data. Pipes, message queues, sockets and signals are all forms of message passing. The choice trades speed against simplicity and safety.

Prerequisites

Core concepts

The two models

AspectShared memoryMessage passing
SetupSystem calls to create and attach a region (shm_open plus mmap, or shmget plus shmat)A channel: pipe, queue, socket, or none for direct addressing
Per transferOrdinary loads and stores; no kernel involvementOne system call to send and one to receive
Copies per transferNone beyond the write itselfTypically two: sender to kernel, kernel to receiver
SynchronizationProgrammer's job (semaphores, mutexes)Built in: receive blocks until data arrives
Best forLarge or frequent data between processes on one machineSmall messages, distributed systems, simpler correctness

Shared memory is faster, message passing is safer. Once set up, shared memory costs nothing per access, but conflicting accesses with at least one writer need suitable synchronization and memory ordering; read-only or disjoint access need not be locked. Message passing pays a system call and a copy per message but cannot corrupt the other side's memory.

Shared memory versus message passing
Figure 1. Left: processes A and B both map one shared region and access it with ordinary loads and stores. Right: A sends message M through the kernel, which copies it once from A and once to B.

Message-passing design choices

  • Naming. Direct: send(P, msg), receive(Q, msg); a link connects exactly one pair. Indirect: messages go to a mailbox (port); several processes may share one mailbox, and a pair may share several.
  • Synchronization. A blocking send waits until the message is received (or accepted by the mailbox); a non-blocking send returns at once. A blocking receive waits until a message is available; a non-blocking receive returns a message or "none". Blocking send plus blocking receive is a rendezvous.
  • Buffering. The link's queue has zero capacity (sender must block until the receiver takes the message, so it is a rendezvous), bounded capacity nn (sender blocks only when the queue is full), or unbounded capacity (sender never blocks).

Pipes

An ordinary (anonymous) pipe is a one-way byte stream with a read end and a write end, created by pipe(fd): fd[0] is for reading and fd[1] for writing. It exists only while some process holds it open, and it is typically shared by related processes through inherited descriptors, usually a parent and child after fork. Unrelated processes need another way to obtain a descriptor, such as descriptor passing. Two-way communication needs two pipes.

C
int fd[2]; char buf[16];
pipe(fd);
if (fork() == 0) {             /* child: reader */
    close(fd[1]);
    ssize_t n = read(fd[0], buf, sizeof buf);   /* blocks until data or EOF */
    /* n == 5 and buf starts with "hello" */
    _exit(0);
}
close(fd[0]);                  /* parent: writer */
write(fd[1], "hello", 5);
close(fd[1]);                  /* reader then sees end-of-file */
wait(NULL);

Pipe rules worth knowing: read on an empty pipe blocks while some writer still has it open, and returns 0 (end of file) once all write ends are closed; write on a full pipe blocks; writing to a pipe with no reader raises the signal SIGPIPE. A process must close the ends it does not use, or the reader never sees end of file.

A named pipe (FIFO, created by mkfifo) has a name in the file system, outlives its creator and can connect unrelated processes on one machine.

Other mechanisms

  • Message queues: kernel-held lists of typed messages (System V msgsnd and msgrcv, POSIX mq_send).
  • Sockets: endpoints identified by an address and port; they work between machines. See Socket API.
  • Signals: ordinary/basic numbered asynchronous notifications (for example SIGINT, SIGKILL, SIGCHLD); a handler runs or a default action (often termination) happens. Queued real-time/POSIX signals may carry a small value.
  • Remote procedure call (RPC): makes a message exchange look like a function call; parameters are marshalled into a message.

How shared memory is used: the bounded buffer

A producer and a consumer share an array of nn slots with two indices: in (next free slot) and out (next full slot).

Text
shared: buffer[0..n-1], in <- 0, out <- 0

PRODUCER:                              CONSUMER:
  loop                                   loop
    produce item                           while in = out do nothing     (empty)
    while (in + 1) mod n = out             item <- buffer[out]
        do nothing                (full)   out <- (out + 1) mod n
    buffer[in] <- item                     consume item
    in <- (in + 1) mod n

Why only n−1n - 1 slots are used. With only in and out, "empty" is in = out. If all nn slots could fill, in would also equal out when full, and the two cases could not be told apart. So the producer stops one slot early: the buffer holds at most n−1n - 1 items. Adding a separate count variable (or semaphores, as in Classical Synchronization Problems) allows all nn slots to be used, but a shared count updated by both sides is a race unless protected.

The loops above busy-wait. This version is correct only for one producer and one consumer, because each index is written by exactly one side.

Worked examples

Bounded buffer capacity

With n=8n = 8 and the in/out scheme, a producer that runs alone fills slots 0 to 6 and then spins: 7 items, in =7= 7, out =0= 0, and (7+1) mod 8=0=(7 + 1) \bmod 8 = 0 = out, so the buffer reports full. After the consumer removes 3 items, out =3= 3 and the producer can add 3 more, wrapping in round to 2.

Cost of moving 1 MiB

Process A sends 1 MiB (1,048,576 bytes) to process B.

  • Message passing with 4 KiB messages: 1048576/4096=2561048576 / 4096 = 256 messages, so 256 send and 256 receive calls, 512 system calls in all, and each byte is copied twice (into and out of the kernel): 2 MiB of copying.
  • Shared memory: a fixed number of setup calls, after which A writes the data straight into the shared region. No per-message system calls, but A and B must still synchronize (for example one semaphore signal when the data is complete).

Important properties and observations

  • Message passing gives synchronization for free (a blocking receive waits for data); shared memory gives speed and needs explicit synchronization.
  • A zero-capacity link forces both sides to meet: the sender blocks until the receiver takes the message.
  • An ordinary pipe is unidirectional and is usually shared through inherited descriptors by related processes; unrelated processes can use descriptor passing. A named FIFO can instead be opened by path.
  • Shared memory regions persist until removed (System V segments even survive the processes that created them); this is a common source of leaks.

Common misconceptions.

  • "Shared memory needs a system call for every access." Only for setup; accesses are ordinary memory operations.
  • "A pipe can be read by both ends." A pipe has one read end and one write end.
  • "Message passing cannot deadlock." Two processes that each block in receive waiting for the other deadlock just as surely.

GATE examination perspective

  • Model comparison. Shared memory: fastest, kernel only at setup, explicit synchronization. Message passing: system call per message, kernel copies, built-in blocking.
  • Bounded buffer with in and out. At most n−1n - 1 items; full is (in+1) mod n=out(in + 1) \bmod n = out, empty is in=outin = out.
  • Blocking semantics. Know the four combinations of blocking and non-blocking send and receive; zero capacity implies rendezvous.
  • Pipe behaviour. Read blocks on empty while writers exist; read returns 0 when the pipe is empty and all write ends are closed; unidirectional; inherited through fork.
  • Counting system calls. Messages times two (send and receive); bytes copied twice.

Summary

Sources

  1. A. Silberschatz, P. B. Galvin, G. Gagne, Operating System Concepts, 10th ed., Wiley: Chapter 3 (3.4 Interprocess Communication, 3.5 IPC in Shared-Memory Systems, 3.6 IPC in Message-Passing Systems, 3.7 Examples of IPC Systems).
  2. A. S. Tanenbaum, H. Bos, Modern Operating Systems, 4th ed., Pearson: Section 2.3.8 Message Passing.

Next steps