by: George North
439-68-5643
Program Assignment 2
Principles of Operating Systems I
CSCI 4401
Fall 1995
Instructor: M. Rasit Eskicioglu
System Logic Diagram
What I have Learned
Program Design
Program Analysis
Some Code
The Cigarette Smokers problem generalizes to the client - server model. Common examples of client server systems are: Finger, Telnet, ftp, NFS (Network File System), X Window System, Gopher. During this last year, the World Wide Web has become the most popular of these type of systems. In this model, the (one or more) server process provides a service to the (one or more) client processes. When service is available, the server process listens on a known communications link, and the client process uses a connect method to reach the server. The pipe is the IPC mechanism most characteristic of UNIX. A pipe permits a reliable unidirectional byte stream between two processes. Implemented as an ordinary file, it is of a small fixed size. A benefit to this structure of pipes is that data are seldom actually written to disk -- kept in memory by the normal block buffer cache. BSD sockets are Unix system calls for this type of protocol. They are part of the Unix kernel and are the lowest practical level of implementing client/server systems. Sockets have been adopted by the PC world and form the highest common denominator for IPC between PC and Unix platforms. As low level protocols, sockets are powerful, flexible, and system dependent. The low level and concurrent nature of sockets makes coding tedious and debugging difficult.
Network Support (especially over dial-up telephone lines)
may be the most common systems implemented using the
client - server model. User processes communicate
with network protocols (and thus with other processes
on other machines) via the socket facility. The server
usually uses fork to produce a new process after accepting
a request for service. In this way the original server
process continues to listen for more connection request.
When a connection needs to be closed, the child process
closes its socket and terminates -- effectively destroying
its associated socket. This method allows for acquisition
of finite supply of resources one at a time. And allows
access to these resources in different quantities.
For example:
Many clients may request service at the same time
Client connect to the service only one at a time
Once established, multiple client connections can be
services.
The UNIX socket protocols provide the tools needed by
software developers to implement interprocess communications
and to avoid the potential hazards of deadlock and
blocking.
shop.c
The server procedure, shop.c will randomly supply 2
of three components needed to make a cigarette. This
will be simulated by reading a file from standard input
with the next components to make available.
smoker.c
The three (3) client procedures will each have one of
the three components needed to complete a cigarette.
Shop process will assume that three (3) smoker process exist and are requesting connection and access to the two missing components needed to make a cigarette. Shop will provide ONLY one pair of components at a time. As follows:
Shop listens (select) for a connection then accepts maximum of three connections from Smoker processes. Using a loop until end of file, Shop reads next two components from standard input. These components are (broadcast) output (write) to all Smokers. Shop than selects the smoker with missing component, than (using read()) waits for Smoker to signal that he is finished. Shop now loops to get next two components.
Each Smoker process establishes their own socket connection with the shop. In the main loop, Smoker uses select to insure a message is available for reading. Than reading the two components available decides if to respond to this message. If Smoker holds missing component, he responds by writing a message with missing component, than sleeps a while (smokes) before writing a message that he is finished. Smoker now loops to await next components.
Refer to the Shop - Smoker logic diagram above ...
In their main loops, Shop and Smokers processes use write(), select(), read() to control communications. Shop broadcasts to all Smokers the two available components using write(). Smokers use select() to insure a message is available, then read(). Shop waits for a response, using select() to detect which Smoker is responding. Since read() is a blocking construct, select() is used to prevent a process from entering a blocked state without expectation of completion. Write() is not a blocking construct and does not need similar protection. When two processes will exchange a known number of messages, only the first read() needs to be protected with select() -- as is the case at the end of Shop's main loop.
Both deadlock and starvation are easily possible in a client/server message exchange system like the one described above. Every critical construct needs to be checked for possible error conditions and provided with safe exit methods.
Proper termination of all processes is also important. In above system, Shop notifies all Smokers with a special message that communications is termination. The Smoker process uses one read() construct to detect both next components and end of communications. This insures that Shop and Smoker processes will coordinate termination.
Shop -- Main Loop while(not end of file) get(2components); { for(every smoker process) /* broadcast available components */ { if (wirte(smokerSock(#), ³2components², sizeof(2components)) < 0) { perror(³??? no smokers²); exit (?); } } successfulRead = 0; do /* loop waiting a response */ { /* select a responding smoker */ if (select(smokerSock(#)+1, &readset, NULL, etc,)) == -1 { perror(³??? no smokers²); exit (?); } /* read smoker reply */ if (FD_ISSET(smokerSock(#), &readset)) { if (read(smokerSock(#), etc.)) < 0 { perror(³??? no smokers²); exit (?); } /* wait for smoker to finish */ if (read(smokerSock(#), etc.)) < 0 { perror(³??? no smokers²); exit (?); } } successfulRead = 1; } while(successfulRead == 0); } Smoker -- Main Loop while(message NOT equal to ³DN²) /* loop until shop closes */ { /* select a message from shop */ if (select(shopSock+1, &readset, NULL, etc,)) == -1 { perror(³??? no shop²); exit (?); } /* read shop message */ if (FD_ISSET(shopSock, &readset)) { if (read(shopSock, etc.)) < 0 { perror(³??? no shop²); exit (?); } } /* is this my missing component */ if (this is my missing component) { if (wirte(shopSock, ³myComponents², sizeof(myComponents)) < 0) { perror(³??? no shop²); exit (?); } sleep(1); /* let shop know I¹m done */ if (wirte(shopSock, ³finished², sizeof(³finished²)) < 0) { perror(³??? no shop²); exit (?); } } }