0% found this document useful (0 votes)
14 views12 pages

Sample

The document outlines three programming assignments involving inter-process communication (IPC) in C. The first assignment focuses on a producer-consumer problem using shared memory, the second involves creating a command-input and execute-command system using pipes, and the third simulates a game with a parent process and child processes using signals. Each assignment requires the implementation of specific C programs and adherence to detailed instructions regarding process management and communication.

Uploaded by

vobigo8622
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views12 pages

Sample

The document outlines three programming assignments involving inter-process communication (IPC) in C. The first assignment focuses on a producer-consumer problem using shared memory, the second involves creating a command-input and execute-command system using pipes, and the third simulates a game with a parent process and child processes using signals. Each assignment requires the implementation of specific C programs and adherence to detailed instructions regarding process management and communication.

Uploaded by

vobigo8622
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

Q1] Shared Memory :

One producer and n consumers share a memory M[] capable of storing two int variables.
The producer generates items (random integers) in M[1] for a predetermined number t of times. For
each item generated, the producer specifies in M[0] the consumer (an integer in the range 1, 2, 3, . .
. , n) for which the item written in M[1] is meant. The designated consumer reads M[1], and sets
M[0] to 0, indicating that the item is consumed. After all of the t items are generated and consumed,
the producer writes –1 to M[0]. After reading this special value of M[0], each consumer prints some
aggregate information, and terminates. Finally, the producer terminates too after printing some
aggregate information.

In order to implement this set of actions, write a C program prodcons.c. The parent process (call it
P) plays the role of the producer. P reads n (the number of consumers) and t (the number of items to
be produced) from the user or as command-line arguments. Then, P creates a shared-memory
segment M capable of storing two int variables. P also initializes M[0] to 0 (implying that no item is
available for consumption at the beginning). P then forks n child processes C1, C2, . . . , Cn which play
the roles of the n consumers. These child processes (or consumers) are numbered 1, 2, . . . , n. After
this, P goes to a production loop, and each Ci goes to a consumption loop. The loops run until all of
the t items are produced and consumed. These loops work as follows.

Production Loop :

For each i = 1, 2, . . . , t, the producer P (parent in our case) generates a random 3-digit int value item
and a random consumer c in the range 1, 2, . . . , n. P waits (busy wait) until M[0] becomes 0. When
M[0] becomes 0, P sets M[0] to c and M[1] to item (in that order). An optional delay (you can use
usleep()) between setting M[0] and setting M[1] should be used if a compile-time macro SLEEP is set.

After producing t items, P waits (busy wait) until M[0] becomes 0 (that is, the last item is consumed
by the designated consumer child). P then writes –1 to M[0], and waits until all of the n child
processes terminate. P then prints, for each consumer c, the count of items produced for c, and the
sum of these items.

P finally removes the shared-memory segment M, and exits.

Consumption Loop :

The c-th consumer waits until M[0] becomes c or –1. If M[0] becomes c, the consumer reads M[1] as
the next item meant for it. When M[0] becomes –1, the consumption loop is broken. The number of
items read by the consumer and the sum of these items are then printed, and the child process
terminates.
Compile Time Tags :

The default behavior of your program should be to print only an initial message and the final
statistics. If the compile-time flag VERBOSE is set, then the production and the consumption of each
item should also be printed (see the Sample Output). Another compile-time flag SLEEP (already
mentioned above) dictates whether there is no delay between the setting of M[0] and the setting of
M[1] by the producer (this should be the default behavior if the flag is not set) or there is a small
delay (of 1–10 microseconds) between these two assignments. This delay simulates preemption of P
(which would otherwise be very difficult to reproduce), and highlights the necessity of
synchronization for this producer-consumer problem

Submit a single C source file prodcons.c.


Q2 . Pipes

In this assignment, you write a single C program CSE.c. Compile the program to an executable file
called CSE. The program deals with three initial processes called C, S, and E. They work as follows.

Supervisor (S):

This is the parent process. This process creates the necessary pipe(s), and then forks two child
processes henceforth referred to as the “First Child” and the “Second Child”, respectively. Each of
these child processes opens an xterm to run ./CSE itself for interacting with the user.

