Detailed Review


While the pipe function is the majority of the pipe model, there are a couple of other functions that we should discuss in their applicability toward pipe-based programming. Table 11.1 lists the functions that we ll detail in this chapter.

Table 11.1: API Functions for Pipe Programming

API Function

Use

pipe

Create a new anonymous pipe

dup

Create a copy of a file descriptor

mkfifo

Create a named pipe (fifo)

We ll also look at some of the other functions that are applicable to pipe communication, specifically those that can be used to communicate using a pipe.

Note  

Remember that a pipe is nothing more than a pair of file descriptors, and therefore any functions that operate on file descriptors can be used. This includes but is not restricted to select, read, write, fcntl, freopen , and such.

pipe

The pipe API function creates a new pipe, represented by an array of two file descriptors. The pipe function has the following prototype:

 #include <unistd.h>     int  pipe  (int fds[2]); 

The pipe function returns zero on success, or -1 on failure, with errno set appropriately. On successful return, the fds array (which was passed by reference) is filled with two active file descriptors. The first element in the array is a file descriptor that can be read by the application, and the second element is a file descriptor that can be written to.

Let s now look at a slightly more complicated example of pipe in a multiprocess application. In this application (see Listing 11.2), we ll create a pipe (line 14) and then fork our process into a parent and a child process (line 16). At the child, we attempt to read from the input file descriptor of our pipe (line 18), which suspends the process until something is available to read. When something is read, we terminate the string with a NULL and print out what was read. The parent simply writes a test string through the pipe using the write file descriptor (array offset 1 of the pipe structure) and then waits for the child to exit using the wait function.

Note that there isn t anything spectacular about this application except for the fact that our child process inherited the file descriptors that were created by the parent (using the pipe function) and then used them to communicate with one another. Recall that once the fork function is complete, our processes are independent (except that the child inherited features of the parent, such as the pipe file descriptors). Memory is separate, so the pipe method provides us with an interesting model to communication between processes.

Listing 11.2: Illustrating the Pipe Model with Two Processes (on the CD-ROM at ./source/ch11/fpipe.c )
start example
  1:  #include <stdio.h>  2:  #include <unistd.h>  3:  #include <string.h>  4:  #include <wait.h>  5:  6:       #define MAX_LINE        80      7:         8:       int main()      9:       {      10:         int thePipe[2], ret;      11:         char buf[MAX_LINE+1];      12:         const char *testbuf={"a test string."};      13:             14:         if (pipe(thePipe) == 0) {      15:             16:           if (fork() == 0) {      17:             18:             ret = read(thePipe[0], buf, MAX_LINE);      19:             buf[ret] = 0;      20:             printf("Child read %s\n", buf);      21:             22:           } else {      23:             24:             ret = write(thePipe[1], testbuf, strlen(testbuf));      25:             ret = wait(NULL);      26:             27:           }      28:             29:         }      30:             31:         return 0;  32:  } 
end example
 

Note that in these simple programs, we ve not discussed closing the pipe, because once the process finishes, the resources associated with the pipe will be automatically freed. It s good programming practice, nonetheless, to close the descriptors of the pipe using the close call, such as:

 ret =  pipe  (myPipe);     ...  close  (myPipe[0]);  close  (myPipe[1]); 

If the write end of the pipe is closed and a process tries to read from the pipe, a zero is returned. This indicates that the pipe is no longer used and should be closed. If the read end of the pipe is closed and a process tries to write to it, a signal is generated. This signal (as discussed in Chapter 12, Introduction to Sockets Programming) is called SIGPIPE . Applications that write to pipes commonly include a signal handler to catch just this situation.

dup and dup2

The dup and dup2 calls are very useful functions that provide the ability to duplicate a file descriptor. They re most often used to redirect the stdin, stdout , or stderr of a process. The function prototypes for dup and dup2 are:

 #include <unistd.h>     int dup(int oldfd);     int dup2(int oldfd, int targetfd); 

The dup function allows us to duplicate a descriptor. We pass in an existing descriptor, and it returns a new descriptor that is identical to the first. This means that both descriptors share the same internal structure. For example, if we perform an lseek (seek into the file) for one file descriptor, the file position is the same in the second. Sample use of the dup function is illustrated in the following code snippet:

 int fd1, fd2;     ...     fd2 =  dup  (fd1); 
Note  

Creating a descriptor prior to the fork call has the same effect as calling dup . The child process receives a duplicated descriptor, just like it would after calling dup .

The dup2 function is similar to dup but allows the caller to specify an active descriptor and the id of a target descriptor. Upon successful return of dup2 , the new target descriptor is a duplicate of the first ( targetfd = oldfd ). Let s now look at a short code snippet that illustrates dup2 :

 int oldfd;     oldfd =  open  ("app_log", (O_RDWR  O_CREATE), 0644);  dup2  (oldfd, 1);  close  (oldfd); 

In this example, we open a new file called app_log and receive a file descriptor called fd1 . We call dup2 with oldfd and 1 , which has the effect of replacing the file descriptor identified as 1 (stdout) with oldfd (our newly opened file). Anything written to stdout now will go instead to the file named app_log . Note that we close oldfd directly after duplicating it. This doesn t close our newly opened file, as file descriptor 1 now references it.

Let s now look at a more complex example. Recall that earlier in the chapter we investigated pipelining the output of ls -1 to the input of wc -l . We ll now explore this example in the context of a C application (see Listing 11.3).

