Named pipe
lastmod 2025-07-30created 2018-12-2617 edits
A named pipe is an extension of the ordinary pipe on Unix and Unix-like systems, and is one of the inter-process communication methods. It is also called Pipes or FIFO.
The concept also exists in Microsoft Windows, but the meaning is quite different. An ordinary pipe is a "pipe" and exists only while the processes using it are running. A named pipe is persistent and continues to exist even after the process ends, so it needs to be removed when no longer in use. A named pipe can be treated like a file, and processes open and use it for inter-process communication (IPC). Because of its behavior, a named pipe is also called a FIFO.
Standard I/O Pipe speed test
For a method using the dd command, see Standard streams#Standard I/O Pipe speed test.
Should you use a Lock?
When doing IPC (Inter-Process Communication) with a FIFO file created by mkfifo, you generally don't need to put a separate lock on read and write. A FIFO is a special file managed by the kernel, designed to work safely even when multiple processes access it at the same time. A FIFO guarantees the following behavior by default:
- Atomicity
- Small writes (usually no larger than #PIPE_BUF) are handled atomically, so data does not get mixed up even if multiple processes try to write at the same time.
- Blocking
- A FIFO works in blocking mode by default. That is, read waits until data arrives, and write blocks if there is no process to read. This provides a degree of synchronization between processes.
However, in certain situations an additional synchronization mechanism may be needed:
- Large data transfers
- Transferring data larger than #PIPE_BUF (usually 4KB) requires multiple write calls, and in this case atomicity is not guaranteed. So if you need to transfer large amounts of data, additional synchronization may be needed.
- Ordering guarantees
- A FIFO itself works on a first-in, first-out basis, but when multiple processes access it at the same time, the order of transmission may not be guaranteed. If data must be processed in a specific order, separate synchronization may be needed for that.
Synchronization methods you can use for this include:
- Mutexes or Semaphores
- You can synchronize read or write access using a shared-memory or file-based mutex or semaphore.
- Message Queues
- Message queues provide functionality similar to a FIFO while also offering additional synchronization mechanisms.
- Shared Memory
- You can transfer data using shared memory and use a separate synchronization mechanism to control it.
For typical use cases, the default behavior of a FIFO is enough, but if you have specific synchronization requirements, you can consider additional methods like the ones above.
ENXIO
A process can open a FIFO in non-blocking mode.
In this case, opening for read only will succeed even if no-one has opened on the write side yet,
opening for write only will fail with ENXIO (no such device or address) unless the other end has already been opened.
A process can open a FIFO in non-blocking mode. In this case, opening read-only succeeds even if no one has opened the write side yet, and opening write-only fails with ENXIO (no such device or address) unless the other end is already open.
In other words, for a write-only file, an ENXIO (no such device or address) error occurs if the read-only file is not open.
So when opening in O_NONBLOCK mode, you must open the read-only file first.
PIPE_BUF
The PIPE_BUF system variable defined in the limits.h file specifies the maximum number of bytes guaranteed to be atomic when written to a pipe.
You can check it in the manual page. The description of PIPE_BUF available via man 7 pipe is as follows:
PIPE_BUF
POSIX.1 says that write(2)s of less than PIPE_BUF bytes must be atomic: the output data is written to the pipe as a contiguous sequence.
Writes of more than PIPE_BUF bytes may be nonatomic: the kernel may interleave the data with data written by other processes. POSIX.1 requires
PIPE_BUF to be at least 512 bytes. (On Linux, PIPE_BUF is 4096 bytes.) The precise semantics depend on whether the file descriptor is non‐
blocking (O_NONBLOCK), whether there are multiple writers to the pipe, and on n, the number of bytes to be written:
O_NONBLOCK disabled, n <= PIPE_BUF
All n bytes are written atomically; write(2) may block if there is not room for n bytes to be written immediately
O_NONBLOCK enabled, n <= PIPE_BUF
If there is room to write n bytes to the pipe, then write(2) succeeds immediately, writing all n bytes; otherwise write(2) fails, with
errno set to EAGAIN.
O_NONBLOCK disabled, n > PIPE_BUF
The write is nonatomic: the data given to write(2) may be interleaved with write(2)s by other process; the write(2) blocks until n bytes
have been written.
O_NONBLOCK enabled, n > PIPE_BUF
If the pipe is full, then write(2) fails, with errno set to EAGAIN. Otherwise, from 1 to n bytes may be written (i.e., a "partial
write" may occur; the caller should check the return value from write(2) to see how many bytes were actually written), and these bytes
may be interleaved with writes by other processes.
PIPE_BUF can differ between systems and is usually defined in the limits.h header file. There are several ways to check this value:
You can check it directly in the terminal using the getconf command.
On Linux, the command may not work. Try testing it like this:
If you already have a FIFO file (if not, create one with mkfifo /tmp/my_fifo) and use python, you can check it like this:
import os
# 파이프의 경로 또는 파일 디스크립터를 지정합니다.
pipe_path = '/tmp/my_fifo'
# PIPE_BUF 값을 확인합니다.
pipe_buf_size = os.pathconf(pipe_path, 'PC_PIPE_BUF')
print(f'PIPE_BUF: {pipe_buf_size}')
How fast are Linux pipes anyway?
- [Recommended] How fast are Linux pipes anyway? | GeekNews
Analyzes the performance of Unix pipes as implemented in Linux through incremental optimization
- The bandwidth of the initial simple pipe program is measured at about 3.5GiB/s, and the article walks through improving it more than 20x via profiling and changing system calls
- Explains various optimization techniques, such as using Zero-Copy system calls like vmsplice and splice to reduce unnecessary data copying, and increasing the page size
- Resolves bottlenecks by using Huge Pages and applying a busy loop technique, reaching a throughput of up to 62.5GiB/s
- Provides insights into factors important for high-performance servers and kernel programming, such as pipes, paging, synchronization costs, and Zero-Copy
Overview and introduction
- This article covers how Unix pipes are implemented in Linux, incrementally optimizing performance while writing test programs that read and write data through a pipe
- Starts with a simple program with a bandwidth of roughly 3.5GiB/s and, through various optimizations, achieves about a 20x performance improvement
- Each optimization step is decided based on profiling results from the perf tool, and the related source code is published at GitHub - pipes-speed-test
- The inspiration was a reflection that began after seeing the data processing speed through a pipe in a high-performance FizzBuzz program (36GiB/s)
- Basic knowledge of C is enough to follow the content
Measuring pipe performance: the first slow version
- Running the high-performance FizzBuzz program as an example confirmed that it processes 36GiB of data per second through a pipe
- FizzBuzz outputs in blocks of the L2 cache size (256KiB) to balance memory access and IO overhead
- The pipe performance test program written in this article also repeatedly outputs (read/write) in 256KiB blocks, and implements both the read and write ends directly for measurement
- write.cpp repeatedly writes the same 256KiB buffer, and read.cpp reads 10GiB, terminates, and displays the throughput
- In the test, read/write through the pipe ran at 3.7GiB/s, about 10x slower than FizzBuzz
Bottlenecks and internals of write
- Tracing the call graph with the perf tool while running the program showed that about half of the total time is spent in the pipe write step (i.e., pipe_write)
- Within pipe_write, most of the time is spent copying and allocating memory pages (copy_page_from_iter, __alloc_pages)
- Linux pipes are implemented as a ring buffer, and each entry references a page where the actual data is stored
- The total buffer size of a pipe is fixed; when the pipe is full, write blocks, and when it is empty, read blocks
- In the C structs (pipe_inode_info, pipe_buffer), head and tail indicate the write/read positions respectively, and include offset and length information for individual pages
Pipe read/write logic
- pipe_write works in the following order
- If the pipe is full, wait until space becomes available
- First fill the remaining space at the current head
- If there is more space, allocate a new page, copy data into the buffer, and update head
Zero-Copy: optimizing with Splice/vmsplice
- The general approach to fast IO is to bypass the kernel or minimize copying
- Linux supports skipping copies when moving data between pipes and user space via the splice and vmsplice system calls
- splice: moves data between a pipe and a file descriptor
- vmsplice: moves data between user memory and a pipe
Page-related bottlenecks and using Huge Pages
- perf analysis showed that the bottlenecks of vmsplice are concentrated in the pipe lock (mutex_lock) and page acquisition (iov_iter_get_pages)
- iov_iter_get_pages converts user memory (virtual address) into actual physical pages and stores references to them in the pipe
- Linux paging does not use only 4KiB pages; depending on the architecture, it supports various sizes such as 2MiB (huge page)
- Using Huge Pages (e.g., 2MiB) significantly reduces page translation overhead thanks to fewer page table management operations and references
- Applying huge pages in the program increased the maximum throughput by about another 50%, to 51.0GiB/s
Applying a busy loop
- The remaining bottleneck is synchronization handling, such as wait for space to become available in the pipe and wake for waking the reader
- Uses the SPLICE_F_NONBLOCK option and calls repeatedly in a busy loop when EAGAIN occurs, eliminating kernel scheduling overhead
- Applying this technique improved the maximum throughput by another 25%, to 62.5GiB/s
- A busy loop consumes 100% of CPU resources, but it is a common pattern in high-performance servers
Summary and other notes
- Explains step-by-step how to dramatically improve pipe performance through perf and analysis of the Linux source code
- Lets you experience key issues of high-performance programming, such as pipes, splice, paging, Zero-Copy, and synchronization costs, with real examples
- The actual code applies additional performance tuning, such as allocating buffers on different pages to reduce refcount contention
- The tests were run with each program process pinned to a separate core (taskset)
- The Splice family can be dangerous by design and has long been a point of debate among some kernel developers
Python example
When different Python programs need to exchange data with each other, they can communicate between processes using a named pipe file. For example, if you split a GUI and a data processing program into separate processes, the data processing program sends its results to the GUI program through a named pipe (also called a FIFO).
First, in the first program, create a named pipe (FIFO) file using the os.mkfifo function from Python's built-in module.
Open the created file with write permission and write the desired content with the write() function.
Then, in the second program, open the same named pipe file with read permission and read it with the read() function.
Full source code
Data sending program: send.py
import os.path
FIFO_FILENAME = './fifo-test'
if not os.path.exists(FIFO_FILENAME):
os.mkfifo(FIFO_FILENAME)
if os.path.exists(FIFO_FILENAME):
fp_fifo = open(FIFO_FILENAME, "w")
for i in range(128):
fp_fifo.write("Hello,MakeCode\n")
fp_fifo.write("")
Data receiving program: recv.py
import os.path
FIFO_FILENAME = './fifo-test'
if os.path.exists(FIFO_FILENAME):
fp_fifo = open(FIFO_FILENAME, "r")
i = 0
while True:
with open(FIFO_FILENAME, 'r') as fifo:
data = fifo.read()
line = data.split('\n')
for str in line:
i = i+1
print(str + "%4d" % i)
How to Read in Non-Block mode
- python - How to read named FIFO non-blockingly? - Stack Overflow
- nonblocking read function for python, e.g. for named pipes - Gist
According to the manpage of read(2):
EAGAIN or EWOULDBLOCK
The file descriptor fd refers to a socket and has been marked
nonblocking (O_NONBLOCK), and the read would block.
POSIX.1-2001 allows either error to be returned for this case,
and does not require these constants to have the same value, so
a portable application should check for both possibilities.
So what you're getting is that there is no data available for reading. It is safe to handle the error like this:
try:
buffer = os.read(io, BUFFER_SIZE)
except OSError as err:
if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK:
buffer = None
else:
raise # something else has happened -- better reraise
if buffer is None:
# nothing was received -- do something else
else:
# buffer contains some received data -- do something with it
Make sure you have the errno module imported: import errno.
Full-duplex flow
A FIFO is basically a Half-duplex flow. Since multiple processes share a single file to write to and read from, both cannot write at the same time.
To achieve a Full-duplex flow, you need to create two FIFOs at the same time.
TCP/IP vs Unix Domain Sockets vs Named PIPE
See Unix Domain Socket#TCP/IP vs Unix Domain Sockets vs Named PIPE.
Troubleshooting
OSError: [Errno 6] No such device or address
When there is a mkfifo file named fifo, opening it with a = os.open('./fifo', os.O_WRONLY | os.O_NONBLOCK) produces the following error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 6] No such device or address: './fifo'
To cut to the chase, you must open the read file first. See #ENXIO.
See also
- Inter-process communication (IPC)
- mkfifo
- Dumb Pipe - Connecting two computers with a pipe
Favorite site
- Wikipedia (en) Named pipe
- MSDN: Named Pipes
- [Recommended] Named Pipes in Linux C
- Linux System Programming Chapter 8 IPC