Command-input child (C):

This child keeps on reading lines of commands from the user in its own xterm, and sending the
commands verbatim to the other child.

Execute-command child (E):

This child keeps on reading the commands sent by the other child, and executing them in the
foreground (by forking child processes) in its own xterm. The parent S initiates First Child in the C
mode and the Second Child in the E mode. It then waits until both the child processes terminate.

Each command supplied by the user can be one of the following:

1. A standard Linux command (like ls, ps, who, cat) with any number of command-line parameters
but without pipes (like |) or redirections (like >, >>, and <).

2. The special command exit that terminates both the child processes.

3. Another special command swaprole that swaps the roles (C and E) of the two child processes. That
is, when the user types this command (without any arguments) to the C child, the C child sends this
command verbatim to the E child. After this, the C child switches to the E (execute-command) mode,
and the E child switches to the C (command-input) mode. This command may be supplied by the
user any number of times in the appropriate C windows.

All of the processes C, S, and E run the same executable file ./CSE. If ./CSE is run without any
commandline arguments, it is the parent process (S). After the parent forks the First Child in C mode
or the Second Child in E mode, each child execs xterm which in turn runs ./CSE with appropriate
command-line arguments, so that each child knows its role (mode) along with other relevant
information (like pipe fd’s).

Follow the instructions given below in order to implement CSE.c.

• An xterm can be opened with a customized title to run an executable file (in our case, ./CSE) as

xterm -T "Customized Title" -e ./CSE arg1 arg2 arg3 . . .

• Child C reads user commands from its stdin. It sends the command to Child E via a pipe. Child E
reads each command from the pipe, and forks a (grand)child for executing the command (and itself
waits until the grandchild terminates). The grandchild prints the output of the command to the
stdout of E. This pipe must be created by the parent S, and the corresponding file descriptors must
be sent to the child processes as command-line arguments. Two child processes cannot themselves
establish an unnamed pipe between them. Do not involve the parent S in any child-to-child
communication.

• The stdout of C should be dup-ed as the write-end of the pipe. Likewise, the stdin of E should be
dup-ed as the read-end of the pipe. This allows the two child processes to use high-level I/O
primitives like scanf, printf, fgets, fputs, . . . This however prevents the Child C from printing a
prompt like Enter Command> to the terminal. Use the un-dup-ed stderr for that purpose. This is not
the intended use of stderr, but nobody will mind.

• A swaprole command will necessitate the restoring of the original stdin or stdout. This can be
achieved by maintaining copies of the original stdin and of the original stdout (can be done by dup
once at the beginning).

• Use fflush() whenever it is necessary to flush an output buffer (like stdout) immediately.

There are two more problems to solve. The solutions are outlined below.

1. Suppose that the user supplies the command swaprole in the C window. Immediately after this
input, this situation may happen: C writes the command to the pipe, switches role to E, and reads
from the pipe, before the earlier E gets a chance to read from the pipe. This should not happen. Do
not use sleep to delay the earlier C (and give the earlier E time to read from the pipe). Use another
pipe instead (to be created by S before forking).

2. Suppose that the user supplies an interactive command. When E lets this command get executed
by a (grand)child process, the stdin of E and of the grandchild is the pipe’s read end. That is, the
inputs for the interactive command should come from the other window C. This can be unimaginably
bad (for example, you will go mad if you open a text editor in one window and type in another
window). To solve this problem, note that the (interactive) command is not executed by E itself, so E
can afford to continue with the dup-ed definition of stdin. The grandchild which actually does the
running of the command should restore the original stdin for taking user inputs in the E window
itself. This strategy will also relieve E and the grandchild from any contention over user inputs. That’s
all indeed. Submit the single C source file CSE.c
Q3 . IPC using Signals

Think of n children C1, C2, C3, . . . , Cn standing in a circle, and playing a game with the parent P
standing at the center of the circle. P throws a ball to the children in a circular sequence. If the child
(say, Ci) to which the ball is thrown can catch the ball, then Ci continues to play. If Ci misses the ball,
then Ci goes out of the game. After each throw, the ball comes back to P who then throws the ball to
the next (in the circular order) child who is not yet out of the game. Eventually, n – 1 children miss
and go out of the game. The remaining child wins the game.

