Programming lesson
Mastering Linux Pipes for Interprocess Communication: A Hands-On COP4600 Tutorial
Learn basic pipes, named pipes (FIFOs), and the pipe() system call in C++ with practical examples. This tutorial walks you through the COP4600 Ex7 assignment step by step.
Introduction to Pipes in Linux
Pipes are a fundamental mechanism for interprocess communication (IPC) in Linux. They allow data to flow from one process to another, enabling powerful command chaining and process coordination. In this tutorial, we'll explore three types of pipes: basic pipes (the | operator), named pipes (FIFOs), and the pipe() system call. These concepts are essential for systems programming and are a key part of the COP4600 exercise on pipes.
Think of pipes like a relay race: the output of one runner (process) is handed off to the next runner. In the world of gaming, this is similar to how a game engine passes rendered frames to the display. Understanding pipes helps you build efficient, modular programs.
Part 1: Basic Pipes
What Are Basic Pipes?
A basic pipe is created using the vertical bar | in the shell. It connects the standard output of the left command to the standard input of the right command. For example:
cat result.txt | grep -o "COP4600" | wc -lThis command counts how many times the string "COP4600" appears in result.txt. The output of cat is piped into grep, which extracts matching lines, and then into wc -l to count lines.
Your Task: Detect Failure in part1.o
You are given an executable part1.o that performs operations until it fails. Your C++ program must read the piped output and determine which operation number caused the failure. For instance, if the output shows operations 1 through 11 succeeded and operation 12 failed, your program should print: Program failed on operation 12.
To implement this, write a C++ program that reads from std::cin line by line, parses the operation numbers, and reports the first missing or failed operation. Here's a skeleton:
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main() {
string line;
int expected = 1;
while (getline(cin, line)) {
// Parse operation number from line
// If line doesn't match expected, report failure
}
return 0;
}Test your program by piping part1.o output into it:
./part1.o | ./your_programRun it twice and take a screenshot as required.
Part 2: Named Pipes (FIFOs)
What Are Named Pipes?
Named pipes, or FIFOs (First In, First Out), are similar to basic pipes but exist as a special file in the filesystem. They allow unrelated processes to communicate. A FIFO is created with the mkfifo command:
mkfifo mypipeData written to the FIFO is not stored on disk; the FIFO just acts as a rendezvous point. Writing to a FIFO blocks until a reader opens it, and vice versa.
Your Task: Modify Part 1 to Use Named Pipe
Create a named pipe (e.g., mypipe). Modify your part 1 C++ program to read from the named pipe instead of std::cin. Name your source file lastname_part2.cpp. Use ifstream to open the FIFO:
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main() {
ifstream fifo("mypipe");
string line;
while (getline(fifo, line)) {
// Process as before
}
return 0;
}In one terminal, run ./part1.o > mypipe. In another terminal, compile and run your part 2 program (no redirection needed). The programs will synchronize via the FIFO. Run both twice and capture a screenshot showing both terminals.
Part 3: Pipe System Call
Using pipe() and fork()
The pipe() system call creates an anonymous pipe inside a process. Combined with fork(), it allows parent and child processes to communicate. The pipe provides two file descriptors: fd[0] for reading and fd[1] for writing.
Your Task: Multi-Process Sorting and Median
Write a C++ program named lastname_part3.cpp that:
- Accepts 5 integers as command-line arguments.
- Creates two child processes using
fork(). - Uses four pipes for communication.
- The parent sends the 5 integers to the first child via a pipe.
- The first child sorts the integers, sends the sorted list back to the parent (via pipe) and also to the second child (via another pipe).
- The parent prints the sorted list.
- The second child receives the sorted list, computes the median, and sends it back to the parent.
- The parent prints the median.
Example execution:
./lastname_part3 42 15 8 16 23Expected output (sorted order: 8 15 16 23 42, median: 16):
Sorted: 8 15 16 23 42
Median: 16Here's a code outline for the pipe creation and forking:
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <algorithm>
#include <vector>
using namespace std;
int main(int argc, char* argv[]) {
// Parse 5 integers
vector<int> nums;
for (int i = 1; i <= 5; ++i) nums.push_back(atoi(argv[i]));
// Create pipes: p1 (parent to child1), p2 (child1 to parent), p3 (child1 to child2), p4 (child2 to parent)
int p1[2], p2[2], p3[2], p4[2];
pipe(p1); pipe(p2); pipe(p3); pipe(p4);
pid_t child1 = fork();
if (child1 == 0) {
// Child 1: read from p1, sort, write to p2 and p3
close(p1[1]); close(p2[0]); close(p3[0]); close(p4[0]); close(p4[1]);
// Read 5 ints from p1
// Sort
// Write to p2 and p3
exit(0);
}
pid_t child2 = fork();
if (child2 == 0) {
// Child 2: read from p3, compute median, write to p4
close(p1[0]); close(p1[1]); close(p2[0]); close(p2[1]); close(p3[1]); close(p4[0]);
// Read sorted list from p3
// Compute median
// Write median to p4
exit(0);
}
// Parent: write to p1, read from p2 and p4
close(p1[0]); close(p2[1]); close(p3[0]); close(p3[1]); close(p4[1]);
// Write nums to p1
// Read sorted from p2, print
// Read median from p4, print
wait(nullptr); wait(nullptr);
return 0;
}Compile with g++ -o lastname_part3 lastname_part3.cpp and run with the specified arguments. Take a screenshot of the output.
Conclusion
You've now mastered three ways to use pipes in Linux: basic shell pipes, named pipes (FIFOs), and the pipe() system call. These techniques are crucial for building efficient, modular systems. Whether you're chaining commands in the terminal or designing complex multi-process applications, pipes are your go-to IPC tool.
As you continue your journey, consider how pipes are used in real-world applications like streaming data pipelines, game engines, and even AI model serving. The same principles apply at scale. Happy coding!