15.2. PipesPipes are the oldest form of UNIX System IPC and are provided by all UNIX systems. Pipes have two limitations.
Figure 15.2. Two ways to view a half-duplex pipeSection 4.2) returns a file type of FIFO for the file descriptor of either end of a pipe. We can test for a pipe with the S_ISFIFO macro.POSIX.1 states that the st_size member of the stat structure is undefined for pipes. But when the fstat function is applied to the file descriptor for the read end of the pipe, many systems store in st_size the number of bytes available for reading in the pipe. This is, however, nonportable.A pipe in a single process is next to useless. Normally, the process that calls pipe then calls fork, creating an IPC channel from the parent to the child or vice versa. Figure 15.3 shows this scenario. Figure 15.3. Half-duplex pipe after a fork![]() Figure 15.4. Pipe from parent to child![]() ExampleFigure 15.5 shows the code to create a pipe between a parent and its child and to send data down the pipe. Figure 15.5. Send data from parent to child over a pipe#include "apue.h" int main(void) { int n; int fd[2]; pid_t pid; char line[MAXLINE]; if (pipe(fd) < 0) err_sys("pipe error"); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid > 0) { /* parent */ close(fd[0]); write(fd[1], "hello world\n", 12); } else { /* child */ close(fd[1]); n = read(fd[0], line, MAXLINE); write(STDOUT_FILENO, line, n); } exit(0); } In the previous example, we called read and write directly on the pipe descriptors. What is more interesting is to duplicate the pipe descriptors onto standard input or standard output. Often, the child then runs some other program, and that program can either read from its standard input (the pipe that we created) or write to its standard output (the pipe). ExampleSection 3.12). In this program, if standard input had not been opened by the shell, the fopen at the beginning of the program should have used descriptor 0, the lowest unused descriptor, so fd[0] should never equal standard input. Nevertheless, whenever we call dup2 and close to duplicate a descriptor onto another, we'll always compare the descriptors first, as a defensive programming measure.Note how we try to use the environment variable PAGER to obtain the name of the user's pager program. If this doesn't work, we use a default. This is a common usage of environment variables. Figure 15.6. Copy file to pager program#include "apue.h" #include <sys/wait.h> #define DEF_PAGER "/bin/more" /* default pager program */ int main(int argc, char *argv[]) { int n; int fd[2]; pid_t pid; char *pager, *argv0; char line[MAXLINE]; FILE *fp; if (argc != 2) err_quit("usage: a.out <pathname>"); if ((fp = fopen(argv[1], "r")) == NULL) err_sys("can't open %s", argv[1]); if (pipe(fd) < 0) err_sys("pipe error"); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid > 0) { /* parent */ close(fd[0]); /* close read end */ /* parent copies argv[1] to pipe */ while (fgets(line, MAXLINE, fp) != NULL) { n = strlen(line); if (write(fd[1], line, n) != n) err_sys("write error to pipe"); } if (ferror(fp)) err_sys("fgets error"); close(fd[1]); /* close write end of pipe for reader */ if (waitpid(pid, NULL, 0) < 0) err_sys("waitpid error"); exit(0); } else { /* child */ close(fd[1]); /* close write end */ if (fd[0] != STDIN_FILENO) { if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) err_sys("dup2 error to stdin"); close(fd[0]); /* don't need this after dup2 */ } /* get arguments for execl() */ if ((pager = getenv("PAGER")) == NULL) pager = DEF_PAGER; if ((argv0 = strrchr(pager, '/')) != NULL) argv0++; /* step past rightmost slash */ else argv0 = pager; /* no slash in pager */ if (execl(pager, argv0, (char *)0) < 0) err_sys("execl error for %s", pager); } exit(0); } ExampleRecall the five functions TELL_WAIT, TELL_PARENT, TELL_CHILD, WAIT_PARENT, and WAIT_CHILD from Section 8.9. In Figure 10.24, we showed an implementation using signals. Figure 15.7 shows an implementation using pipes.We create two pipes before the fork, as shown in Figure 15.8. The parent writes the character "p" across the top pipe when TELL_CHILD is called, and the child writes the character "c" across the bottom pipe when TELL_PARENT is called. The corresponding WAIT_xxx functions do a blocking read for the single character.Note that each pipe has an extra reader, which doesn't matter. That is, in addition to the child reading from pfd1[0], the parent also has this end of the top pipe open for reading. This doesn't affect us, since the parent doesn't try to read from this pipe. Figure 15.7. Routines to let a parent and child synchronize#include "apue.h" static int pfd1[2], pfd2[2]; void TELL_WAIT(void) { if (pipe(pfd1) < 0 || pipe(pfd2) < 0) err_sys("pipe error"); } void TELL_PARENT(pid_t pid) { if (write(pfd2[1], "c", 1) != 1) err_sys("write error"); } void WAIT_PARENT(void) { char c; if (read(pfd1[0], &c, 1) != 1) err_sys("read error"); if (c != 'p') err_quit("WAIT_PARENT: incorrect data"); } void TELL_CHILD(pid_t pid) { if (write(pfd1[1], "p", 1) != 1) err_sys("write error"); } void WAIT_CHILD(void) { char c; if (read(pfd2[0], &c, 1) != 1) err_sys("read error"); if (c != 'c') err_quit("WAIT_CHILD: incorrect data"); } Figure 15.8. Using two pipes for parentchild synchronization![]() ![]() |