CIS 307: Guidelines for the solution of Homework 3

Organization of the program

We have four processes - HMW_MAIN, RAND_PROC1, RAND_PROC2, and STORE_MANAGER. HMW_MAIN first creates the pipes C_BOX, RAND_BOX1, and RAND_BOX2 and then spawns the three child processes RAND_PROC1, RAND_PROC2 and STORE_MANAGER. STORE_MANAGER maintains a table of ordered pairs in STORE, and processes serially requests for read and update operations on STORE from RAND_PROC1 and RAND_PROC2 received through C_BOX, placing the responses in RAND_BOX1 and RAND_BOX2 respectively. HMW_MAIN interacts with the child processes to make available statistical information about RAND_PROC1 or RAND_PROC2, and to terminate all processes according to the user's choice.

The child processes inherit the descriptors for the pipes from HMW_MAIN. RAND_PROC1 and RAND_PROC2 execute identical algorithms but use different seeds for the random number generator. Given this set up, the program may be organized into three files - hmw_main.c, rand_proc.c and store_manager.c (additional header files may be used to group together data structures and operations needed in each process). Each child process operates in a copy of the address space of the parent process HMW_MAIN as it begins execution. It closes all pipe descriptors except those that it needs in order to interact with the other child process(es) and invokes the function representing the appropriate child process. Thus STORE_MANAGER would invoke, for example,

store_manager(arg1,arg2,arg3)

where

are the arguments to be passed to the function in store_manager.c.
The function store_manager is defined in store_manager.c.
Likewise RAND_PROC1 and RAND_PROC2 would invoke

rand_proc(arg1,arg2,arg3,arg4)

where

are the arguments to the function defined in rand_proc.c.

Structure of STORE

STORE_MANAGER maintains a table of pairs (STORE_ID, STORE_ELEM) in STORE, where STORE_ID and STORE_ELEM are character strings. In order to simplify the transmission of messages through the pipes we will define STORE_ID and STORE_ELEM to be fixed-length strings. For uniformity we shall use the following definitions :
  const

    STORE_ID_SIZE = 14;
    STORE_ELEM_SIZE = 8;
    STORE_SIZE = 10;
     
  type

    STRING_TYPE_ID = array [1..STORE_ID_SIZE] of char;
    STRING_TYPE_ELEM = array [1..STORE_ELEM_SIZE] of char;

    STORE_ENTRY_TYPE = record
                         STORE_ID : STRING_TYPE_ID;
                         STORE_ELEM : STRING_TYPE_ELEM;
                       end;

    STORE_TYPE = array [1..STORE_SIZE] of STORE_ENTRY_TYPE;

  var
    
    STORE : STORE_TYPE;
Since STORE is maintained by STORE_MANAGER and RAND_PROC1 and RAND_PROC2 are not supposed to access it directly, these declarations should be local to STORE_MANAGER.

Initialization of STORE

