Pipes PDF
Pipes PDF
4 Pipes
[This writeup is a copy (hopefully faithful) of material from Section 3.4 of W.R. Stevens’ book
UNIX Network Programming, First Edition – ISBN 0-13-949876. Some minor revisions were
made to suit C++ instead of C.]
Pipes are provided with all flavors of Unix. A pipe provides a one-way flow of data. A pipe is created by
the pipe system call.
Two file descriptors are returned – filedes[0] which is open for reading, and filedes[1] which is open for
writing. Pipes are of little use within a single process, but here is a simple example that shows how they
are created and used.
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
int pipefd[2], n;
char buff[100];
if (pipe(pipefd) < 0)
perror("pipe errer");
cout << "read fd = " << pipefd[0] << ", write fd = " << pipefd[1] << endl;
if (write(pipefd[1], "hello world\n", 12) != 12)
perror("write error");
exit(0);
}
read fd = 3, write = 4
hello world
A diagram of what a pipe looks like in a single process is shown in the following Figure 1. A pipe has a
finite size, always at least 4096 bytes. The rules for reading and writing a pipe – when there is either no
data in the pipe, or when the pipe is full – are provided in the next section on FIFO’s.
Pipes are typically used to communicate between two different processes in the following way. First, a
process creates a pipe and then forks to create a copy of itself, as shown in Figure 2.
1
user process
read fd
write fd
kernel
pipe
flow of data
Figure 1:
read fd read fd
write fd write fd
kernel
pipe
flow of data
Figure 2:
Next the parent process closes the read end of the pipe and the child process closes the write end of the
pipe. This provides a one-way flow of data between the two processes as shown in Figure 3.
to a Unix shell, the shell does the steps shown below to create three processes with two pipes between
them. (who is a program that outputs the login names, terminal names, and login times of all users on the
system. the sort program orders this list by login names, and lpr is a 4.3BSD program that sends the
result to the line printer.) We show this pipeline in Figure 4.
Note that all the pipes shown so far have all been unidirectional, providing a one-way flow of data only.
When a two-way flow is desired, we must create two pipes and use one for each direction. The actual steps
are
2
parent process client process
read fd
write fd
kernel
pipe
flow of data
Figure 3:
write fd write fd
read fd read fd
kernel
pipe 1 pipe 2
flow of data flow of data
Figure 4:
• fork,
Let’s now implement the client-server example described in the previous section using pipes. The main
function creates the pipe and forks. The client then runs in the parent process and the server runs in the
child process.
3
parent process client process
write fd write fd
read fd read fd
kernel
pipe 1
flow of data
pipe 2
flow of data
Figure 5:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <wait.h>
#include <strings.h>
int main()
{
void client (int, int);
void server (int, int);
int childpid, pipe1[2], pipe2[2];
client(pipe2[0], pipe1[1]);
4
close(pipe1[1]);
close(pipe2[0]);
exit(0);
} else {
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0], pipe2[1]);
close(pipe1[0]);
close(pipe2[1]);
exit(0);
}
return 0;
}
/*
* Read the filename from standard input,
* write it to the IPC descriptor
*/
n = strlen(buff);
if (buff[n-1] == ’\n’)
n--; /* ignore newline from fgets() */
if (write(writefd, buff, n) != n)
perror("client: filename write error");
/*
* Read the data from the IPC descriptor and
* write to standard output
*/
if (n < 0)
perror("client: data read error");
}
5
int n, fd;
/*
* Read the filename from the IPC descriptor
*/
if (n < 0)
perror("server: read error");
}
}
The standard IO library provides a function that creates a pipe and initiates another process that either
reads from the pipe or writes to the pipe.
#include <stdio.h>
command is a shell command line. It is invoded by the Bourne shell, so the PATH environment variable is
used to locate the command. A pipe is created between the call-ing process and the specified command.
The value returned by the popen is a standard IO FILE pointer that is used for either input or output,
depending on the character string type. If the value of type is r the calling process writes to the standard
output of the command. If the popen call fails, a value of NULL is returned. The function
#include <stdio.h>
closes an IO stream that was created by popen, returning the exit status of the command, or -1 if the
stream was not created by popen.
6
We can provide another solution to our client-server example using the popen function and the Unix cat
program.
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
main()
{
int n;
char line[MAXLINE], command[MAXLINE + 10];
FILE *fp;
/*
* Read the filename from standard input
*/
/*
* Use popen to create a pipe and execute the command
*/
/*
* Read the data from the FILE pointer and write
* to standard output
*/
if (ferror(fp))
perror("fgets error");
pclose(fp);
exit(0);
}
Another use of popen is to determine the current working directory of a process. Recall that the chdir
system call changes the current working directory for a process, but there is not an equivalent system call
to obtain its current value. System V provides a getcwd function to do this. 4.3BSD provides a similar,
but not identical, function getwd.
The following program obtains the current working directory and prints it, using the Unix pwd command.
#include <iostream>
7
#include <stdio.h>
main()
{
FILE *fp;
char line[MAXLINE];
pclose(fp);
exit(0);
}