Add Book to My BookshelfPurchase This Book Online

Chapter 13 - Interprocess Communication

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

Pipes
A pipe provides an interface between two processes. It is a special pair of file descriptors that, rather than being connected to a file, is connected to another process. When process A writes to its pipe file descriptor, process B can read that data from its pipe file descriptor. Alternatively, when process B writes to its pipe file descriptor, process A can read the data from its pipe file descriptor. Thus, a pipe provides a unidirectional communications medium for two cooperating processes.
Once a pipe has been created, there is very little difference between a pipe file descriptor and a regular file descriptor. In fact, unless a program takes special steps to find out, there is no way for it to know that it is reading or writing a pipe instead of a file. The UNIX shell makes use of this fact all the time, when it creates pipeline commands. For example, consider the following shell commands:
    % eqn report > out1
    % tbl out1 > out2
    % troff out2 > out3
    % psdit out3 > out4
    % lp out4
    % rm out1 out2 out3 out4
Although we can certainly execute these programs in this fashion, it's not terribly efficient. There's a lot of typing involved, there are four temporary files created which must then be deleted, etc. However, with the knowledge that each of the above commands has been written as a filter, we can simplify things. A filter is a program that will read from its standard input (instead of from a disk file) and write to its standard output. Programs that have been written in this way can be joined together in pipelines by the shell. For example, we can combine the five commands above into a single command, as follows:
    % eqn report | tbl | troff | psdit | lp
The eqn program reads its input from the file report, just as in the previous example. But, instead of storing its output in the file out1, we have told the shell to connect the standard output from eqn to the standard input of the tbl command. The tbl command, instead of reading its input from the file out1, reads it from standard input. The standard output from tbl has been connected to the standard input of troff. The standard output from troff has been connected to the standard input of psdit. And finally, the standard output from psdit has been connected to the standard input of lp. Thus, data flows from one program to the next, with no need for temporary files in between. The tool used to connect these programs together is a pipe. The programs themselves, however, have no knowledge of being used in this manner—they just know that if there are no filename arguments given to them on the command line, they should read from their standard input and write to their standard output. For all they know, the standard input could be a file and the standard output could be the terminal screen. Because pipes work just like file descriptors, there is no need for special code in each of these programs to handle them.
Simple Pipe Creation
The simplest way to create a pipe to another process is to use the popen function:
    #include <stdio.h>
    FILE *popen(const char *command, const char *type);
The popen function is similar to fopen, described in Chapter 4, The Standard I/O Library, except that instead of opening a file for reading or writing, it creates a pipe for reading from or writing to another command. The command, passed in the command string, may be any valid shell command; it is executed with the Bourne shell (/bin/sh) using the shell's -c option. The type argument contains one of the strings r (open the pipe for reading) or w (open the pipe for writing).
When called, popen creates a new process, and executes the command. It also creates a pipe to that process, and connects it to the process' standard input or standard output, depending on the value in the type argument. It then returns a file pointer to the calling process. The calling process may read from this file pointer to obtain output from the child process, or may write to the file pointer to provide input to the child process. If the command cannot be executed, or the pipe cannot be created, popen returns the constant NULL.
With one exception, all of the usual Standard I/O Library functions described in Chapter 4 may be used with the file pointer returned by popen. The one exception is the fclose function. Instead, the pclose function should be used:
    #include <stdio.h>
    int pclose(FILE *stream);
The pclose function closes the stream and frees up the buffers associated with it, just like fclose. However, it also issues a call to waitpid (see Chapter 11, Processes) to wait for the child process to terminate, and then returns the child's termination status to the caller.
Example 13-1 shows a different version of the program from Example 11-1 that prints out the day of the week; this one uses popen.
Example 13-1:  popen
#include <stdio.h>
struct {
    char    *abbrev;
    char    *fullname;
} days[] = {
    "Sun",  "Sunday",
    "Mon",  "Monday",
    "Tue",  "Tuesday",
    "Wed",  "Wednesday",
    "Thu",  "Thursday",
    "Fri",  "Friday",
    "Sat",  "Saturday",
    0,      0
};
int
main(void)
{
    int i;
    FILE *pf;
    char line[BUFSIZ];
    /*
     * Open a pipe to the date command.  We will
     * be reading from the pipe.
     */
    if ((pf = popen("date", "r")) == NULL) {
        perror("popen");
        exit(1);
    }
    /*
     * Read one line of output from the pipe.
     */
    if (fgets(line, sizeof(line), pf) == NULL) {
        fprintf(stderr, "No ouput from date command!\n");
        exit(1);
    }
    /*
     * For each day, see if it matches the output
     * from the date command.
     */
    for (i=0; days[i].abbrev != NULL; i++) {
        if (strncmp(line, days[i].abbrev, 3) == 0)
            printf("Today is %s.\n", days[i].fullname);
        else
            printf("Today is not %s.\n", days[i].fullname);
    }
    /*
     * Close the pipe and pick up the command's
     * termination status (which we ignore).
     */
    pclose(pf);
    /*
     * Exit with a status of 0, indicating that
     * everything went fine.
     */
    exit(0);
}
    % popen
        Today is not Sunday.
    Today is not Monday.
    Today is not Tuesday.
    Today is not Wednesday.
    Today is Thursday.
    Today is not Friday.
    Today is not Saturday.
