CSE 522S: Studio 13

Linux Pipes and FIFOs


Pipes and FIFOs are efficient ways to do file-like input and output between processes. Their biggest benefit is the ability to treat the endpoints as formatted input and output streams, just like you would read and write from files, or from streams like standard input and output.

In this studio, you will:

  1. Create and pass data through pipes with the pipe() system call
  2. Create and pass data through FIFOs with the mkfifo() function
  3. Implement a rudimentary active object pattern with processes and FIFOs

Please complete the required exercises below, as well as any optional enrichment exercises that you wish to complete.

As you work through these exercises, please record your answers, and when finished email your results to eng-cse522@email.wustl.edu with the phrase Linux Pipes in the subject line.

Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.


Required Exercises

  1. As the answer to the first exercise, list the names of the people who worked together on this studio.

  2. First we will use the pipe() system call to create a communicating child and parent. However, once created, pipes cannot be shared between executing processes. Thus, the standard way to use this system call is to call the pipe() function, which will create separate pipe endpoints for reading and writing, and then to fork() the process. Thus, the child will inherit the pipe endpoints from the parent, and both processes now have access.

    Create a program which:

    1. Calls pipe() to create a pipe with read and write file descriptors (see man 2 pipe for details)
    2. Calls fork() to create a child process
    3. After the fork, the child process closes its copy of the read file descriptor with close(), and the parent process closes its copy of the write file descriptor similarly
    4. After the fork, the child process writes several test messages to the pipe
    5. After the fork, the parent process reads several messages from the pipe and prints them to stdout

    Pipe I/O, like file I/O in C, is comparatively low level. There are two approaches you can use to read and write to your pipe. You could use the integer file descriptors returned by pipe() directly with the read() and write() system calls, documented at man 2 read and man 2 write. However you might find it easier to instantiate a FILE* stream, which allows you to work with your pipe like it was any other file object (i.e. you write to the pipe with fprintf() and read from the pipe with intelligent input operations such as fgets() or fscanf(). You can open a file stream from a file descriptor with the fdopen() function. Read more about the difference between file descriptors and file streams here. Of course, all of the above functions have informative man pages.

    Hint: Blocking functions that read the pipe will block indefinitely until they are able to return successfully or the write-end of the pipe is closed by the writer. The writer should write all of it's data and then call close() on its file descriptor when it is done. This allows you to write code such as while( fgets() != NULL ) to do open-ended reads, on the pipe, which will eventually fail and return once the write-end is closed.

  3. As the answer to this exercise, copy and paste the terminal output from your program.

  4. Now we are going to implement a rudimentary active object. An active object is an executable context that performs services for other contexts upon request. In this case our active object will be a process that listens to one end of a FIFO, does some trivial computation when data is recieved, and prints the result of that computation to standard output. Any number of other processes can open the FIFO's read side and submit data, and the order that requests are scheduled depends on a race for the FIFO (recall that writes and reads are atomic for less than PIPE_BUF sized data).

    To begin, create a new file for your active object. This program should:

    1. Create a new FIFO with the function mkfifo(). This is documented at man 3 mkfifo. Be careful not to confuse it with the program called mkfifo that is documented at man 1 mkfifo.
    2. Open the FIFO with read permission as though it were a file using fopen().
    3. Enter a while loop that continually attempts to read from the FIFO. The same code you used to read from a pipe previously can be adapted here. Whenever something is read, print it to the standard output.

    When you run your program it will create a FIFO which will appear in the filesystem. In a separate terminal, write some data into the FIFO and validate that it is read by your program correctly. For example, if your FIFO is named my_ao_fifo, the command "echo "my_message" > my_ao_fifo will insert "my_message" into the FIFO. Test your program with larger input as well, for example: "cat my_file.c > my_ao_fifo".

    As the answer to this exercise, copy and paste the terminal output.

  5. Now, we will make two modifications to make your active object to perform a minimal but useful purpose. After opening a read stream to the FIFO, also open a write stream (i.e. fopen( fifo_name, "w" ). Your active object will not ever write to this stream, but the fact that it is held open will prevent your program from automatically quitting.

    Next, modify the active object to read from the FIFO with the function fscanf(). This allows you to perform formatted input. Your program should read integer inputs from the pipe, double the input, and then print out the original and the new values. Your input stream should not have any non-integer characters and you do not need to handle this case.

    Test your program by passing integers into your FIFO, and copy and paste the results here.

  6. Lastly, we want to write a set of programs that concurrently use the FIFO to request work from the active object. Write two programs that open the write end of the fifo with fopen(fifo_name, "w") and insert integer values with fprintf(). One of these programs should only insert even numbers, and the other should only insert odd numbers. Run these programs long enough to verify that they can both write concurrently to the FIFO without problem. Examine the output of your active object to verify correctness.


Things to turn in:


Optional Enrichment Exercises

  1. The active object we have constructed in somewhat limited. While it allows processes to request some work to be performed, the true purpose of the active object is to allow for remote procedure call semantics. This would require that processes be able to submit work to be performed, and then the results of that work would be returned directly to those specific processes (rather than being printed to standard out). Such an implementation has several challenges, such as how to multiplex/demultiplex the communication channel, and how to create and organize additional channels to enable bi-directional communication. Try implementing true remote procedure call semantics.