We begin in Listing 11.3 by creating our pipe (line 9) and then forking the application into the child (lines 13 “16) and parent (lines 20 “23). In the child, we begin by closing the stdout descriptor (line 13). The child here will provide the ls -1 functionality and will not write to stdout but instead to the input to our pipe (redirected using dup ). At line 14, we use dup2 to redirect the stdout to our pipe ( pfds[1] ). Once this is done, we close our input end of the pipe (as it will never be used). Finally, we use the execlp function to replace the child s image with that of the command ls -1 . Once this command executes, any output that is generated is sent to the input.

Now let s look at the receiving end of the pipe. The parent plays this role and follows a very similar pattern. We first close our stdin descriptor at line 20 (since we ll accept nothing from it). Next, we use the dup2 function again (line 21) to make the stdin the output end of the pipe. This is done by making file descriptor 0 (normal stdin ) the same as pfds[0] . We close the stdout end of the pipe ( pfds[1] ) since we won t use it here (line 22). Finally, we execlp the command wc -l , which takes as its input the contents of the pipe (line 23).

Listing 11.3: Pipelining Commands in C (on the CD-ROM at ./source/ch11/dup.c )
start example
  1:  #include <stdio.h>  2:  #include <stdlib.h>  3:  #include <unistd.h>  4:   5:  int main()  6:  {  7:  int pfds[2];  8:   9:  if (  pipe  (pfds) == 0) {  10:   11:  if (  fork  () == 0) {  12:   13:   close  (1);  14:   dup2  (pfds[1], 1);  15:   close  (pfds[0]);  16:   execlp  ("ls", "ls", "-1", NULL);  17:   18:  } else {  19:   20:   close  (0);  21:   dup2  (pfds[0], 0);  22:   close  (pfds[1]);  23:   execlp  ("wc", "wc", "-l", NULL);  24:   25:  }  26:   27:  }  28:   29:  return 0;  30:  } 
end example
 

What s important to note in this application is that our child process redirects its output to the input of the pipe, and the parent redirects its input to the output of the pipe ”a very useful technique that is worth remembering.

mkfifo

The mkfifo function is used to create a file in the filesystem that provides FIFO functionality ( otherwise known as a named pipe ). Pipes that we ve discussed thus far are anonymous pipes. They re used exclusively between a process and its children. Named pipes are visible in the filesystem and therefore can be used by any ( related or unrelated) process. The function prototype for mkfifo is defined as:

 #include <sys/types.h>     #include <sys/stat.h>     int  mkfifo  (const char *pathname, mode_t mode); 

The mkfifo command requires two arguments. The first ( pathname ) is the special file in the filesystem that is to be created. The second ( mode ) represents the read/write permissions for the FIFO. The mkfifo command returns zero on success or -1 on error (with errno filled appropriately). Let s look at an example of creating a fifo using the mkfifo function.

 int ret;     ...     ret =  mkfifo  ("/tmp/cmd_pipe", S_IFIFO  0666);     if (ret == 0) {       // Named pipe successfully created     } else {       // Failed to create named pipe     } 

In this example, we create a fifo (named pipe) using the file cmd_pipe in the /tmp subdirectory. We can then open this file for read or write to communicate through it. Once we open a named pipe, we can read from it using the typical I/O commands. For example, here s a snippet reading from the pipe using fgets :

 pfp =  fopen  ("/tmp/cmd_pipe", "r");     ...     ret =  fgets  (buffer, MAX_LINE, pfp); 

We could write to the pipe for this snippet using:

 pfp =  fopen  ("/tmp/cmd_pipe", "w+);     ...     ret = fprintf(pfp, "Heres a test string!\n"); 

What s interesting about named pipes, which we ll explore in the discussion of the mkfifo system command, is that they work in what is known as a rendezvous model. A reader will be unable to open the named pipe unless a writer has actively opened the other end of the pipe. The reader is blocked on the open call until a writer is present. Despite this limitation, the named pipe can be a useful mechanism for interprocess communication.

System Commands

Let s now look at a system command that is related to the pipe model for IPC. The mkfifo command, just like the mkfifo API function, allows us to create a named pipe from the command line.

mkfifo

The mkfifo command is one of two methods for creating a named pipe (fifo special file) at the command line. The general use of the mkfifo command is:

 mkfifo [options] name 

where options are -m for mode (permissions) and name is the name of the named pipe to create (including path if needed). If permissions are not specified, the default is 0644. Here s a sample use, creating a named pipe in /tmp called cmd_pipe :

 $ mkfifo /tmp/cmd_pipe 

We can adjust the options simply by specifying them with the -m option. Here s an example setting the permissions to 0644 (but we delete the original first):

 $ rm cmd_pipe     $ mkfifo -m 0644 /tmp/cmd_pipe 

Once the permissions are created, we can communicate through this pipe via the command line. Consider the following scenario. In one terminal, we attempt to read from the pipe using the cat command:

 $ cat cmd_pipe 

Upon typing this command, we re suspended awaiting a writer opening the pipe. In another terminal, we write to the named pipe using the echo command, as:

 $ echo Hi > cmd_pipe 

When this command finishes, our reader wakes up and finishes (here s the complete reader command sequence again for clarity):

 $ cat cmd_pipe     Hi     $ 

This illustrates that named pipes can be useful not only in C applications, but also in scripts (or combinations).

Named pipes can also be created with the mknod command (along with many other types of special files). We can create a named pipe (as mkfifo before) as

 $ mknod cmd_pipe p 

where the named pipe cmd_pipe is created in the current subdirectory (with type as p for named pipe).




GNU/Linux Application Programming
GNU/Linux Application Programming (Programming Series)
ISBN: 1584505680
EAN: 2147483647
Year: 2006
Pages: 203
Authors: M. Tim Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net