Pipes and Queues
This chapter covers communication between processes using STREAMS-based pipes and named pipes. Discussion is limited to communications between applications.
Overview of Pipes and FIFOs
A pipe in the SunOS 5.6 system provides a communication path between multiple processes. Prior to the SunOS 5.0 release, SunOS had standard pipes and named pipes (also called FIFOs). With standard pipes, one end was opened for reading and the other end for writing, so data flow was unidirectional. FIFOs had only one end and typically one process opened the file for reading and another process opened the file for writing. Data written into the FIFO by the writer could then be read by the reader.
To provide greater support and development flexibility for networked applications, pipes and FIFOs are STREAMS-based in the SunOS 5 software. The interface is unchanged but the underlying implementation is changed. When a pipe is created through the pipe(2) interface, two streams are opened and connected. Data flow is serial.
The remainder of this chapter uses the terms pipe and STREAMS-based pipe interchangeably to mean a STREAMS-based pipe.
Note - After both ends of a FIFO have been opened, there is no guarantee that further calls to open O_RDONLY (O_WRONLY) will synchronize with later calls to open O_WRONLY (O_RDONLY) until both ends of the FIFO have been closed by all readers and writers. Any data written into a FIFO will be lost if both ends of the FIFO are closed before the data is read.
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(2), mkfifo(3C), or the mknod(1M) command. They are removed using unlink(2) or the rm(1) command.
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 simple 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 Figure 6-1.
Figure 6-1 Pushing Modules on a STREAMS-based FIFO
FIFOs are opened in the same manner as other file system nodes with open(2). 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. See open(2) for the restrictions that apply when opening a FIFO. 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. See "Named Streams" for details.
A STREAMS-based pipe, also referred to as an anonymous pipe, is created using pipe(2), 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(2). A module can be pushed onto one or both ends of the pipe (see Figure 6-2). However, if a module is pushed onto one end of the pipe, that module cannot be popped from the other end.
Figure 6-2 Pushing Modules on a STREAMS-based Pipe
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(2) or getmsg(2)) are used to read from a pipe or FIFO. Data can be read from either end of a pipe. On success, the read(2) returns the number of bytes read and buffered. When the end of the data is reached, read(2) 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(2) 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(2) 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(2) 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.
Writing to a Pipe or FIFO
When a user process calls write(2), data is sent down the associated stream. If the pipe or FIFO is empty (no modules 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.
Zero-Length Writes
If a user process issues write(2) 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(2) can be used to change this default behavior. If SNDZERO is set in the stream head, write(2) 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(2). If the arg in the ioctl(2) 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(2) is used to get the current write settings.
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 should 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 writes on the pipe cannot be guaranteed.
Closing a Pipe or FIFO
close(2) 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(2) or getmsg(2) calls on that stream head return the number of bytes read and zero when there are no more data. Subsequent write(2) or putmsg(2) 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.
Flushing Pipes and FIFOs
When the flush request is initiated from an ioctl(2) or from flushq(9F), the FLUSHR or the FLUSHW bits of an M_FLUSH message must be switched. Bits are switched at the point where 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.
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 should be pushed onto a pipe or FIFO where flushing of any kind will take place. The pipemod(7M) 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(7M) must be the first module pushed onto a pipe so that it is at the midpoint of the pipe itself.
The pipemod(7M) module handles only M_FLUSH messages. All other messages are passed to the next module using the putnext(9F) utility routine. If an M_FLUSH message is passed to pipemod(7M) and the FLUSHR and FLUSHW bits are set, the message is not processed but is passed to the next module using putnext(9F). 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(9F). 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.
The pipemod(7M) 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.
Named streams are useful for passing file descriptors between unrelated processes on the same machine. A user process can send a file descriptor to another process by invoking the I_SENDFD ioctl(2) 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(2) call on the other end of the pipe.