Although pipes are a simple, flexible, and efficient communication mechanism, they have one main drawback—namely, that there is no way to open an already existing pipe. This makes it impossible for two arbitrary processes to share the same pipe, unless the pipe was created by a common ancestor process.
This drawback is substantial for many application programs. Consider, for instance, a database engine server, which continuously polls client processes wishing to issue some queries and which sends the results of the database lookups back to them. Each interaction between the server and a given client might be handled by a pipe. However, client processes are usually created on demand by a command shell when a user explicitly queries the database; server and client processes thus cannot easily share a pipe.
To address such limitations, Unix systems introduce a special file type called a named pipe or FIFO (which stands for “first in, first out”; the first byte written into the special file is also the first byte that is read). Any FIFO is much like a pipe: rather than owning disk blocks in the filesystems, an opened FIFO is associated with a kernel buffer that temporarily stores the data exchanged by two or more processes.
Thanks to the disk inode, however, a FIFO can be accessed by any process, since the FIFO filename is included in the system’s directory tree. Thus, in our example, the communication between server and clients may be easily established by using FIFOs instead of pipes. The server creates, at startup, a FIFO used by client programs to make their requests. Each client program creates, before establishing the connection, another FIFO to which the server program can write the answer to the query and includes the FIFO’s name in the initial request to the server.
In Linux 2.4, FIFOs and
pipes are almost identical and use the
same pipe_inode_info
structures. As a matter of
fact, the read
and write
file
operation methods of a FIFO are implemented by the same
pipe_read( )
and pipe_write( )
functions described in the earlier sections Section 19.1.4 and Section 19.1.5. Actually, there are only
two significant differences:
FIFO inodes appear on the system directory tree rather than on the pipefs special filesystem.
FIFOs are a bidirectional communication channel; that is, it is possible to open a FIFO in read/write mode.
To complete our description, therefore, we just have to explain how FIFOs are created and opened.
A process creates a FIFO by issuing
a mknod( )
[125] system call (see Section 13.2), passing to it as parameters the pathname of
the new FIFO and the value S_IFIFO
(0x1000
) logically ORed with the permission bit
mask of the new file. POSIX introduces a function named
mkfifo( )
specifically to create a FIFO. This call
is implemented in Linux, as in System V Release 4, as a C library
function that invokes mknod( )
.
Once created, a FIFO can be accessed through the usual open( )
, read( )
, write( )
,
and close( )
system calls, but the VFS handles it
in a special way because the FIFO inode and file operations are
customized and do not depend on the filesystems in which the FIFO is
stored.
The POSIX standard specifies the behavior of the open( )
system call on FIFOs; the behavior depends essentially on
the requested access type, the kind of I/O operation (blocking or
nonblocking), and the presence of other processes accessing the FIFO.
A process may open a FIFO for reading, for writing, or for reading and writing. The file operations associated with the corresponding file object are set to special methods for these three cases.
When a process opens a FIFO, the VFS performs the same operations as
it does for device files (see Section 13.2.3). The inode object associated with the opened
FIFO is initialized by a filesystem-dependent
read_inode
superblock method; this method always
checks whether the inode on disk represents a special file, and
invokes if necessary the init_special_inode( )
function. It turn, this function sets the i_fop
field of the inode object to the address of the
def_fifo_fops
table. Later, the kernel sets the
file operation table of the file object to
def_fifo_fops
, and executes its
open
method, which is implemented by
fifo_open( )
.
The fifo_open( )
function initializes the data
structures specific to the FIFO; in particular, it performs the
following operations:
Acquires the i_sem
inode semaphore.
Checks the i_pipe
field of the inode object; if it
is NULL
, it allocates and initializes a new
pipe_inode_info
structure, as in Step 1 in the
earlier section Section 19.1.3.
Depending on the access mode specified as the parameter of the
open( )
system call, it initializes the
f_op
field of the file object with the address of
the proper file operation table (see Table 19-4).
If the access mode is either read-only or read/write, it adds one to
the readers
and r_counter
fields of the pipe_inode_info
structure. Moreover,
if the access mode is read-only and there is no other reading
process, it wakes up any writing process sleeping in the wait queue.
If the access mode is either write-only or read/write, it adds one to
the writers
and w_counter
fields of the pipe_inode_info
structure. Moreover,
if the access mode is write-only and there is no other writing
process, it wakes up any reading process sleeping in the wait queue.
If there are no readers or no writers, it decides whether the function should block or terminate returning an error code (see Table 19-5).
Table 19-5. Behavior of the fifo_open( ) function
Access type |
Blocking |
Nonblocking |
---|---|---|
Read-only, with writers |
Successfully return |
Successfully return |
Read-only, no writer |
Wait for a writer |
Successfully return |
Write-only, with readers |
Successfully return |
Successfully return |
Write-only, no reader |
Wait for a reader |
Return |
Read/write |
Successfully return |
Successfully return |
Releases the inode semaphore, and terminates, returning 0 (success).
The FIFO’s three specialized file operation tables
differ mainly in the implementation of the read
and write
methods. If the access type allows read
operations, the read
method is implemented by the
pipe_read( )
function. Otherwise, it is
implemented by bad_pipe_r( )
, which just returns
an error code. Similarly, if the access type allows write operations,
the write
method is implemented by the
pipe_write( )
function; otherwise, it is
implemented by bad_pipe_w( )
, which also returns
an error code.
[125] In fact, mknod( )
can be used to create nearly any kind of file, such as
block and character device files, FIFOs, and even regular files (it
cannot create directories or sockets, though).