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:
- Create the two pipes from the command line.
- In one window, start compile_one.
- In a second window, send a token to the requestpipe
using cat.
- In a third window, use cat to read from
the replypipe.
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.
- 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?
- Remove the sleep and try again. What happens?
- 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.
- If the programs did not give correct output, explain why.
- 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.
- 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.