STORE_MANAGER initializes STORE before it starts processing requests for read and update operations from RAND_PROC1 and RAND_PROC2. It assigns to the STORE_ID component of each element of STORE a unique value that is never modified by subsequent (read or update) operations. The values chosen are fixed-length strings and represent names of stocks like "intel" and "microsoft" (padded with enough spaces at the end to make up the appropriate size). This set of values should be made available to RAND_PROC1 and RAND_PROC2 (for example, through a header file specifying these values that is attached to rand_proc.c and store_manager.c through #include directives) to enable them to generate read and update requests that can be satisfied. The STORE_ELEM component of each element of STORE is assigned the string representation of some random value in an interval of your choice.

Choice of arguments for STORE_READ() and STORE_UPDATE() operations

As stated above, STORE_MANAGER initializes STORE before it starts accepting requests for read and update operations. Correctness requires that STORE_MANAGER serialize the read and update operations and thus maintain the consistency of STORE.

Format of entries in the log file LOG.DAT, CHILDprocid.DAT

RAND_PROC1 and RAND_PROC2 write into the files CHILDprocid.DAT the requests made to and the responses received from STORE_MANAGER for read and update operations. Since the log file is our primary source of information in determining whether operations on STORE are performed consistently, we must ensure that all the relevant information about each request and the corresponding response are written to the log file. Each entry in the log file should identify the operation requested (read or update), the parameters of the operation, the status returned for the operation by STORE_MANAGER, and a timestamp. Each entry may be written on a separate line for clarity.

The following are examples of the suggested format :

    12345678901234  12345678
 R  ibm             < timestamp >
 R  ibm                32.48  OK   < timestamp >
 U  intel             127,48  OK   < timestamp >
 U  intel             127,48  OK   < timestamp >
 R  zaccarote       < timestamp >
 R  zaccarote       xxxxxxxx  ERR  < timestamp >
The last operations fails because "zaccarote" is not a valid STORE_ID value in STORE.

The format of the LOG.DAT file is similar except that it starts with a "1" or a "2".

Format of messages exchanged through pipes

STORE_MANAGER retrieves messages one at a time from C_BOX, carries out on STORE the operation requested in the message, and places the response in the pipe designated for the originator of the message. Since the update operation requires one more operand than the read operation, a message requesting an update operation would be longer than one asking for a read operation. However, Unix pipes are byte-oriented and treat data passing through them as raw streams of characters, oblivious to any kind of structure it may be organized into. Thus STORE_MANAGER would be able to retrieve the exact number of bytes making up a message through a SINGLE read on C_BOX only if it could predict in advance the type of operation on STORE (read or update) that would be specified in the message. Since these operations are chosen at random by RAND_PROC1 and RAND_PROC2, STORE_MANAGER would not be be able to anticipate them, and would have to adopt an elaborate procedure to retrieve one message at a time from C_BOX. We may simplify matters by arranging to use messages of the same length (incurring some loss in effective bandwidth) while requesting both read and update operations. A message specifying a read operation is padded at the end with enough spaces to make it as long as one asking for an update operation before being written into C_BOX, and STORE_MANAGER ignores the dummy characters while parsing the message. This makes for a simpler design, accompanied, however, by a slight reduction in communication bandwidth.

The above discussion applies to messages exchanged through RAND_BOX1 and RAND_BOX2 as well. Response messages for all cases except that of an invalid read operation have the same length, but the latter requires fewer characters. All cases may, however, be handled in a uniform manner by getting STORE_MANAGER to put in some padding in the messages containing the response to invalid read operations.

Interaction of the processes with the user

The parent process HMW_MAIN, after creating the child processes, prompts the user repeatedly to ask whether he wants statistical information from either RAND_PROC1 or RAND_PROC2, or to have all processes terminated.

If statistical information is requested, HMW_MAIN sends the signal SIGUSR1, a signal with user defined semantics, to the appropriate process, which in turn catches the signal and displays on the screen the number of read and update operations it has completed. The response from RAND_PROC1 directly to the screen, for example, would look like

RAND_PROC 1 : Completed Reads = 8; Requested Reads =9; Completed Updates = 6; Requested Updates = 6;

Since the signal catcher is not allowed to take any user defined arguments, the variables recording the number of read and update operations completed should be made global to it. When the process returns from the signal catching function, it resumes its normal operation, going back to where it was before being interrupted by the signal.

If the user indicates that he would like all processes terminated, HMW_MAIN sends SIGTERM signals to all three child processes, and waits for each child to terminate, invoking one of the wait functions as many times as necessary. Each child process informs the user of the receipt the SIGTERM signal, and terminates itself invoking exit(). HMW_MAIN confirms the termination of a child process each time it returns from the wait function, and finally invokes exit() to terminate itself. Here is an example of screen output resulting from the user's choice of terminating all processes :

  RAND_PROC2       :  Terminating in response to SIGTERM signal.
  STORE_MANAGER    :  Terminating in response to SIGTERM signal.
  HMW_MAIN         :  RAND_PROC2 terminated.
  RAND_PROC1       :  Terminating in response to SIGTERM sigal.
  HMW_MAIN         :  STORE_MANAGER terminated.
  HMW_MAIN         :  RAND_PROC1 terminated.
  HMW_MAIN         :  Terminating after all child processes.

The Structure of STORE_MANAGER

Because of our assumption that the operations on STORE are slow, the STORE_MANAGER is not easy to write. We need to separate the beginning of READ/UPDATE operations from their end, and allow other activities to take place in the interim. Since the completion of the operations occurs after a second, we need to schedule all such completions using an agenda as we did in our first homework. Only this time we will need to use a real time clock with the alarm function. The handler for the completion of the alarm will take care of what needs to be done at completions. The code in the main portion of the STORE_MANAGER function will need to disable the alarm before manipulating the data shared with the handler and re-enable the alarm when done.

ingargiola@cis.temple.edu
ppappach@thunder.ocis.temple.edu