How Linux Pipes and Signals Work Under the Hood
This article explains the internal workings of Linux pipes, named pipes (FIFOs), and signal handling, covering their kernel implementation, synchronization mechanisms, relevant system calls, and provides concrete C code examples for creating and using these inter‑process communication primitives.
Source: https://www.cnblogs.com/biyeymyhjob/archive/2012/11/03/2751593.html
Pipe Overview
Pipes enable communication between related processes, while named pipes (FIFOs) extend this capability to unrelated processes by providing a pathname in the file system.
Implementation Mechanism
In Linux, a pipe is a kernel‑managed circular buffer. One end of the pipe is connected to a process's output (write side) and the other end to a process's input (read side). When the buffer is empty, a read blocks until data is written; when full, a write blocks until space becomes available. The pipe disappears automatically when both processes terminate.
Internally, the pipe does not use a dedicated data structure; instead, two file structures point to the same temporary VFS inode, which in turn references a physical memory page. One file points to the write routine, the other to the read routine, allowing standard file‑operation system calls to be used.
Read/Write Functions
The core source code resides in fs/pipe.c. The two crucial functions are pipe_read() and pipe_write(). Writing copies bytes from the user buffer into the VFS inode’s memory page; reading copies bytes from that page back to the user buffer.
Synchronization is achieved with locks, wait queues, and signals. A write blocks if there is insufficient space or if the buffer is locked by a reader; the writer then sleeps on the inode’s wait queue. Conversely, a read blocks when the buffer is empty, also sleeping on the wait queue. When conditions change, the opposite side is awakened.
Pipe System Call
#include <unistd.h>
int pipe(int filedes[2]); filedes[0]is the read end (close filedes[1] before reading). filedes[1] is the write end (close filedes[0] before writing).
Example Program
int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];
if (pipe(fd) < 0) {
exit(0);
}
if ((pid = fork()) < 0) {
exit(1);
} else if (pid > 0) { /* parent writes */
close(fd[0]);
write(fd[1], "
hello world
", 14);
} else { /* child reads */
close(fd[1]);
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}
exit(0);
}Named Pipe (FIFO)
Because ordinary pipes rely on fork(), they only work between parent/child or sibling processes sharing an ancestor. Linux provides FIFOs to overcome this limitation. A FIFO is a special file type that appears in the file system with a pathname.
When one process opens the FIFO for reading and another opens it for writing, the kernel creates a pipe between them. The FIFO behaves as a first‑in‑first‑out queue, ensuring data order. Deleting the FIFO file removes the pipe.
FIFO System Calls
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);Example to create a FIFO:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo("/tmp/my_fifo", 0777);
if (res == 0) {
printf("FIFO created
");
}
exit(EXIT_SUCCESS);
}Compile and run:
gcc -o fifo1 fifo1.c
./fifo1
ls -lF /tmp/my_fifo # output starts with 'p' indicating a pipeSignal Overview
Signals are asynchronous notifications sent to a process to indicate events such as hardware interrupts, illegal operations, or explicit requests via kill, raise, alarm, setitimer, or sigqueue. Linux supports the traditional signal() interface and the POSIX‑compliant sigaction() interface.
Each signal has a default action (often termination) but can be ignored, caught with a handler, or restored to default. Two signals cannot be ignored: SIGKILL and SIGSTOP.
Signal Handling Mechanism (Kernel Perspective)
The kernel records a pending signal by setting a bit in the process’s signal bitmap. When the process returns from kernel mode to user mode, or wakes from an interruptible sleep, the kernel checks this bitmap. If a handler is installed, the kernel arranges for the process to jump to the handler before resuming normal execution.
For signals that interrupt a system call, the call typically returns -1 with errno set to EINTR. BSD kernels may automatically restart certain system calls.
Special handling exists for SIGCHLD (child termination): the parent receives the signal when a child exits. If the parent has a handler, it should call wait() to reap the child; otherwise the default action is to ignore the signal.
Signal Lifecycle Diagram
Understanding these mechanisms helps developers write robust IPC code that correctly synchronizes reads, writes, and signal handling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
