Pipes

Pipes are, as I said last time, a good way of getting information from one process to another. They come in two varieties, named and unnamed, but once you have them they act very similarly. The most important thing common to both types is reading and writing, which you do with:

read and write

write

write (pg 120) takes a file descriptor and some data (and its size in bytes), and dumps the data into that descriptor's. Often that data will be a string (char *), so the call might look like this:

ssize_t write(f_des, mystring, strlen(mystring));

write returns -1 on failure, and the number of bytes written on success

read

read is the corresponding command to get the data back out of the pipe and into a char *. To make sure you don't exceed the size of your target character array, it takes a max number of bytes to read. Reading a string back out would look like this:

message[BUFSIZ];
ssize_t read(f_des, message, BUFSIZ);

read, like write returns -1 on failure, and the number of bytes read on success. It also returns 0 on EOF, which makes it a useful stopping condition if you are reading an unknown amount of data. The BUFSIZ constant is a pre-declared constant that's useful for reading from pipes, but you could use any size array.

Note that although my examples use character arrays, you can actually read and write any data type, since it copies the raw bytes. Also, they can be used anytime you have a file descriptor, so if you are doing file IO and you'd rather use read and write, got ahead. Similarly, you can use fscanf and fprintf to read and write pipes (but it's not generally as good an idea, since write is more multi-process friendly for pipes).

Unnamed Pipes

So you know what they are, and you know how to read to / write from them, but how do you make them? The answer (in the case of unnamed pipes) is the pipe command, which takes an array of two ints and fills them in with file descriptors corresponding to the two ends of a new pipe. Usually the next step would be to fork, and then each process can write to one end of the pipe, and read from the other. There's a good example of doing just that on pg 125 of the Monkey book. If you wanted to have the child send a message back, it could write to f_des[1] instead of closing it, and the parent could then read from f_des[0], which is all the Monkey book means in its somewhat confusing discussion of half-duplex v. full-duplex

dup2

Another option is to have the other end of a pipe be in an exec'd process, instead of another process you've written. This is done with the dup2 command (there is a dup command, but there's no reason to use it now that dup2 exists) which links a file descriptor to another open file descriptor. For example:

dup2(f_des[1], fileno(stdout));

will (assuming f_des[1] is one end of a pipe you've previously opened), replace the stdout of the process with the write end of a pipe. So now everything which would go to stdout will instead feed into the pipe, which is especially powerful when combined with exec. Pipes and dup2 make it possible to exec a command, but send all the output back into a parent process rather then just displaying it. Conversely, you can replace stdin, and send input into an exec'd command.

popen

In fact, the sequence of opening a pipe, forking a child, replacing the stdin or stdout, and execing another command is so widely used, that the popen command was made to do all of that at once. Given a command (like you would pass as the second argument of exec) and access type, popen will return a FILE *. If the access type you pass is "w", then the FILE * corresponds to the stdin of the command, and if it's "r" then the FILE * corresponds to stdout. If you want to change both stdin and stdout, you're stuck with the manual dup2 method.

Named Pipes

The biggest drawback of unnamed pipes is that unless you are forking, there's no way to let any other process know what the file descriptors for the pipes are. If you want to communicate between two processes that are started separately, you need to use named pipes. Named pipes are treated just like files by the system, but they behave like pipes in terms of reading and writing (i.e. reading is destructive). You can make pipes from the command line with mkfifo name, and when you ls -l you'll see it listed with a 'p' as the file type, where 'd' would be for directories. From within a program you use the corresponding mkfifo system call, which works almost exactly like creat but makes a pipe instead of a normal file.

Note: there are also mknod shell and c commands, which can also create pipes, along with a lot of other things. Since you need to be root to run mknod to make anything but pipes, and even if you could it would be really bad to make anything but pipes without knowing what you are doing, there's no reason to use mknod instead of mkfifo.

Stuart Morgan, 2003