Pipes - Most minimal form of inter process communication
Source: Dev.to
Introduction
When you need to transfer data between two processes, direct access is not allowed by the operating system. This isolation protects one process from interfering with another (e.g., preventing a malicious program from reading your payment information). To share information safely, processes agree on a communication method. This article focuses on pipes, which are a form of inter‑process communication (IPC). Pipes are not network programming, although they are often a stepping stone toward learning sockets.
Using pipes in the terminal
Pipes can connect the output of one command to the input of another:
$ ls /bin | grep system | more
The shell creates an anonymous pipe, connects the standard output of ls to the standard input of grep, and then connects grep to more.
Anonymous pipes in programs
In programs, anonymous pipes can be used only between processes that share a common ancestor (typically a parent and its child created with fork()). The pipe’s file descriptors exist only in the memory space of the process that created them, so unrelated processes cannot access them.
Example program (C)
#include
#include
#include
#include
#include
#include
int main(void)
{
int pipefd[2];
pid_t child_pid;
const char *payload = "Hello pipes";
char child_buffer;
char child_payload[12] = {0};
if (pipe(pipefd) == -1)
err(EXIT_FAILURE, "pipe");
child_pid = fork();
if (child_pid == -1)
err(EXIT_FAILURE, "fork");
if (child_pid == 0) { // Child process
close(pipefd[1]); // Close write end
printf("Reading payload from parent...\n");
while (read(pipefd[0], &child_buffer, 1) > 0) {
strncat(child_payload, &child_buffer, 1);
}
printf("%s\n", child_payload);
close(pipefd[0]);
exit(EXIT_SUCCESS);
} else { // Parent process
close(pipefd[0]); // Close read end
printf("Writing payload to child process...\n");
if (write(pipefd[1], payload, strlen(payload)) == -1)
err(EXIT_FAILURE, "write");
if (close(pipefd[1]) == -1)
err(EXIT_FAILURE, "close");
if (wait(NULL) == -1) // Wait for child to exit
err(EXIT_FAILURE, "wait");
exit(EXIT_SUCCESS);
}
}
How it works
-
pipe(pipefd)creates a pipe with two file descriptors:pipefd[0]– read endpipefd[1]– write end
-
fork()creates a child process. Both processes inherit the pipe descriptors. -
The parent closes the read end (
pipefd[0]) and writes the string"Hello pipes"to the write end (pipefd[1]). -
The child closes the write end (
pipefd[1]) and reads from the read end (pipefd[0]) one byte at a time, assembling the message. -
Each process closes the descriptor it does not use; failing to do so can cause deadlocks because the reading side would never see an end‑of‑file condition.
-
The parent waits for the child to finish before exiting.
Building and running
$ gcc -o main main.c
$ ./main
The program prints:
Writing payload to child process...
Reading payload from parent...
Hello pipes
Feel free to discuss or ask questions about the implementation.