What a pipe is
A pipe is a unidirectional byte stream maintained by the kernel, with two ends: a write end and a read end. Bytes written to the write end can be read from the read end in FIFO order. The kernel buffers the data between writes and reads. If the buffer is full, writes block; if it's empty, reads block.
pipe(int pipefd[2]) creates a pipe and fills pipefd[0]
with the read-end fd and pipefd[1] with the write-end fd.
Both are ordinary file descriptors — you use read and write
on them exactly as with files.
The fork-pipe-exec pattern
Pipes are most useful between processes. The pattern is: create the pipe, fork, each process closes the end it doesn't need, then exec. The child inherits both ends; closing the unused end is mandatory — if any process holds the write end open, reads on the read end will block forever waiting for more data rather than seeing EOF.
grep will never
see EOF on its stdin — it blocks forever waiting for more input that never comes.
Every process that inherits a pipe fd must close the end it doesn't use.
EOF semantics
A read on a pipe returns 0 (EOF) when all write ends of the pipe
are closed and the buffer is empty. This is how commands know the upstream is done:
when ls exits, the write end closes, grep's
read returns 0, and grep exits.
SIGPIPE
If you write to a pipe whose read end has no open file descriptors
(all readers have closed or exited), the kernel sends SIGPIPE to the
writing process. The default action is termination. This is why a command producing
large output to a pipe exits cleanly when the reader exits early —
rather than running to completion writing into nothing.
mkfifo("myfifo", 0644) creates a pipe-like file in the filesystem.
Any two processes can open it by name and communicate through it.
Anonymous pipes (from pipe()) only work between related processes
(parent/child sharing the fd via fork).
A pipe is a kernel buffer with two fds — connect two processes by forking, dup2-ing the ends to stdin/stdout, and closing whatever you don't use.