Skip to content

10/32 · 5 min

Talking across the boundary

Separate processes have separate memory, which is the point. When they need to cooperate, the kernel provides the channel.

Pipes

A one directional byte stream. What one writes, the other reads.

bash
ls -l | grep '.txt' | wc -l

Three processes, two pipes, running at the same time. grep does not wait for ls to finish; it processes lines as they arrive, which is why a pipeline over a huge file starts producing output immediately.

A pipe has a fixed buffer. A writer filling it blocks until the reader consumes some, which is flow control for free: a fast producer cannot exhaust memory because a slow consumer is not keeping up.

Named pipes are the same thing with a filesystem name, so unrelated processes can use one.

Message queues

Discrete messages rather than a stream, with the kernel preserving boundaries. Send three messages and the reader receives three, not one run together. A receiver can also pick by priority.

Shared memory

The fastest, because after setup there is no kernel involvement at all. Two processes map the same physical pages and read and write them as ordinary memory.

c
int fd = shm_open("/region", O_CREAT | O_RDWR, 0600);
ftruncate(fd, 4096);
void *p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

The catch is that the kernel provides no coordination. Nothing stops both writing at once, so a semaphore or mutex has to be added, and now you have all the problems of threads with none of the convenience.

Sockets

Work between processes on one machine (Unix domain sockets) and across a network (TCP, UDP) with the same interface. Slower than shared memory, and the only option once the processes are on different machines, which is why every distributed system ends up here.

MechanismSpeedBoundaries keptAcross machines
Pipefastno, a byte streamno
Message queuefastyesno
Shared memoryfastestn/a, raw memoryno
Socketslowerdepends on protocolyes

Signals

Not really communication: one number, asynchronously delivered. SIGTERM asks a process to stop, SIGKILL cannot be caught and ends it immediately.

The difference matters when writing a service. Handling SIGTERM is how a process finishes its current request and closes connections cleanly, and a container runtime sends exactly that before resorting to SIGKILL.