This program creates a pipe from the date command, and reads its output. It then compares that output to its list of day name abbreviations, and prints out the appropriate information. This version of the program is much more efficient than the version in Chapter 11, because it creates only one child process instead of seven.
Because it works in a similar way, we can make the same points about popen that we did about system:
 Although very convenient, popen is also quite inefficient. Each time it is called, it starts up not only a copy of the command you want to execute, but also a copy of the shell. If your program will be executing many commands, you should execute them yourself directly and do your own “plumbing,” rather than using popen. The means to do this are described in the next section.
 System calls and library routines are always more efficient than using popen. In the example above, it would be much better to simply use the time and localtime functions described in Chapter 7, Time of Day Operations, and avoid the overhead of executing a child process to obtain the same information.
 The popen function should never, under any circumstances, be used in programs that will be run with superuser permissions, or with the set-user-id bit set. Because popen uses the shell to execute commands, there may be ways in which an unethical person can fool your program into executing a command other than the one you intended. This may enable the person to circumvent the security of your computer system.
Advanced Pipe Creation
In this section, we examine the procedures used to create pipes ourselves. Before reading this section, you should be familiar with the information in Chapter 11, Processes, on which it relies.
A pipe is created with the pipe function:
    #include <unistd.h>
    int pipe(int fd[2]);
This function creates two file descriptors; fd[0] is open for reading, and fd[1] is open for writing. The two file descriptors are joined like a pipe, such that data written to fd[1] can be read from fd[0]. If the pipe is successfully created, pipe returns 0. If it cannot be created, pipe returns -1, and places the reason for failure in errno.
After creating a pipe, the calling process normally calls fork to create a child process. The two processes can then communicate, in one direction, using the pipe. Notice that, because a pipe is a half-duplex communications channel (it can only be used to communicate in one direction), either the parent may send data to the child, or the child may send data to the parent, but not both. If both processes must be able to send data, two pipes must be created, one for the child to use to send data to the parent, and the other for the parent to use to send data to the child.
In SVR4, pipes are full-duplex communications channels. This means that both file descriptors are opened for both reading and writing. A read from fd[0] accesses the data written to fd[1], and a read from fd[1] accesses the data written to fd[0]. However, this feature is peculiar to SVR4, and is not the way pipes work on other UNIX systems. The POSIX standard specifies the more common half-duplex pipe described in the previous paragraph, and that is what we describe in the rest of this section.
As long as both ends of a pipe are open, communication can take place. When one end of a pipe is closed, the following rules apply:
 If the write end of a pipe has been closed, any further reads from the pipe (after all the data remaining in the pipe has been read) will return 0, or end-of-file.
 If the read end of a pipe has been closed, any attempt to write to the pipe will result in a SIGPIPE signal being delivered to the process attempting the write.
