OS: Pipes
How do processes talk to each other? One of the simplest mechanisms is a pipe. If you've used Linux, you've already seen pipes:
ls | grep ".txt"
The | symbol takes the output of ls and sends it as input to grep. That's a pipe
A pipe is a one-way communication channel. One process writes into it, another process reads from it. Think of it like a water pipe: water flows in one direction
In C, we create a pipe with the pipe() function
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(void) {
int fd[2];
pipe(fd);
// fd[0] = read end
// fd[1] = write end
pid_t pid = fork();
if (pid == 0) {
// CHILD: write to the pipe
close(fd[0]); // close read end (child only writes)
char msg[] = "Hello from child!";
write(fd[1], msg, strlen(msg) + 1);
close(fd[1]);
} else {
// PARENT: read from the pipe
close(fd[1]); // close write end (parent only reads)
char buf[100];
read(fd[0], buf, sizeof(buf));
printf("Parent received: %s\n", buf);
close(fd[0]);
wait(NULL);
}
return 0;
}
Output: Parent received: Hello from child!
Let's break it down:
- pipe(fd) creates two file descriptors: fd[0] for reading, fd[1] for writing
- We fork; now both parent and child have copies of the pipe
- The child closes the read end (it only needs to write), writes a message, and closes the write end
- The parent closes the write end (it only needs to read), reads the message, and closes the read end
Why do we close the ends we don't use? It's like closing a door you don't need. If the parent doesn't close the write end, the read will never know when the child is done writing: it will wait forever
File descriptors are a big idea. In Unix, everything is a file. Your keyboard is file descriptor 0 (stdin). Your screen is file descriptor 1 (stdout). Error messages go to file descriptor 2 (stderr). A pipe creates two new file descriptors
When you write printf("hello"), it goes to fd 1 (stdout → your screen). When you use scanf, it reads from fd 0 (stdin → your keyboard). Pipes connect the stdout of one process to the stdin of another. That's literally all the | symbol does in a terminal
Pipes are the fundamental building block for process communication in Unix. Shells use them, web servers use them, even Docker uses them internally. The philosophy is simple: small programs that do one thing well, connected by pipes
Mission: The Teleprinter Message
Two processes need to communicate through a pipe, exactly like a message passing from one teleprinter to another over a dedicated line.
- Create a pipe and fork
- The child writes the message "Bell Labs" to the pipe, then closes its ends
- The parent reads the message and prints "Received: Bell Labs", then closes its ends and waits for the child
Example
Your program should print
Received: Bell Labs
Remember to close the pipe ends you do not use