Pipes

There is no form of IPC that is simpler than pipes.

Let me put it this way: you know about “FILE*” from stdio.h, right? You know how you have all those nice functions like fopen(), fclose(), fwrite(), and so on?

For example, stdin is file descriptor “0”, stdout is “1”, and stderr is “2”. Likewise, any files you open using fopen() get their own file descriptor, although this detail is hidden from you. (This file descriptor can be retrived from the FILE* by using the fileno() macro from stdio.h.)

___________________

fd[1] -------->| Pipe |----------- fd[0]

| |__________________| |

| \/

write() read()

How a Pipe is organised

Basically, a call to the pipe() function returns a pair of file descriptors. One of these descriptors is connected to the write end of the pipe, and the other is connected to the read end. Anything can be written to the pipe, and read from the other end in the order it came in. On many systems, pipes will fill up after you write about 10K to them without reading anything out.

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <unistd.h>

int main(void)

{

int pfds[2];

char buf[30];

if (pipe(pfds) == -1) {

perror("pipe");

exit(1);

}

printf("writing to file descriptor #%d\n", pfds[1]);

write(pfds[1], "test", 5);

printf("reading from file descriptor #%d\n", pfds[0]);

read(pfds[0], buf, 5);

printf("read \"%s\"\n", buf);

return 0;

}

pipe() takes an array of two ints as an argument. Assuming no errors, it connects two file descriptors and returns them in the array. The first element of the array is the reading-end of the pipe, the second is the writing end.

fork() and pipe()—you have the power!

Pretend that you are a top federal agent assigned to get a child process to send the word “test” to the parent.

First, we'll have the parent make a pipe. Secondly, we'll fork(). Now, the fork() man page tells us that the child will receive a copy of all the parent's file descriptors, and this includes a copy of the pipe's file descriptors. Alors, the child will be able to send stuff to the write-end of the pipe, and the parent will get it off the read-end.

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <sys/types.h>

#include <unistd.h>

int main(void)

{

int pfds[2];

char buf[30];

pipe(pfds);

if (!fork()) {

printf(" CHILD: writing to the pipe\n");

write(pfds[1], "test", 5);

printf(" CHILD: exiting\n");

exit(0);

} else {

printf("PARENT: reading from pipe\n");

read(pfds[0], buf, 5);

printf("PARENT: read \"%s\"\n", buf);

wait(NULL);

}

return 0;

}

PARENT: reading from pipe

CHILD: writing to the pipe

CHILD: exiting

PARENT: read "test"

In this case, the parent tried to read from the pipe before the child writes to it. When this happens, the parent is said to block, or sleep, until data arrives to be read. It seems that the parent tried to read, went to sleep, the child wrote and exited, and the parent woke up and read the data.