6.1 Pipes

Team-FLY

The capacity to communicate is essential for processes that cooperate to solve a problem. The simplest UNIX interprocess communication mechanism is the pipe, which is represented by a special file. The pipe function creates a communication buffer that the caller can access through the file descriptors fildes[0] and fildes[1] . The data written to fildes[1] can be read from fildes[0] on a first-in-first-out basis.

  SYNOPSIS  #include <unistd.h>   int pipe(int fildes[2]);  POSIX  

If successful, pipe returns 0. If unsuccessful , pipe returns “1 and sets errno . The following table lists the mandatory errors for pipe .

errno

cause

EMFILE

more than MAX_OPEN-2 file descriptors already in use by this process

ENFILE

number of simultaneously open files in system would exceed system-imposed limit

A pipe has no external or permanent name , so a program can access it only through its two descriptors. For this reason, a pipe can be used only by the process that created it and by descendants that inherit the descriptors on fork . The pipe function described here creates a traditional unidirectional communication buffer. The POSIX standard does not specify what happens if a process tries to write to fildes[0] or read from fildes[1] .

When a process calls read on a pipe, the read returns immediately if the pipe is not empty. If the pipe is empty, the read blocks until something is written to the pipe, as long as some process has the pipe open for writing. On the other hand, if no process has the pipe open for writing, a read from an empty pipe returns 0, indicating an end-of-file condition. (This description assumes that access to the pipe uses blocking I/O.)

Example 6.1

The following code segment creates a pipe.

 int fd[2]; if (pipe(fd) == -1)    perror("Failed to create the pipe"); 

If the pipe call executes successfully, the process can read from fd[0] and write to fd[1] .

A single process with a pipe is not very useful. Usually a parent process uses pipes to communicate with its children. Program 6.1 shows a simple program in which the parent creates a pipe before forking a child. The parent then writes a string to the pipe and prints a message to standard error. The child reads a message from the pipe and then prints to standard error. This program does not check for errors on the read or write operations.

Program 6.1 parentwritepipe.c

A program in which a parent writes a string to a pipe and the child reads the string. The program does not check for I/O errors .

 #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #define BUFSIZE 10 int main(void) {    char bufin[BUFSIZE] = "empty";    char bufout[] = "hello";    int bytesin;    pid_t childpid;    int fd[2];    if (pipe(fd) == -1) {       perror("Failed to create the pipe");       return 1;    }    bytesin = strlen(bufin);    childpid = fork();    if (childpid == -1) {       perror("Failed to fork");       return 1;    }    if (childpid)                                       /* parent code */       write(fd[1], bufout, strlen(bufout)+1);    else                                                 /* child code */       bytesin = read(fd[0], bufin, BUFSIZE);    fprintf(stderr, "[%ld]:my bufin is {%.*s}, my bufout is {%s}\n",            (long)getpid(), bytesin, bufin, bufout);    return 0; } 
Exercise 6.2

Run Program 6.1 and explain the results. Does the child always read the full string?

Answer:

The parent's bufin always contains the string "empty" . The child's bufin most likely contains the string "hello" . However, reads from pipes are not atomic. That is, there is no guarantee that a single read call actually retrieves everything written by a single write call. It is possible (though not likely in this case) that the child's bufin could contain something like "helty" if read retrieves only partial results. If the parent's write operation fails, the child's bufin contains "empty" .

Exercise 6.3

Consider the following code segment from Program 6.1.

 if (childpid)    write(fd[1], bufout, strlen(bufout)+1); else    bytesin = read(fd[0], bufin, BUFSIZE); 

What happens if you replace it with the following code?

 if (childpid)    copyfile(STDIN_FILENO, fd[1]); else    copyfile(fd[0], STDOUT_FILENO); 

(The copyfile function is shown in Program 4.6 on page 100.)

Answer :

The parent process reads from standard input and writes to the pipe, while the child reads from the pipe and echoes to standard output. The parent echoes everything entered at the keyboard as it is typed, and the child writes to the screen as it reads each entered line from the pipe. A difficulty arises, however, when you enter the end-of-file character (usually Ctrl-D) at the terminal. The parent detects the end of the input, displays the message written by its fprintf , and exits with no problem, closing its descriptors to the pipe. Unfortunately, the child still has fd[1] open, so the copyfile function does not detect that input has ended. The child hangs , waiting for input, and does not exit. Since the parent has exited, the prompt appears, but the child process is still running. Unless you execute ps you might think that the child terminated also. To fix the problem, replace the substitute code with the following.

 if (childpid && (close(fd[0]) != -1))    copyfile(STDIN_FILENO, fd[1]); else if (close(fd[1]) != -1)    copyfile(fd[0], STDOUT_FILENO); 

Program 6.2 shows a modification of Program 3.2 from page 68. The modification demonstrates how to use reading from pipes for synchronization. The parent creates a pipe before creating n-1 children. After creating all its children, the parent writes n characters to the pipe. Each process, including the parent, reads a character from the pipe before proceeding to output its information to standard error. Since the read from the pipe blocks until there is something to read, each child waits until the parent writes to the pipe, thereby providing a synchronization point called a barrier . None of the processes can do any writing to standard error until all of the processes have been created. Section 6.8 gives another example of barrier synchronization. Notice that Program 6.2 uses r_write and r_read rather than write and read to ensure that the parent actually writes everything and that the children actually perform their reads. The children do not synchronize after the barrier.

Program 6.2 synchronizefan.c

A synchronized process fan. Processes wait until all have been created before echoing their messages to standard error .

 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "restart.h" int main  (int argc, char *argv[]) {    char buf[] = "g";    pid_t childpid = 0;    int fd[2];    int i, n;    if (argc != 2){      /* check for valid number of command-line arguments */       fprintf (stderr, "Usage: %s processes\n", argv[0]);       return 1;    }    n = atoi(argv[1]);    if (pipe(fd) == -1) {                 /* create pipe for synchronization */       perror("Failed to create the synchronization pipe");       return 1;    }    for (i = 1; i < n;  i++)                  /* parent creates all children */        if ((childpid = fork()) <= 0)            break;    if (childpid > 0) {          /* write synchronization characters to pipe */       for (i = 0; i < n; i++)          if (r_write(fd[1], buf, 1) != 1)             perror("Failed to write synchronization characters");    }    if (r_read(fd[0], buf, 1) != 1)                      /* synchronize here */       perror("Failed to read synchronization characters");    fprintf(stderr, "i:%d  process ID:%ld  parent ID:%ld  child ID:%ld\n",            i, (long)getpid(), (long)getppid(), (long)childpid);    return (childpid == -1); } 
Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

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