Each pipe has a buffer size; this size is defined by the constant PIPE_BUF, described in the include file limits.h. A write of this many bytes or less is guaranteed not to be interleaved with the writes from other processes writing the same pipe. Writes of more than PIPE_BUF bytes, however, can get jumbled up in the pipe if more than one process is writing to it at the same time. (It is possible to have more than one process writing to a pipe by using dup or dup2 on the file descriptor.)
Example 13-2 shows a reimplementation of the program in Example 13-1; this time we create the pipe and execute date ourselves.
Example 13-2:  pipedate
#include <sys/types.h>
#include <unistd.h>
struct {
    char    *abbrev;
    char    *fullname;
} days[] = {
    "Sun",  "Sunday",
    "Mon",  "Monday",
    "Tue",  "Tuesday",
    "Wed",  "Wednesday",
    "Thu",  "Thursday",
    "Fri",  "Friday",
    "Sat",  "Saturday",
    0,      0
};
int
main(void)
{
    pid_t pid;
    int pfd[2];
    int i, status;
    char line[64];
    /*
     * Create a pipe.
     */
    if (pipe(pfd) < 0) {
        perror("pipe");
        exit(1);
    }
    /*
     * Create a child process.
     */
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    /*
     * The child process executes "date".
     */
    if (pid == 0) {
        /*
         * Attach standard output to the pipe.
         */
        dup2(pfd[1], 1);
        close(pfd[0]);
        execl("/bin/date", "date", 0);
        perror("exec");
        _exit(127);
    }
    /*
     * We will not be writing to the pipe.
     */
    close(pfd[1]);
    /*
     * Read the output of "date".
     */
    if (read(pfd[0], line, 3) < 0) {
        perror("read");
        exit(1);
    }
    /*
     * For each day, see if it matches the output
     * from the date command.
     */
    for (i=0; days[i].abbrev != NULL; i++) {
        if (strncmp(line, days[i].abbrev, 3) == 0)
            printf("Today is %s.\n", days[i].fullname);
        else
            printf("Today is not %s.\n", days[i].fullname);
    }
    /*
     * Close the pipe and wait for the child
     * to exit.
     */
    close(pfd[0]);
    waitpid(pid, &status, 0);
    /*
     * Exit with a status of 0, indicating that
     * everything went fine.
     */
    exit(0);
}
    % pipedate
        Today is not Sunday.
    Today is not Monday.
    Today is not Tuesday.
    Today is not Wednesday.
    Today is Thursday.
    Today is not Friday.
    Today is not Saturday.
The program begins by creating a pipe. It then calls fork to create a child process. The child process will be executing the date command, and we want the parent to be able to read the output from this command, so the child process calls dup2 to attach its standard output to pfd[1]. Because the child process will not be reading from the pipe, it closes pfd[0]. The child process then calls execl to execute the date command. Meanwhile, the parent closes pfd[1], since it will not be writing to the pipe. It then calls read to obtain the data it needs, and examines the data just as in the previous example. Finally, the parent closes the read side of the pipe since it's done with it, and calls waitpid to wait for the child process to terminate and pick up its termination status.
Example 13-3 shows another program; this one uses the pipe in the other direction, to allow the parent to send data to the child.
Example 13-3:  pipemail
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int
main(void)
{
    pid_t pid;
    int pfd[2];
    int i, status;
    char *username;
    /*
     * Obtain the user name of the person
     * running this program.
     */
    if ((username = cuserid(NULL)) == NULL) {
        fprintf(stderr, "Who are you?\n");
        exit(1);
    }
    /*
     * Create a pipe.
     */
    if (pipe(pfd) < 0) {
        perror("pipe");
        exit(1);
    }
    /*
     * Create a child process.
     */
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    /*
     * The child process executes "mail".
     */
    if (pid == 0) {
        /*
         * Attach standard input to the pipe.
         */
        dup2(pfd[0], 0);
        close(pfd[1]);
        execl("/bin/mail", "mail", username, 0);
        perror("exec");
        _exit(127);
    }
    /*
     * We won't be reading from the pipe.
     */
    close(pfd[0]);
    /*
     * Write our mail message to the pipe.
     */
    write(pfd[1], "Greetings and salutations,\n\n", 28);
    write(pfd[1], "This is your program saying hello.\n", 35);
    write(pfd[1], "Have a nice day.\n\n", 18);
    write(pfd[1], "Bye.\n", 5);
    /*
     * Close the pipe and wait for the child
     * to exit.
     */
    close(pfd[1]);
    waitpid(pid, &status, 0);
    /*
     * Exit with a status of 0, indicating that
     * everything went fine.
     */
    exit(0);
}
    % pipemail
    % mailx
        mailx version 5.0 Mon Sep 27 07:25:51 PDT 1993  Type ? for help.
    "/var/mail/davy": 1 message 1 new
    >N  1 David A. Curry     Thu Dec  8 11:43   19/383  
        ? 1
        Message  1:
    From davy Thu Dec  8 11:43 EST 1994
    Date: Thu, 8 Dec 1994 11:43:55 +0500
    From: davy (David A. Curry)
    Greetings and salutations,
    This is your program saying hello.
    Have a nice day.
    Bye.
    ? d
    ? q
In this case, the child process executes the mail command, and the parent will be sending a message. Since mail reads from its standard input, the child process uses dup2 to attach its standard input to the read side of the pipe. Since it won't be writing to the pipe, it closes pfd[1]. The parent closes pfd[0] since it won't be reading from the pipe, and then writes a few strings to the child process by using pfd[1]. It then closes the write side of the pipe (this provides the end-of-file indication to the mail command), and waits for the child process to terminate.
 NoteWhen you execute this program, depending on the load on your system, it may take anywhere from a few seconds to several minutes for the mail message to be delivered to your mailbox. Be patient before assuming the program doesn't work.

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback