Go to main content

STREAMS Programming Guide

Exit Print View

Updated: November 2020
 
 

Creating and Opening Pipes and FIFOs

A named pipe, also called a FIFO, is a pipe identified by an entry in a file system's name space. FIFOs are created using mknod, mkfifo, or the mknod command. They are removed using unlink or the rm command. For more information, see the mknod(2), mkfifo(8), mknod(8), unlink(2), and rm(1) man pages.

FIFOs look like regular file system nodes, but are distinguished from them by a p in the first column when the ls -l command is run.

	/usr/sbin/mknod xxx pls -l xxx
prw-r--r-- 1 guest other 0 Aug 26 10:55 xxx
echoput> hello.world>xxx &put>
[1] 8733
cat xxx
hello world
[1]  + Done
rm xxx

FIFOs are unidirectional, that is, one end of the FIFO is used for writing data, the other for reading data. FIFOs allow one-way interprocess communication between unrelated processes. Modules may be pushed onto a FIFO. Data written to the FIFO is passed down the write side of the module and back up the read side as shown in Pushing Modules on a STREAMS-Based FIFO.

Figure 12  Pushing Modules on a STREAMS-Based FIFO

image:Diagram shows a module that has been pushed onto a STREAMS-based FIFO.

FIFOs are opened in the same manner as other file system nodes with open(). Any data written to the FIFO can be read from the same file descriptor in the first-in, first-out manner (serial, sequentially). Modules can also be pushed on the FIFO. For more information about the restrictions that apply when opening a FIFO, see the open(2) man page. If O_NDELAY or O_NONBLOCK is not specified, an open on a FIFO blocks until both a reader and a writer are present.

Named or mounted streams provide a more powerful interface for interprocess communications than does a FIFO. For more information, see Named Streams.

A STREAMS-based pipe, also referred to as an anonymous pipe, is created using pipe, which returns two file descriptors, fd[0] and fd[1], each with its own stream head. The ends of the pipe are constructed so that data written to either end of a pipe may be read from the opposite end.

STREAMS modules can be added to a pipe with I_PUSH ioctl(). A module can be pushed onto one or both ends of the pipe. See Pushing Modules on a STREAMS-Based Pipe. However, if a module is pushed onto one end of the pipe, that module cannot be popped from the other end.

Figure 13  Pushing Modules on a STREAMS-Based Pipe

image:Diagram shows a bi-directional STREAMS-based pipe with two stream heads.

Using Pipes and FIFOs

Pipes and FIFOs can be accessed through the operating system routines, read(2), write(2), ioctl(2), close(2), putmsg(2), putpmsg(2), getmsg(2), getpmsg(2), and poll(2). For FIFOs, open(2) is also used.

Reading From a Pipe or FIFO

read() or getmsg() are used to read from a pipe or FIFO. Data can be read from either end of a pipe. On success, the read() returns the number of bytes read and buffered. When the end of the data is reached, read() returns 0.

When a user process attempts to read from an empty pipe (or FIFO), the following happens:

  • If one end of the pipe is closed, 0 is returned, indicating the end of the file.

  • If the write() side of the FIFO has closed, read() returns 0 to indicate the end of the file.

  • If some process has the FIFO open for writing, or both ends of the pipe are open, and O_NDELAY is set, read() returns 0.

  • If some process has the FIFO open for writing, or both ends of the pipe are open, and O_NONBLOCK is set, read() returns -1 and sets errno to EAGAIN.

  • If O_NDELAY and O_NONBLOCK are not set, the read() call blocks until data is written to the pipe, until one end of the pipe is closed, or the FIFO is no longer open for writing.

For more information, see the read(2) and getmsg(2) man pages.

Writing to a Pipe or FIFO

When a user process calls write(), data is sent down the associated stream. If the pipe or FIFO is empty, that is, if no modules are pushed, the data that is written is placed on the read queue of the other stream for pipes, and on the read queue of the same stream for FIFOs. Since the size of a pipe is the number of unread data bytes, the written data is reflected in the size of the other end of the pipe. For more information, see the write(2) man page.

Zero-Length Writes

If a user process issues write() with 0 as the number of bytes to send a pipe or FIFO, 0 is returned, and, by default, no message is sent down the stream. However, if a user must send a zero-length message downstream, SNDZERO ioctl() can be used to change this default behavior. If SNDZERO is set in the stream head, write() requests of 0 bytes generate a zero-length message and send the message down the stream. If SNDZERO is not set, no message is generated and 0 is returned to the user.

The SNDZERO bit may be changed by the I_SWROPT ioctl(). If the arg in the ioctl() has SNDZERO set, the bit is turned on. If the arg is set to 0, the SNDZERO bit is turned off.

The I_GWROPT ioctl() is used to get the current write() settings. For more information, see the ioctl(2) man page.

Atomic Writes

If multiple processes simultaneously write to the same pipe, data from one process can be interleaved with data from another process, if modules are pushed on the pipe or the write is greater than PIPE_BUF. The order of data that is written is not necessarily the order of data that is read. To ensure that writes of less than PIPE_BUF bytes are not interleaved with data written by other processes, any modules pushed on the pipe must have a maximum packet size of at least PIPE_BUF.


Note - PIPE_BUF is an implementation-specific constant that specifies the maximum number of bytes that are atomic when writing to a pipe. When writing to a pipe, write requests of PIPE_BUF or fewer bytes are not interleaved with data from other processes doing writes to the same pipe. However, write requests of more than PIPE_BUF bytes may have data interleaved on arbitrary byte boundaries with writes by other processes whether or not the O_NONBLOCK or O_NDELAY flag is set.

