Skip to content
Search

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:

#include <stdio.h>
#include <limits.h>

int main() {
    printf("PIPE_BUF: %d\n", PIPE_BUF);
    return 0;
}

You can check it directly in the terminal using the getconf command.

getconf PIPE_BUF

On Linux, the command may not work. Try testing it like this:

getconf -a | grep PIPE_BUF

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?

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
  • All operations are protected by a lock, which incurs synchronization overhead
  • Read uses the same structure, moving tail and freeing pages that have been read
  • Essentially, data is copied twice, from user memory to the kernel and from the kernel back to user space, incurring considerable overhead

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
  • Both system calls only move references, without actually moving the data
  • For example, when using vmsplice, the 256KiB buffer is split in half and each half is vmspliced into the pipe alternately, in a double-buffering scheme
  • In practice, applying vmsplice improves speed more than 3x (about 12.7GiB/s), and applying splice on the read side further improves it to 32.8GiB/s
  • 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.

os.mkfifo(FIFO_FILENAME)

Open the created file with write permission and write the desired content with the write() function.

fp_fifo = open(FIFO_FILENAME, "w")
fp_fifo.write("Hello,MakeCode\n")

Then, in the second program, open the same named pipe file with read permission and read it with the read() function.

fp_fifo = open(FIFO_FILENAME, "r")
data = fifo.read()

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

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

Favorite site

author Zer0 sitemap