At this point, all readers should notice a very important thing:
That is the asymmetry between the use of
O_NONBLOCK with O_RDONLY and O_WRONLY. A non-blocking open
for writing fails if no process has
the pipe open for reading. But a non-blocking read
doesn't fail. So, now that we have
seen what a FIFO is, what it does, how it operates and the
associated functions, let's sum up this section by getting some
code up and running.
A few things that need to be mentioned here are:
Using the O_NONBLOCK mode affects how read()
and write()
calls behave on FIFOs. A
read on an empty blocking FIFO will wait until some data can be
read. Conversely, a read on a non-blocking FIFO with no data will
return 0 bytes. A write on a full blocking FIFO will wait until
the data can be written. A write on a FIFO that can't accept all
of the bytes being written will, in most cases, write part of the
data if the request is more than PIPE_BUF
bytes. Another very important point to note here is the size of a
FIFO. There is a "system-imposed"
limit on how much data can be within a FIFO at a given instant of
time. This is the #define PIPE_BUF
which is usually found in limits.h
.
On Linux systems, this is commonly 4096
bytes. On other UNIX systems this value may be
different. To illustrate how unrelated processes can communicate
using named pipes, we create two programs ghosh1.c
and ghosh2.c
, where the first program
is the "producer", cause this creates the pipe, if
required, then writes data to it as quickly as possible; the
second program is the "consumer". It reads and discards
data from the FIFO. So, get moving, and create these two files as
given below.
ghosh1.c : The "producer":
/* Inter-process Communication with
FIFOs:
Note: For illustration purposes, we don't mind what the data
is, so we don't bother to initialize buffer.
By: Subhasish Ghosh
Date: August 17th 2001
Place: Calcutta, WB, India
E-mail:
subhasish_ghosh@linuxmail.org
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME
"/tmp/subhasish"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)
int main()
{
int pipe_fd;
int res;
int open_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[BUFFER_SIZE + 1];
if (access(FIFO_NAME, F_OK) = = -1)
{
res = mkfifo(FIFO_NAME, 0777);
if (res != 0)
{
fprintf (stderr, "Could not create fifo %s\n",
FIFO_NAME);
exit (EXIT_FAILURE);
}
}
printf ("Process %d opening FIFO
O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf ("Process %d result %d\n", getpid(),
pipe_fd);
if (pipe_fd != -1)
{
while (bytes_sent < TEN_MEG)
{
res = write (pipe_fd, buffer, BUFFER_SIZE);
if (res = = -1)
{
fprintf (stderr, "Write error on page\n");
exit (EXIT_FAILURE);
}
bytes_sent += res;
}
(void)close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
printf ("Process %d finished\n", getpid());
exit (EXIT_SUCCESS);
}
ghosh2.c : The "consumer":
/* The consumer program for program
ghosh1:
By: Subhasish Ghosh
Date: August 17th 2001
Place: Calcutta, WB, India
Email:
subhasish_ghosh@linuxmail.org
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME
"/tmp/subhasish"
#define BUFFER_SIZE PIPE_BUF
int main()
{
int pipe_fd;
int res;
int open_mode = O_RDONLY;
char buffer[BUFFER_SIZE + 1];
int bytes_read = 0;
memset (buffer, '\0', sizeof(buffer));
printf ("Process %d opening FIFO O_RDONLY\n",
getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf ("Process %d result %d\n", getpid(),
pipe_fd);
if (pipe_fd != -1)
{
do
{
res = read(pipe_fd, buffer, BUFFER_SIZE);
bytes_read += res;
} while (res > 0);
(void) close(pipe_fd);
}
else
{
exit (EXIT_FAILURE);
}
printf ("Process %d finished, %d bytes read\n",
getpid(), bytes_read);
exit (EXIT_SUCCESS);
}
After creating the files, save them, compile as usual, and
then all we need to do is to use the "time" command to
time the reader. Thus both the programs run at the same time. We
get an output similar to this:
# ./ghosh1 &
[1] 380
Process 380 opening FIFO O_WRONLY
# time ./ghosh2
Process 382 opening FIFO O_RDONLY
Process 380 result 3
Process 382 result 3
Process 380 finished
Process 382 finished, 10485760 bytes read
real 0m0.049s
user 0m0.010s
sys 0m0.040s
[1]+ Done ghosh1
As you might notice, both programs use the FIFO in
blocking mode. We start ghosh1
(the
writer/producer) first, which blocks, waiting for some reader to
open the FIFO. When ghosh2
(the
consumer) starts, the writer is then unblocked and starts writing
data to the pipe. At the same time, the reader starts reading
data from the pipe. This simple pair of programs illustrates the
immense potential and beauty of FIFOs.
Okay, now I have one question for all the readers reading this
article. It is simple, but I am asking this question so that
everyone out there can relate to different facts relating to a
FIFO. My question is: Can anyone tell me, when a process opens a
FIFO, what role does VFS (Virtual Filesystem) play at that time?
The answer is: When a process opens a FIFO, the VFS performs the
same operations as it does for device files. 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 FIFO. The code
snippet maybe represented as below:
if ( inode-> i_mode &
00170000) = = S_IFIFO)
init_fifo (inode);
The init_fifo()
function sets the i_op
field of the inode
object to the address of the fifo_inode_operations
table. The function
also initializes to 0 all the fields of the pipe_inode_info
data structure stored inside the inode object (for pipe_inode_info
data structure, please
refer to the section earlier where I explained the structure
while explaining pipes). I also did mention while explaining
pipes the peculiar behavior of the fields of the pipe_inode_info
data structure, that they
act as "flags" when dealing with pipes, and
"counters" when dealing with FIFOs. The code snippet
mentioned above that sets the "counter-behavior" of the
fields within the pipe_inode_info
data structure is the justification of what I had mentioned
earlier. This is the beauty of the Linux Kernel, where different
entities play different roles when the time comes for them to play
their roles. And thus it takes a lot of passion and hard work on
behalf of Kernel hackers to identify when and why they act in
such a manner. Everything we have covered here in this section on
FIFOs is nearly nothing compared to all that really exists. Anyway, I really
hope everyone had a lot of fun reading it (cause I had a lot of
fun writing it!). So, after dealing with pipe and FIFO, it's high
time that we look into the different System V IPC facilities
available. So, let's move on to the next section and have some
more fun! Hmmmm...
About the Author: My name is Subhasish
Ghosh. I'm 20 years old, currently a computer-systems engineering
student in India; a Microsoft Certified Professional (MCP), MCSD,
MCP certified on NT 4.0, recently completed Red Hat Linux
Certified Engineer (RHCE) Training & cleared Brainbench.com
"Linux General Administration" certification exam. Have
been installing, configuring and developing on Linux patform for
a long time now, have had programmed using C, C++, VC++, VB, COM,
DCOM, MFC, ATL 3.0, PERL, Python, POSIX Threads and Linux Kernel
programming; currently holding a total of 8 International
Industry Certifications. For a list of all my articles at
Linux.com (and other sites), click here.