In this assignment, up to 10 child processes will cooperate to compute the sum of integers obtained from a parent process. Each child will count and add the integers it receives and return these two values - count and sum - along with the child's pid to the parent. The parent then combines the results from the children to get the total count and sum. Note that some children may get no integers and should return a count of 0 and a sum of 0.
These processes will not share any memory and so the communication between the parent and children must be handled differently than Project I.
The replacement for shared memory will be Unix Named Pipes or FIFO's.
The parent will write the integers into one named pipe, sumfifo, and the children will read the integers from this pipe.
After the the children finish (your job again!), they will write their results to another fifo, reply_sumfifo, which the parent will read once for each child created.
There are two different ways that the termination problem could be handled. In a previous version I had the parent write -1 to the FIFO to let a child know the end of data had been reached.
However, if the data FIFO is opened by the parent and the child(ren) in the WRITE ONLY and READ ONLY mode, the end of data can be coded exactly the same as end of a normal file. One has to be careful about a possible deadlock, however. That is the approach taken here and the one you are to use.
Named Pipes are similar to files. They appear in you directory just like named files. However, there are differences.
If no processes have a named pipe open, then any data in the pipe drains out and the pipe is empty.
If a process reads data from a pipe, the data is removed! So a pipe is a communication channel rather than a permanent data repository.
Generally read's and write's on a pipe are guaranteed to be atomic. The exception is if there are several writers to the same pipe and the amount of data being written on any one write call is greater than the remaining capacity of the pipe.
Although Unix only mandates that the guaranteed pipe capacity is at least 512 bytes, most implementations will have a size more like 32K bytes.
Here is code to create a named pipe (fifo) used in the sample program:
char fifoname[256]; char replyfifo[256]; strcpy(fifoname, "sumfifo"); strcpy(replyfifo, "reply_sumfifo"); if (mkfifo(fifoname, 0600) == -1 ) { if (errno != EEXIST ) { perror("Fatal Error: "); exit(0); } }
The parameters to mkfifo are the name of the new fifo and the permissions - 0600 is like file permissions prw-------. The owner's processes have read and write permission, but no one else. The p means the type of this "file" is a pipe.
A process must open a FIFO before using it, similar to opening a file:
int fd; // Used as a "file" (i.e. a pipe) descriptor; // Reads and writes will use this descriptor, // instead of the name of the FIFO fd = open(fifoname, O_RDWR); if (fd == -1) { printf("Fifo parent unable to open fifo %s\n", fifoname); exit(0); } ... close(fd);
There is a blocking issue:
A write only open (O_WRONLY) will block until another process has the FIFO open for reading. A read only open (O_RDONLY) will block until another process has the FIFO open for writing.
There is some danger if a writing process closes a pipe before any reading process opens it. Then there will be an interval of time in which no process has the pipe open and all the data will drain out. Subsequent reads will then block.
#include <unistd.h> ssize_t read(int fildes, void *buf, size_t nbyte); ssize_t write(int fildes, const void *buf, size_t nbyte);
For example, here is the parent's code in the sample version which writes the first N integers into the FIFO with file descriptor fd:
for(i = 1; i <= N; i++) { write(fd, &i, sizeof(i)); }
An example of read is in the child program which is reading the integers from the FIFO with file descriptor in fd:
while( read(fd, &data, sizeof(data)) > 0 ) { numreceived++; sum += data; debug.printf("d", "Child %ld received: item %d\n", pid, data); }
From the Unix man pages:
When attempting to read from an empty pipe or FIFO: + If no process has the pipe open for writing, read() will return 0 to indicate end-of-file. + If some process has the pipe open for writing and O_NONBLOCK is set, read() will return -1 and set errno to EAGAIN. + If some process has the pipe open for writing and O_NONBLOCK is clear, read() will block until some data is written or the pipe is closed by all processes that had the pipe open for writing.
How do the children know when all the data has been sent by the parent?
Depending on how the FIFOs are opened, this can appear to the children the same as reaching end of file on a normal file. The conditions are:
If the parent has closed the FIFO, then when it is empty the child's read will return 0 for the number of bytes read. This means "end of file." If the FIFO is not closed, then the read of an empty FIFO simply waits; it doesn't return until some data is available or the FIFO is closed.
The previous comments go a long way to solving the termination problem. However, there is a scenario that must be avoided:
/*----------------------------------------------------------------------+ | r = ready or running, b = blocked, ... = created, not yet scheduled | +-----------------------------------------------------------------------+ | parent child1 child2 | +-----------------------------------------------------------------------+ | create child1 start(r) | | create child2 open fifo RDONLY(b) ... | | rrr bbb ... | | open fifo WRONLY(r) rrr ... | | rrr rrr ... | | rrr open reply WRONLY(b) ... | | rrr bbb ... | | open reply RDWR(r) rrr ... | | rrr rrr ... | | rrr rrr ... | | rrr read fifo(b) ... | | write fifo(r) rrr ... | | write fifo(r) read fifo(r) ... | | --- --- ... | | close(fifo) rrr ... | | rrr write reply start | | rrr quit open fifo RDONLY(b) | | read reply(r) bbb | | read reply(b) bbb | | bbb bbb | | deadlock deadlock | +-----------------------------------------------------------------------*/
An initial version of the program is provided. It works fine for only one child. But executing the program with 2 or more children runs the risk of the deadlock scenario described above.
Some sort of "handshake" protocol between the parent and the child(ren) is needed before the parent starts writing the data. This protocol needs to guarantee that all children have opened the fifo for reading before the parent starts writing to it.
This initial version of the program consists of the files: fifoparent.cpp and fifochild.cpp. Additional files provided are debugobject.cpp, debugobject.h, and Makefile. The latter file allows you to (re)compile the project files by simply typing 'make' at the prompt (or make -n to show you what would be compiled without actually doing it).
These files are also bundled together in one jar file, prog2Files.jar.
Once you have copied this jar file to a suitable directory in your unix account, you can extract the files in the usual way:
jar xvf prog2Files.jar This should extract the files: fifoparent.cpp fifochild.cpp debugobject.h debugobject.cpp Makefile
All these files (.cpp, .h, .jar and Makefile) can be copied directly to your unix account form the ~glancast/343class directory or downloaded from http://condor.depaul.edu/~glancast/343class.
The fifoparent.cpp is for the parent process. It creates up to ten child processes that each execute the fifochild (executable for fifochild.cpp).
Also look at the comments in the parent and child files for further information; e.g., the use of the debug.print is described in the child program.
You need to devise the "handshake" protocol.
Send only your modified parent and child programs, I don't need the debugobject.* files.