THE PROBLEM
CS 3750 -- Operating Systems I
Statement of the "Packet Protocol"
Concurrent Programming Problem
User #0 User #1
| \ / |
| \ / |
| \/ |
| /\ |
| / \ |
| / \ |
Buffer #0 Buffer #1
| \ / |
| \ / |
| \/ |
| /\ |
| / \ |
| / \ |
Agent #0 Agent #1
Write the synchronization for a program that uses four threads in one task to
simulate the situation depicted in the diagram above. There are two
user threads and two agent threads.
The idea depicted by the diagram can be stated this way: There are two
different types of packets, say ethernet packets and token ring packets. User
#0 and User #1 are long-running applications that sometimes need to send a
packet. Each user sometimes needs to send an ethernet packet, and each user
sometimes needs to send a token ring packet.
Buffer #0 and Buffer #1 are two different specialized communication endpoints.
Buffer #0 is designed for ethernet packets - only ethernet packets, and Buffer
#1 is exclusively for token-ring packets. When a user thread has a packet it
needs to send, it has to copy the packet into the buffer that corresponds to
the packet type.
Agent #0 and Agent #1 are responsible for servicing Buffer #0 and Buffer #1,
by copying out and then transmitting packets. The agents are not
"specialists." Each agent is able to service either buffer. (This might be a
performance advantage. Suppose one of the buffers gets two packets in a row,
and one of the agents copies out the second packet while the first agent is
busy transmitting the first packet.) Agent #0 and Agent #1 both continually
scan both buffers, looking for one that is full and not yet being serviced by
the other agent.
Clearly these threads have to synchronize some of their actions with each
other. For example, we can't have two users writing data into the same buffer
at the same time. We can't have an agent copying a packet out of a buffer
while a user is writing a packet into the buffer. We can't have the same
packet copied out (and transmitted) twice.
User threads run in a loop, for as long as desired. In each iteration
of the loop, a user:
- simulates the need to send a packet of one type or the other
by randomly choosing Buffer #0 or Buffer #1,
- acquires exclusive access to the required buffer, after first waiting
as long as necessary for it to become available,
- copies a "packet" into the chosen buffer,
- announces that it has performed the copy operation
by writing a message to stdout, and
- performs actions that allow an agent to determine that there is
a packet ready to be copied out of the chosen buffer.
The "packet" contains only the id number of the user (0 or 1) and a
current packet serial number. To keep the program simple, the threads don't
actually put any message data into the buffers. However, as you write your
solution code, keep in mind that it is supposed to work flawlessly, no matter
how much data is in each packet.
agents also run in a loop, as long as users are active. In each
iteration, an agent:
- acquires exclusive access to a full buffer,
- copies out the contents of the buffer,
- writes a message to stdout announcing that the copy-out
operation was performed, and
- does something to allow users to find out that it is now OK
to copy a new packet into the buffer.
In creating the synchronization code, you may find it is convenient to use one
or more flags or status variables associated with each buffer. (However,
remember that such variables are typically shared among two or more threads,
so it is a critical section problem to regulate access to them.)
Your program must carefully prevent concurrent threads from corrupting the
contents of shared variables. On the other hand, a thread must never
unnecessarily prevent another thread from making progress.
To enforce exclusive access to shared buffers, and to prevent critical
operation from being performed out of order, you must use semaphores
implemented by sem.h and sem.cpp. The exact manner in which you employ the
semaphores is for you to decide.
users will write a specific output immediately after they place a
packet in a buffer. Example:
Copy IN: Packet #0 of USER #1 into buffer #0
The threads must be synchronized in such a way that a line of output like the
one above is always written on the screen before the message of an
agent appears on the screen, saying the agent has copied this packet
out of the buffer.
Agents have to cooperate to make sure that only one agent copies
out each packet. Each agent can and will copy packets out of either
buffer, in effect 'competing' with the other agent to copy out as many
packets as possible as soon as possible. The agents must be programmed
in such a way that if one agent is delayed for some reason, the other
agent must automatically 'take over' and do all the work, copying all
packets from both buffers. If one agent is busy working with the
packet from one buffer, and if there is a packet in the other buffer that
needs to be copied, then there must be nothing to prevent the other
agent from immediately working on the other buffer. Agents will
write a specific output immediately after they copy a packet from a buffer.
Example:
AGENT #1 copies packet #145 of USER #0 from buffer #1.
After a message on the screen announces that a user has copied a packet
into a buffer, the very next message about the buffer has to be the one
announcing that the same packet has been copied out.
So, for example, after we see this message,
Copy IN: Packet #145 of USER #0 into buffer #1
the next message having to do with buffer #1 has to be
AGENT #0 copies packet #145 of USER #0 from buffer #1.
or
AGENT #1 copies packet #145 of USER #0 from buffer #1.
Note: User and agent messages must be formatted so that the
packet, user, and buffer numbers all line up in columns. This will make it a
lot easier to check outputs. The shell
program provided to you formats the statements this way.
To test your program, you will use code in critical places that delays each
thread by a random amount of time. We do this by generating a random integer
n, and then making the thread execute
for (i = 0; i < n; i += 1) sched_yield();