You need to implement this game as a multi-process application, where the processes can
communicate with one another by sending signals. You write two programs parent.c and child.c to
simulate the working of the parent and each child process. Suppose that these two programs are
compiled to the executable files parent and child. The program parent (which simulates P) is run
with one command-line argument n (the number of child processes). P creates n child processes Ci
which exec child i for i = 1, 2, 3, . . . , n. P also writes, in a text file childpid.txt, the child count n and
the PIDs of the n child processes created by P. Each child waits for some time (like one second) for P
to finish writing to childpid.txt. After this wait, each child reads n and the n PIDs from the text file
childpid.txt. After the child creation, P waits for some time (like two seconds) so that each child
process gets time to read this text file. These waits may be implemented by sleep() or usleep(), but
after this, no waiting based on these functions will be allowed.

The parent P starts the game by sending SIGUSR2 to C1. P then enters a loop which continues until
only one child is left as the player. Each child Ci, on the other hand, enters an infinite loop. The body
of each loop should contain the single system call pause() which lets the calling process wait until it
receives a signal (avoid busy waits). The game of throwing balls and catching/missing throws will be
implemented by sending and handling signals. In this assignment, we use the three signals SIGUSR1,
SIGUSR2, and SIGINT only.

For a child process Ci, receiving SIGUSR2 implies that a throw is made to it. It then randomly decides
whether it catches the ball (with probability 0.8) or misses the ball (with probability 0.2). If Ci can
catch the ball, it sends SIGUSR1 to P. If Ci fails to catch the ball, it sends SIGUSR2 to P. Depending on
the type of the signal received from the child Ci (to which the throw is made), the parent knows
whether that child continues to play the game or is out of the game. P records this information.

After the outcome of a throw is recorded as explained above, P initiates a printing of the current
status of all the n players. Since P has all the necessary information, it can do that printing itself.
However, as a part of this assignment, this printing should be done by the child processes. This is
achieved by sending SIGUSR1 to the child processes in turn. Recall that to a child process, SIGUSR2
means that a throw is made to it. On the other hand, the reception of SIGUSR1 initiates that child to
print its current status. The possible status of a child are PLAYING (written as ....), CATCHMADE
(written as CATCH), CATCHMISSED (written as MISS), and OUTOFGAME (written as blank). See the
sample at the end to know the format of printing. P initiates the printing process by sending
SIGUSR1 to C1. For each i < n, Ci prints its status, and then sends SIGUSR1 to the next child Ci + 1.
The last child Cn prints its status, but does not send SIGUSR1 to any other process. However, Cn
takes part in the synchronization activity in a different manner. Until all the child processes finish
writing their status, the parent P must wait before it can make the throw to the next playing child.
But you do not know many synchronization primitives at this moment, so let this wait be
accomplished by waitpid(). Before sending SIGUSR1 to C1, P forks a dummy child process D and
writes the PID of D in a text file dummycpid.txt. P then waits until D exits. When Cn is done printing
its status, it reads the PID of D from the file dummycpid.txt, and sends SIGINT to D. Write dummy.c
(the code for D) that enters an infinite loop of pause() at the beginning of its main().

When D exits, P wakes up, and works out the next playing child process Cnext to which the throw is
to be made. Note that P maintains the information of the status of all child processes. P should also
keep track of the child process to which the last throw is made. So P can determine Cnext easily. P
then sends SIGUSR2 to Cnext , and the game continues as explained above.

After n – 1 child processes miss throws, the parent sends SIGINT to all the n child processes. Only the
last playing child process prints a happy message, and exits (see the format in the sample). The other
processes exit without printing anything.

Notice that the sequencing of the throw-and-print cycle must be implemented only by signals (and
by the waitpid() in one case). No other synchronization mechanism is allowed. You may use
fflush(stdout); to avoid garbled output. But do not use any sleep or usleep calls (except only at the
very beginning, that is, before the game starts).

You may use the following makefile.

Submit a zip/tar/tgz archive containing the files parent.c, child.c, dummy.c, and makefile.

You might also like