UNIX Pipes
A pipe is one of the simplest and most efficient Inter-Process Communication (IPC) mechanisms provided by UNIX. It creates a unidirectional communication channel through which one process writes data while another process reads the same data in the order it was written.
Learning Objectives
- Understand anonymous pipes.
- Learn the
pipe()system call. - Understand pipe architecture.
- Study file descriptor allocation.
- Learn parent-child communication.
- Prepare for practical programming examples.
1. What is a Pipe?
A pipe is a kernel-managed communication channel that allows one
process to send a stream of bytes to another process.
A pipe behaves like a First-In First-Out (FIFO) byte stream. Data is
read in exactly the same order in which it is written.
Anonymous pipes are typically used between related processes, such as a
parent process and its child created using fork().
2. Why are Pipes Needed?
| Requirement | Benefit |
|---|---|
| Parent-child communication | Simple data exchange |
| Process cooperation | Exchange computation results |
| Command pipelines | Connect multiple commands |
| Kernel-managed buffering | Temporary data storage |
| Simple IPC | Minimal programming effort |
3. Pipe Architecture
The kernel owns the pipe buffer. One process writes bytes into the buffer, while another process reads bytes from it.
4. Anonymous Pipe
An anonymous pipe exists only while the communicating processes are
running.
It has no pathname in the file system and cannot normally be accessed
by unrelated processes.
Anonymous pipes are most commonly created before calling
fork() so that both parent and child inherit the same
pipe file descriptors.
5. pipe()
The pipe() system call creates an anonymous pipe and
returns two file descriptors.
| Return Value | Description |
|---|---|
| 0 | Pipe created successfully. |
| -1 | Error occurred. |
6. Pipe File Descriptors
| Descriptor | Purpose |
|---|---|
fd[0] |
Read end of the pipe. |
fd[1] |
Write end of the pipe. |
7. How pipe() Works
The application receives two file descriptors that reference opposite ends of the same kernel pipe object.
8. Parent-Child Communication
A common programming pattern is:
- Create a pipe.
- Call
fork(). - Parent and child inherit both file descriptors.
- Each process closes the unused end.
- One process writes data.
- The other process reads data.
9. Advantages of Pipes
- Simple API.
- Kernel-managed buffering.
- Efficient for related processes.
- Automatic synchronization for byte-stream transfer.
- Widely supported across UNIX systems.
10. Interview Questions
- What is a pipe?
- Why is a pipe considered an IPC mechanism?
- What does the
pipe()system call return? - What is stored in
fd[0]? - What is stored in
fd[1]? - Why are pipes generally used between related processes?
- Who owns the pipe buffer?
- What type of data structure does a pipe behave like?
- Why is
fork()commonly used with pipes? - What happens if both processes keep both pipe ends open?
11. File Descriptor Inheritance
When a process calls fork(), the child inherits copies of
all open file descriptors from the parent, including both ends of any
pipes.
Both processes initially have access to:
fd[0](Read End)fd[1](Write End)
12. Closing Unused Pipe Ends
Each process should close the pipe end that it does not use. Closing unused descriptors:
- Reduces resource usage.
- Allows proper End-of-File detection.
- Prevents accidental writes or reads.
- Avoids communication deadlocks.
| Process | Close | Keep Open |
|---|---|---|
| Writer | fd[0] |
fd[1] |
| Reader | fd[1] |
fd[0] |
13. Writing to a Pipe
Data is written into the pipe using the standard
write() system call.
The write end of the pipe must be open.
14. Reading from a Pipe
Data is read using the standard
read() system call.
The read operation retrieves bytes in FIFO order.
15. End-of-File Detection
A call to read() returns 0, indicating
End-of-File (EOF), only after all write descriptors referring to the
pipe have been closed and no more buffered data remains.
16. Blocking Behavior
| Operation | Behavior |
|---|---|
read() |
Blocks if no data is available and at least one write end remains open. |
write() |
Blocks if the pipe buffer is full (unless non-blocking mode is enabled). |
Blocking behavior allows the kernel to synchronize communication between the writer and reader.
17. Example: Parent Writes, Child Reads
18. Pipe Communication Timeline
19. Linux Commands
| Command | Purpose |
|---|---|
man 2 pipe |
Display the pipe() system call manual. |
man 7 pipe |
Display pipe semantics and behavior. |
ls -l /proc/<pid>/fd |
Display open file descriptors. |
lsof -p <pid> |
Display open files for a process. |
20. Interview Questions
- Why does
fork()inherit pipe descriptors? - Why should unused pipe ends be closed?
- What happens if the writer never closes the write end?
- When does
read()return 0? - Can both parent and child write to the same pipe?
- What causes a blocking
read()? - What causes a blocking
write()? - Why is the communication called FIFO?
- How is EOF generated for a pipe?
- Which system calls are most commonly used with
pipe()?
21. Half-Duplex vs Full-Duplex Communication
A single anonymous pipe provides communication in only one direction. If two-way communication is required, two separate pipes are normally created.
| Communication Type | Number of Pipes |
|---|---|
| Half-Duplex | One Pipe |
| Full-Duplex | Two Pipes |
22. PIPE_BUF
POSIX defines PIPE_BUF as the maximum number of bytes
that can be written atomically to a pipe.
If a write request is less than or equal to
PIPE_BUF, the kernel guarantees that the bytes from that
single write operation will not be interleaved with bytes written by
another process to the same pipe.
23. Atomic Writes
| Write Size | Behavior |
|---|---|
| ≤ PIPE_BUF | Atomic write guaranteed. |
| > PIPE_BUF | May be split into multiple writes and interleaved with data from other writers. |
24. Broken Pipe
If every read descriptor referring to a pipe has been closed and a
process attempts to write to the pipe, the write operation fails.
The kernel generates the SIGPIPE signal for the writing
process. If the signal is ignored or handled, the
write() call fails and returns -1 with
errno set to EPIPE.
25. Pipe Capacity
A pipe uses a finite kernel buffer.
When the buffer becomes full, additional blocking
write() operations wait until the reader consumes data.
The actual pipe capacity is implementation dependent and may vary
between UNIX and Linux systems.
26. Non-blocking Pipes
A pipe may be placed into non-blocking mode using
fcntl().
In non-blocking mode:
read()returns immediately if no data is available.write()returns immediately if sufficient buffer space is unavailable.
27. Example: Two-Way Communication
Using two pipes enables bidirectional communication between the parent and child processes.
28. Linux Commands
| Command | Description |
|---|---|
getconf PIPE_BUF /tmp |
Display the value of PIPE_BUF for a pathname. |
cat /proc/sys/fs/pipe-max-size |
Display the maximum permitted pipe size (Linux). |
man 7 pipe |
Display detailed pipe semantics. |
strace ./program |
Trace pipe(), read(), and write() system calls. |
29. Interview Questions
- Why is an anonymous pipe called half-duplex?
- How can full-duplex communication be implemented using pipes?
- What is
PIPE_BUF? - What is an atomic pipe write?
- When can data from multiple writers become interleaved?
- What causes the
SIGPIPEsignal? - What error code is associated with a broken pipe?
- What happens when a pipe buffer becomes full?
- Why would an application use non-blocking pipes?
- Which API is commonly used to enable non-blocking mode?
30. Pipe API Summary
| Function | Purpose |
|---|---|
pipe() |
Create an anonymous pipe. |
read() |
Read bytes from the pipe. |
write() |
Write bytes to the pipe. |
close() |
Close one end of the pipe. |
fork() |
Create a child process that inherits the pipe descriptors. |
fcntl() |
Configure pipe attributes such as non-blocking mode. |
31. Pipe Lifecycle
The kernel automatically destroys the pipe after all file descriptors that refer to it have been closed.
32. Pipe Comparison
| Feature | Pipe | FIFO | Socket | Shared Memory |
|---|---|---|---|---|
| Related Processes | ✔ | ✔ | ✔ | ✔ |
| Unrelated Processes | ✘ | ✔ | ✔ | ✔ |
| Byte Stream | ✔ | ✔ | ✔ | ✘ |
| Named in File System | ✘ | ✔ | UNIX Domain Only | ✘ |
| Network Support | ✘ | ✘ | ✔ | ✘ |
| Communication Direction | Half-duplex | Half-duplex | Full-duplex | Bidirectional |
33. Best Practices
- Create the pipe before calling
fork(). - Close unused pipe ends immediately.
- Always check return values from
pipe(),read(), andwrite(). - Handle
SIGPIPEor check forEPIPEwhen writing. - Use two pipes when bidirectional communication is required.
- Keep messages within
PIPE_BUFwhen atomic writes are important. - Close all descriptors before process termination.
34. Common Programming Mistakes
- Forgetting to close the unused pipe end.
- Reading from the write descriptor.
- Writing to the read descriptor.
- Ignoring blocking behavior.
- Assuming one pipe provides bidirectional communication.
- Ignoring return values from
read()andwrite(). - Failing to handle broken pipes.
35. Debugging Tips
| Problem | Possible Cause |
|---|---|
| Program blocks forever | Unused write descriptor still open. |
| Broken Pipe | Reader closed before writer. |
| No EOF | Writer descriptor not closed. |
| Partial Communication | Incorrect descriptor closed. |
| Interleaved Data | Write larger than PIPE_BUF. |
36. Advanced Interview Questions
- Why must a pipe be created before
fork()? - Explain how the kernel manages a pipe buffer.
- Why is a pipe considered a byte-stream device?
- How does the kernel detect End-of-File for a pipe?
- Why are atomic writes important in multi-process communication?
- Explain the significance of
PIPE_BUF. - What happens internally when the last descriptor referencing a pipe is closed?
- How does non-blocking mode affect
read()andwrite()? - When should sockets be preferred over pipes?
- How would you implement bidirectional communication using anonymous pipes?
37. Module Summary
This module introduced anonymous pipes as one of the simplest UNIX
Inter-Process Communication mechanisms.
We studied the pipe() system call, kernel pipe buffers,
file descriptor inheritance after fork(), reading and
writing using standard I/O system calls, End-of-File detection,
blocking behavior, PIPE_BUF, atomic writes,
non-blocking operation, broken pipes, debugging techniques, and
recommended programming practices.
Anonymous pipes are well suited for communication between related
processes. The next module extends this concept to named pipes
(FIFOs), which enable communication between unrelated processes.
Concept Mastery Assessment — Module 40
Verify your conceptual understanding with 12 targeted technical assessment questions. Instant feedback and explanations are provided for every choice.