If the module packet size is at least the size of PIPE_BUF, the stream head packages the data in such a way that the first message is at least PIPE_BUF bytes. The remaining data may be packaged into smaller or equal-sized blocks depending on buffer availability. If the first module on the stream cannot support a packet of PIPE_BUF, atomic write()s on the pipe cannot be guaranteed.

Closing a Pipe or FIFO

close() closes a pipe or FIFO and dismantles its associated streams. On the last close of one end of a pipe, an M_HANGUP message is sent to the other end of the pipe. Subsequent read() or getmsg() calls on that stream head return the number of bytes read and zero when there are no more data. write() or putmsg() requests fail with errno set to EPIPE. If the other end of the pipe is mounted, the last close of the pipe forces it to be unmounted. For more information, see the close(2), read(2), getmsg(2), write(2), and putmsg(2) man pages.

Flushing Pipes and FIFOs

When the flush request is initiated from an ioctl() or from a flushq(), the FLUSHR or the FLUSHW bits of an M_FLUSH message must be switched. Bits are switched when the M_FLUSH message is passed from a write queue to a read queue. This point is also known as the midpoint of the pipe. For more information, see the ioctl(2) and flushq(9F) man pages.

The midpoint of a pipe is not always easily detectable, especially if there are numerous modules pushed on either end of the pipe. In that case, some mechanism needs to intercept all messages passing through the stream. If the message is an M_FLUSH message and it is at the stream midpoint, the flush bits need to be switched.

This bit switching is handled by the pipemod module. pipemod must be pushed onto a pipe or FIFO where flushing of any kind will take place. The pipemod module can be pushed on either end of the pipe. The only requirement is that it is pushed onto an end that previously did not have modules on it. That is, pipemod must be the first module pushed onto a pipe so that it is at the midpoint of the pipe itself. For more information, see the pipemod(4M) man page.

The pipemod module handles only M_FLUSH messages. All other messages are passed to the next module using the putnext() utility routine. If an M_FLUSH message is passed to pipemod and the FLUSHR and FLUSHW bits are set, the message is not processed but is passed to the next module using putnext(). If only the FLUSHR bit is set, it is turned off and the FLUSHW bit is set. The message is then passed to the next module, using putnext(). Similarly, if the FLUSHW bit was the only bit set in the M_FLUSH message, it is turned off and the FLUSHR bit is turned on. The message is then passed to the next module on the stream. For more information, see the putnext(9F) man page.

The pipemod module can be pushed on any stream if it requires the bit switching.

Named Streams

The name of a stream or STREAMS-based pipe often associates the stream with an existing node in the file system name space. This allows unrelated processes to open the pipe and exchange data with the application. The following interfaces support naming a stream or STREAMS-based pipe.

fattach

Attaches a stream file descriptor to a node in the file system name space, thus naming the stream. For more information, see the fattach(3C) man page.

fdetach

Detaches a named stream file descriptor from its node in the file system name space, thus unnaming the stream. For more information, see the fdetach(3C) man page.

isastream

Tests whether a file descriptor is associated with a stream. For more information, see the isastream(3C) man page.

Named streams are useful for passing file descriptors between unrelated processes on the same system. A user process can send a file descriptor to another process by invoking the I_SENDFD ioctl() on one end of a named stream. This sends a message containing a file pointer to the stream head at the other end of the pipe. Another process can retrieve the message containing the file pointer by an I_RECVFD ioctl() call on the other end of the pipe. For more information, see the ioctl(2) man page.

Unique Connections

With named pipes, client processes may communicate with a server process using the connld module which lets a client process get a unique, non-multiplexed connection to a server. connld is a STREAMS-based module that has open(), close(), and put() procedures.

When the named stream is opened, the open() routine of connld is called. The open() fails if:

  • The pipe ends cannot be created.

  • A file pointer and file descriptor cannot be allocated.

  • The stream head cannot stream the two pipe ends.

The open() is not complete and will block until the server process has received the file descriptor using the I_RECVFD ioctl. The setting of the O_NDELAY or O_NONBLOCK flag has no impact on the open routine.

connld does not process messages. All messages are passed to the next object in the stream. The read(), write(), and put() routines call putnext() to send the message up or down the stream. For more information, see the putnext(9F) man page.

The connld module can be pushed onto the named end of the pipe. If the named end of the pipe is then opened by a client, a new pipe is created. One file descriptor for the new pipe is passed back to a client (named stream) as the file descriptor from open() and the other file descriptor is passed to the server using I_RECUFD ioctl(). The server and the client may then communicate through a new pipe. For more information, see the connld(4M), open(2), and ioctl(2) man pages.

Server Sets Up a Pipe shows a server process that has created a pipe and pushed the connld module on the other end. The server then invokes the fattach routine to name the other end /usr/toserv. For more information, see the fattach(3C) man page.

Figure 14  Server Sets Up a Pipe

image:Diagram shows a server process that has created a pipe and pushed the connld module onto the other end of the pipe.

Figure 15  Processes X and Y Open /usr/toserv

image:Diagram shows how STREAMS-based pipes are used to give user processes unique connections to a server.

When process X (procx) opens /usr/toserv, it gains a unique connection to the server process that was at one end of the original STREAMS-based pipe. When process Y (procy) does the same, it also gains a unique connection to the server. As shown in Processes X and Y Open /usr/toserv, the server process has access to three separate pipes through three file descriptors.