CS 3733 Operating Systems, Spring 2006 Assignment 3


Due March 31

In this assignment we will implement a simple parallel make using named pipes. Get a copy of the restart library, restart.c along with restart.h. Do not modify these. Link your program to restart.c to use the functions defined in this library. Use this library to make your programs safe to use with signals.

Use this cover sheet as the first sheet when handing in your assignment. Make sure you include your makefile and lint output.

This assignment has several parts. Each time you start a new part, create a new directory for the programs and copy all of the programs from the previous part into this directory.


Part 0: Write a function:
int read_token(int fd, char *s, int slen)
which reads a token from the open file descriptor given by the first parameter. It puts the token it in the buffer, s. The last parameter is the size of the buffer. The delimiters are blanks, tabs and newlines. Skip the leading delimiters, put the token in s and terminate it with a string terminator. If the token and terminator will not fit in slen bytes, stop reading and return -1. If successful, return 0. If 0 is returned, s will contain a string that does not contain any delimiters. This function should never allow a buffer overflow to occur. This function should be safe to use with signals. Use r_read to read one byte at a time, first skipping delimiters, then using token characters until a delimiter is found.


Part 1: Write a program in compile_one.c that takes 3 command line parameters. The first parameter, path, is as in Assignment 1, Part 2. The other two, requestpipe and replypipe are the names of FIFOs that we assume already exist. Open each of these for reading and writing. Insert the source code for read_token in this file. Read one token from requestpipe using read_token. This is the name of a C source file (without the extension). As in Assignment 1, fork a child process that execs the C compiler using the path and the token. Wait for the child, get the return status, and send a single byte to the replypipe. If the compile was successful, send back the byte '1' (ASCII code 49). Otherwise, send back the byte '0' (ASCII code 48).

Test this as follows:

Save your test output. When handing this in, label it so that it is clear what is generating each part of the output and that the tests were successful.


Part 2: Copy compile_one.c into compile_daemon.c and modify it so that it creates an object file rather than an executable:
/path/cc -c filename.c

Now modify it so that it reads tokens in a loop. Each time it reads a token, it forks, execs, waits, and writes to replypipe. Once the pipes are opened successfully, this program should never exit, even if an error occurs. Make sure you check for all possible errors and handle them as appropriate. Note that this is doing the compiles serially. It does not start the next one until the previous one is finished. Test this as before using 3 windows. Label and save the output.


Part 3: Write a program in compile_all.c that takes at least 4 command line parameters. The first two are the requestpipe and replypipe. The third parameter, exfile, will be ignored until Part 4. The additional parameters: file1, file2, ... filen are the names of C source files without the extension. For each of these, generate a token and write that token to the requestpipe. Use a separate write for each token. Remember that a token always has at least one delimiter following it. Now read n bytes from the replypipe. Count the number of successes and the number of failures and report the results.

You can assume that it is an error if the token is longer than some reasonable value. Handle this as an error. In no situation should a buffer overflow be possible.

You can test this program independently of Part 2 by using two additional windows to read from requestpipe and write to replypipe. When you think it is working, run one copy of compiledaemon in one window and run compile_all in another. Test this with at least 5 files to compile. Note that you are compiling the files serially.


Part 4: Modify compile_all.c so that if all of the separate compiles were successful, it creates an executable from the created object files. Do this with a fork, exec and wait as before, but the execed program will now execute:
cc -o exfile file1.o file2.o file3.o ... filen.o


Part 5: Now try to use your programs to do a parallel compile. In two windows, start copies of compile_daemon using the same pair of pipes for each. Perform the following tests by running compile_all in a third window.
  1. Modify compile_all so that it does a sleep(1) after writing each token. Run it and watch what happens. How many compiles are done by each copy of compile_daemon? Does it perform correctly?
  2. Remove the sleep and try again. What happens?
  3. If the programs generated the correct output, describe whether you think the two programs work together correctly. Remember that to be correct, the output would need to be correct, independent of the way the system schedules the processes.
  4. If the programs did not give correct output, explain why.
  5. Perform the following test. Put a sleep in the loop of read_token. What does this do to the behavior of the programs? Explain what is happening.
  6. What operation would need to be atomic in order to make these programs correct? Can you suggest a way to do this?


Part 6 (Extra Credit): Design and implement a protocol using one additional pipe that allows for maximum parallelism during compilation but synchronizes the reading from the requestpipe.