CS4411 Intro. To Operating Systems Exam 1 Solutions Fall 2006

Download as pdf or txt
Download as pdf or txt
You are on page 1of 10

CS4411 Intro.

to Operating Systems Exam 1 Solutions – Fall 2006 1

CS4411 Intro. to Operating Systems Exam 1 Solutions


Fall 2006
1. Basic Concepts
(a) [10 points] Explain interrupts and traps, and provide a detailed account of the procedure that
an operating system handles an interrupt.
Answer: An interrupt is an event that requires the attention of the operating system. These
events include the completion of an I/O, a key press, the alarm clock going off, division by zero,
accessing a memory area that does not belong to the running program, and so on. A trap is an
interrupt generated by software.
When an interrupt occurs, the following steps will take place to handle the interrupt:
• The executing program is suspended and the control is transferred to the operating system.
Mode switch may be needed.
• A general routine in the operating system examines the received interrupt and calls the
interrupt-specific handler.
• After the interrupt is served, a context switch transfers the control to a suspended process.
Of course, mode switch may be needed.
See p. 7 of our textbook and class notes.

(b) [10 points] There are two types of clustered system, asymmetric and symmetric. What are the
major differences between these systems?
Answer: Clustering can be structured asymmetrically or symmetrically. In asymmetric clus-
tering, one machine is in hot-standby mode while the other is running the applications. The
hot-standby host machine does nothing but monitor the active server. If that server fails, the
hot-standby host becomes the active server. In symmetric mode, two or more hosts are running
applications, and are monitoring each other. See page 15 of our textbook and class notes.

(c) [10 points] Define the meaning of mechanism and policy in the separation of mechanism and
policy principle.
Answer: Mechanisms determine how to do something, and policies determine what will be done.
They are usually separated for flexibility.
See p. 56 of text and class notes details.

(d) [5 points] What is the differences between A & B and A | B in Unix, where A and B are two
binary executables?
Answer: In “A & B” programs A and B are run concurrently; however, A runs in background and
B in foreground. In “A | B” programs A and B are run concurrently so that the standard output
of A is connected to the standard input of B (i.e., A pipes its output to B). Note that, by default,
A takes its standard input from the standard input (i.e., the keyboard) and B sends its standard
output to standard output (i.e., screen).
This is a problem in a weekly reading list.

2. Fundamentals of Processes and Threads


(a) [15 points] Draw the state diagram of a process from its creation to termination, including all
transitions, and briefly elaborate every state and every transition.
Answer: The following state diagram is taken from my overhead which was covered in class. Fill
in the elaboration for each state and transition by yourself.
See p. 83 of our text and class notes.
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 2

process created reclaim system


and ready to run resouces and
destroy process
CPU is free
New Ready Running Term.
process
create new terminates
process and
allocate system time is up blocks for some events
resources (e.g., input/output)
event occurs

Waiting

(b) [10 points] There are three types of schedulers. What are they and what are their assigned
tasks?
Answer: There are three types of schedulers: long-term, short-term and medium-term schedulers.
The long-term scheduler, or job scheduler, selects jobs from the job pool, and loads into memory
and converts them processes for execution. The short-term scheduler, or CPU scheduler, selects
processes from the ready queue, and allocates the CPU to them. The medium-term scheduler
removes (i.e., swaps out) processes from memory (and from active contention for the CPU), and
thus reduces the degree of multiprogramming. At some later time, a process can be reintroduced
into memory and resume its execution from where it left off (i.e., swapped in). This scheme is
also referred to as swapping.
See pp. 101–103 of our text for the details.

(c) [10 points] What is thread cancelation? Define the term and discuss the two commonly used
versions and their differences.
Answer: Thread cancelation means terminating a thread before it completes. There are two
commonly used cancelation schemes, asynchronous and deferred.
Asynchronous cancelation means the target thread terminates immediately. Deferred cancelation
means the target thread can periodically check if it should terminate, allowing the target thread
an opportunity to terminate itself in an orderly fashion.
See p. 139 of our text and class notes.

3. Synchronization
(a) [10 points] Define the meaning of race condition? Answer the question first and use an execu-
tion sequence to illustrate your answer. You will receive no credit if only an example is
provided without an elaboration.
Answer: A race condition is a situation in which more than one processes or threads are executing
and accessing a shared data item concurrently, and the result depends on the order of execution.
The following is a very simple counter updating example discussed in class. The value of count
may be 9, 10 or 11, depending on the order of execution of the machine instructions of count++
and count--.
int count = 10;

Thread_1(...) Thread_2(...)
{ {
// do something // do something
count++; count--;
} }
The following execution sequence shows a race condition. There are two threads running concur-
rently (condition 1). Both threads access the shared variable count at the same time (condition
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 3

2). Finally, the computation result depends on the order of execution of the SAVE instructions
(condition 3). The table below shows the result being 9; however, if the two SAVE instructions are
switched (i.e., B’s runs first and A’s second), the result would be 11. Since all three conditions
of a race conditions are met, we have a race condition.

Thread_1 Thread_2 Comment


do somthing do somthing count = 10 initially
LOAD count Thread_1 executes count++
ADD #1
LOAD count Thread_2 executes count--
SUB #1
SAVE count count is 11 in memory
SAVE count Now, count is 9 in memory

Stating that “count++ followed by count--” or “count-- followed by count++” would produce
different results and hence a race condition is incorrect, because the threads do not access the
shared variable count at the same time (i.e., Condition 2).
See p. 193 of our text and class notes.

(b) [10 points] A good solution to the critical section problem must satisfy three conditions: mutual
exclusion, progress and bounded waiting. Explain the meaning of the progress condition. Does
starvation violate the progress condition? Note that there are two problems. You should
provide a clear answer with a convincing argument. Otherwise, you will receive no
credit.
Answer: If no process is executing in its critical section and some processes are waiting to enter
their critical sections, then only those processes that are not executing in their remainder section
can participate in the decision on which will enter its critical section next (i.e., only those wanted
to enter can join the competition and none of the other processes can have any influence), and
this selection cannot be postponed indefinitely.
The progress condition only guarantees the decision of selecting one process to enter a critical
section will not be postponed indefinitely. It does not mean a waiting process will enter its critical
section eventually. Therefore, starvation is not a violation of the progress condition. Instead,
starvation violates the bounded waiting condition.
See p. 194 of our textbook.

(c) [10 points] The methods Wait() and Signal() must be atomic to ensure a correct implemen-
tation of mutual exclusion. Use an execution sequence to show that if Wait() is not atomic then
mutual exclusion cannot be maintained. You must use an execution sequence as we did
many times in class to present your answer. Otherwise, you risk low or no credit.
Answer: If Wait() is not atomic, its execution may be switched in the middle. If this happens,
mutual exclusion will not be maintained. The following is a possible execution sequence, where
Count = 1 is the counter variable of the involved semaphore.
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 4

Process A Process B Count Comment


1 Initial value
LOAD Count 1 A executes Count-- of Wait()
SUB #1 1
LOAD Count 1 B executes Count-- of Wait()
SUB #1 1
SAVE Count 0 B finishes Count--
SAVE Count 0 A finishes Count--
if (Count < 0) 0 It is false from A
if (Count < 0) 0 It is false from B
Both A and B enter the critical section

This problem was assigned in class.

(d) [10 points] Consider the solution to the mutual exclusion problem for two processes P0 and
P1, where flag[2] is a Boolean array of two elements and turn is an integer variable with an
initial value 0 or 1. Both are global to processes P0 and P1 . Show, with a step-by-step execution
sequence table, that mutual exclusion is implemented incorrectly. You will risk low or very
low grade if you do not use an execution sequence table.
bool flag[2]; // global flags
int turn; // global turn variable

Process i (i = 0 or 1)

flag[i] = TRUE; // I am interested


while (turn != i) { // while it is not my turn
while (flag[j]) // while you are interested
; // do nothing: busy waiting
turn = i; // because your are not interested, it is my turn
}
// critical section
flag[i] = FALSE // I am done and not interested
Answer: Suppose turn is initially 0. Consider the following execution sequence:

P0 P1 flag[0] flag[1] turn


0 0 0
enters its critical section 1 0 0
flag[1] = TRUE 1 1 0
executes the outer while 1 1 0
executes the inner while 1 1 0
exits its critical section 1 1 0
flag[0] = FALSE 0 1 0
exits the inner while 0 1 0
flag[0] = TRUE 1 1 0
enters its critical section 1 1 0
turn = 1 1 1 1
enters its critical section 1 1 1

Therefore, both processes are in their critical sections and mutual exclusion is violated.
This is a problem in weekly reading list #4.
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 5

4. [15 points] We cannot assume any particular order when a process (or thread) is released from a
semaphore. A programmer wishes to overcome this problem by implementing a FIFO semaphore so
that the waiting processes will be released in a first-in-first-out order. The following is his first attempt.
In this programmer’s mind, a FIFO semaphore consists of an integer counter that is used to simulate a
semaphore’s counter, and a semaphore queue which is used to enforce the first-in-first-out order. The
two corresponding procedures are FIFO_Wait(..) and FIFO_Signal().
To guarantee atomic execution of these two procedures, a semaphore Mutex is used with an initial value
1. FIFO_Wait() uses Mutex to lock the procedure and checks the semaphore counter Counter. If this
counter is greater than zero, it is decreased by one. Then, FIFO_Wait() releases the procedure and we
are all set. If Counter is zero, then the caller must be queued. To this end, a semaphore X with initial
value 0 is allocated, and added to the end of a semaphore queue. Then, FIFO_Wait() releases the
procedure, and lets the caller wait on X. On the other hand, FIFO_Signal() first locks the procedure,
and checks if the semaphore queue is empty. If the queue is empty, the counter is increased by one,
unlocks the procedure, and returns. However, if there is a waiting process in the queue, the head of
the queue is removed and signaled so that the only waiting process on that semaphore can continue.
Then, this semaphore node is freed and the procedure is unlocked. Finally, the initialization procedure
FIFO_Init(), not shown below, sets the counter to an initial value and the queue to empty.

Semaphore Mutex = 1;
int Counter;

FIFO_Wait(...) FIFO_Signal(...)
{ {
Wait(Mutex); Wait(Mutex);
if (Counter > 0) { if (queue is empty) {
Counter--; Counter++;
Signal(Mutex); Signal(Mutex);
} }
else { /* must wait here */ else { /* someone is waiting */
allocate a semaphore node, X=0; remove the head X;
add X to the end of queue; Signal(X);
Signal(Mutex); free X;
Wait(X); Signal(Mutex);
} }
} }

Discuss the correctness of this solution. If you think it correctly implements a first-in-first-out semaphore,
provide a convincing argument to justify your claim. Otherwise, discuss why it is wrong with a step-
by-step explanation using an execution sequence.
Answer: One may point out that this “solution” implicitly assumes the FIFO ordering of the Mutex.
In other words, the arriving order of two processes at the semaphore Mutex will be maintained at the
exit point; however, this should not be the case because one should not make any assumption about
this order. While this is true, the major problem is not there, because in a lightly loaded system
processes rarely arrive at Mutex at the same time and the FIFO order may still be maintained. The
major problem is in the allocation and free of semaphore X. In fact, when you see a process allocating
something that is released by another process, you should smell something bad.
Suppose process A calls FIFO_Wait() and sees Counter being zero. Then, it allocates a semaphore
node X, adds it into the semaphore queue, and releases the lock. But, right before A can wait on
semaphore X, it is switched out and B is switched in. Then, process B calls FIFO_Signal(). This B
sees the semaphore queue being non-empty, and, as a result, removes the head semaphore node from
the queue. If this is the only semaphore node, it has to be the semaphore X that process A will be
waiting on. (Keep in mind that A was switched out before it can actually wait on this semaphore.)
Then, B signals semaphore X to release A. Now, what if B continues and A does not run immediately?
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 6

In this case, semaphore X is freed before A can continue. As a result, when A executes Wait(X),
semaphore X has already gone and A has no semaphore to wait on. The following table illustrates this
execution sequence. We assume that initially there is no process waiting on the FIFO semaphore.

No. Process Statement Statement


1 A calls FIFO_Wait()
2 A Wait(Mutex)
3 A sees Counter = 0
4 A allocates semaphore node X
5 A adds X into queue
6 A Signal(Mutex)
7 B Wait(Mutex)
8 B queue is not empty
9 B removes the head X
10 B Signal(X)
11 B free X
12 A Wait(X). Oops! where is X?

In fact, this is a race condition because semaphore X is shared by processes A and B. It is possible that
everything works fine. But, if the execution order is changed to the one described above, the result
would be different. Therefore, we have a race condition!

5. [25 points] There are two groups of threads A and B with unspecified number of threads in each
group. Two threads from group A and one thread from group B are required to establish a rendezvous
in order to perform a specific task. Thus, each thread has the following execution pattern:

thread-in-A(.....) thread-in-B(.....)
{ {
while (1) { while (1) {
// does something // does something
A-rendezvous(..); B-rendezvous(..);
// does something else // does something else
} }
} }

When a thread in group A (resp., B) reaches the rendezvous point, it calls function A-rendezvous()
(resp., B-rendezvous()). This function blocks the calling thread until a rendezvous of two-A-one-B
can be made. Once such a rendezvous occurs, two threads in A and one thread in B return from
A-rendezvous() and B-rendezvous(), respectively.
Use semaphores to write functions A-rendezvous() and B-rendezvous(). The syntax is unimportant.
For example, you may declare and initialize a semaphore S with “Sem S = 1” and use Wait(S) and
Signal(S) for semaphore wait and semaphore signal. Your implementation should not have any busy
waiting, race condition, and deadlock.
Answer: This problem is very similar to the “message exchange” problem discussed in class. You were
asked to study the correct solutions to this problem, which is based on the bounded-buffer problem.
The only difference between this and the “message exchange” problem is that this problem requires two
“A”’s to exchange messages with one “B”. The bounded-buffer problem can still be applied exactly
the same way as the “message exchange” problem. In what follows, we shall try to develop a correct
solution based on the bounded-buffer problem.
Since two A’s and one B are required to form a rendezvous, one may use two bounded-buffers X and
Y , each of which has two slots. When an A reaches the rendezvous point, it executes the PUT(X)
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 7

procedure placing its message into buffer X. Since X has two slots, at most two A’s can deposit
their messages into the buffer. The B that will form a rendezvous with these two A’s will retrieve the
two messages using GET(X). After this B successfully taking the two messages, it adds its own two
messages to buffer Y for the two A’s to retrieve. So, basically, we have the following pattern:

Thread A Thread B

PUT(X); GET(X);
GET(Y ); GET(X);
PUT(Y );
PUT(Y );

However, this scheme will not work properly. There are a number of potential problems
• It is possible that two B’s can start their own rendezvous at the same time. As a result, each
of them may execute the GET(X) taking out one of the two messages from the A’s. This would
generate an incorrect rendezvous. Consequently, we should guarantee that at any time there is
at most one B can form a rendezvous. This is easy to do with a mutex. So, we add a semaphore
B-Mutex with initial value 1.
• Thread A’s also has problems. Suppose A1 and A2 reach the rendezvous point, and successfully
deposit their messages. Then, A1 proceeds to GET(Y ) and successfully retrieves one of the two
messages left by B. This A1 may come back quickly, deposits its new message, and executes
GET(Y ) again. In this way, one B forms a rendezvous with the same A (i.e., A1), which is
incorrect. Therefore, we need some mechanism to prevent a thread from coming back so fast
before B can form a correct rendezvous. To this end, a semaphore A-exit with initial value 0 at
the end of thread A’s sequence would be needed. After a B has formed a correct rendezvous, it
signals A-exit twice to release both A’s. In this way, A1 won’t have any chance to come back
before a rendezvous can be made correctly.
• However, there could be more than two A’s arriving at the rendezvous point, which may cause a
potential problem even though buffer X only has two slots. For example, suppose A1 , A2 and A3
arrive at the rendezvous point, and A1 and A2 successfully deposit their messages with PUT(X).
Now, a B comes in and retrieves one message from buffer X. This would allow A3 to execute
PUT(X). It is possible that A3 runs faster than A2 , and reaches GET(Y ) with A1 at the same
time. Consequently, A1 and A3 would form a rendezvous with the B that has already retrieved
the messages from A1 and A2 . Of course, this is an incorrect rendezvous. To overcome this
problem, we need a semaphore A-enter with initial value 2 to limit the number of A’s that can
do message exchange even though buffer X has two slots.
From this discussion, a possible solution looks like the following. Note that the two Signal(A-enter)’s
in B may be moved to A following the two Wait(A-exit)’s.
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 8

Semaphore A-enter = 2;
Semaphore A-exit = 0;
Semaphore B-Mutex = 1;

Thread A Thread B

Wait(A-enter); Wait(B-Mutex);
PUT(X); GET(X);
GET(Y ); GET(X);
Wait(A-exit); PUT(Y );
PUT(Y );
Signal(A-exit);
Signal(A-exit);
Signal(A-enter);
Signal(A-enter);
Signal(B-Mutex);

The above can be greatly simplified. First, take a look at the thread A part. Since each buffer has a
not-full semaphore and a not-empty semaphore, expanding thread A yields the following:

Semaphore A-enter = 2;
Semaphore A-exit = 0;
Semaphore B-Mutex = 1;

Wait(A-enter);
Wait(X-not-full);
Signal(X-not-empty);
Wait(Y -not-empty);
Signal(Y -not-full);
Wait(A-exit);

Since Wait(A-enter) guarantees that only two A’s can execute the PUT() and GET() commands, buffer
X cannot be full when these two A’s are depositing messages. Therefore, Wait(X-not-full) is not
needed; however, Signal(X-not-empty) has to be there in order to inform B the fact that messages are
ready. The next Wait(Y -not-empty) and Signal(Y -not-full) are unnecessary for a simple reason.
At this point, B has made its two messages ready and the two A’s passing Signal(X-not=empty) will
wait for the messages, take them, and wait on semaphore A-exit. Since there is no actual message
exchange, the effect of waiting for messages, picking them up, and waiting to be released is actually
equivalent to waiting to be released only. Therefore, thread A can be greatly simplified to the following:

Semaphore A-enter = 2;
Semaphore A-exit = 0;
Semaphore B-Mutex = 1;

Wait(A-enter);
Signal(X-not-empty);
Wait(A-exit);

Now consider thread B. After expanding the GET()’s and PUT()’s, we have the following.
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 9

Semaphore A-enter = 2;
Semaphore A-exit = 0;
Semaphore B-Mutex = 1;

Wait(B-Mutex);
Wait(X-not-empty); // first GET(X)
Signal(X-not-full);
Wait(X-not-empty); // second GET(X)
Signal(X-not-full);
Wait(Y -not-full); // first PUT(Y )
Signal(Y -not-empty);
Wait(Y -not-full); // second PUT(Y )
Signal(Y -not-empty);
Signal(A-exit); // release first A
Signal(A-exit); // release second A
Signal(A-enter); // open for the next rendezvous
Signal(A-enter);
Signal(B-Mutex);

From A’s discussion, since buffer B does not play any role in the simplified version of thread A, there
is no need to keep buffer Y in B. Therefore, the above is simplified to the following:

Semaphore A-enter = 2;
Semaphore A-exit = 0;
Semaphore B-Mutex = 1;

Wait(B-Mutex);
Wait(X-not-empty); // first GET(X)
Signal(X-not-full);
Wait(X-not-empty); // second GET(X)
Signal(X-not-full);
Signal(A-exit); // release first A
Signal(A-exit); // release second A
Signal(A-enter); // open for the next rendezvous
Signal(A-enter);
Signal(B-Mutex);

Similar to thread A, the two Signal(X-not-full)’s are unnecessary. The reason is simple. There
are two A’s between Wait(A-enter) and Wait(A-exit), each of them adds a message into the 2-slot
buffer that was empty before Wait(A-enter), and, hence, there is never a buffer-not-full problem!
However, the two Wait(X-not-empty) cannot be eliminated because B must wait until messages
become available. After taking out the two buffer X related Signal() calls, B’s code becomes the
following:
CS4411 Intro. to Operating Systems Exam 1 Solutions – Fall 2006 10

Semaphore A-enter = 2;
Semaphore A-exit = 0;
Semaphore B-Mutex = 1;

Wait(B-Mutex);
Wait(X-not-empty); // first GET(X)
Wait(X-not-empty); // second GET(X)
Signal(A-exit); // release first A
Signal(A-exit); // release second A
Signal(A-enter); // open for the next rendezvous
Signal(A-enter);
Signal(B-Mutex);

Since there is no more buffer related operations, we may change the semaphore name X-not-empty
to something more meaningful. Since the wait on this semaphore indicates the fact that B enters the
initial phase of rendezvous, let us rename it as B-enter with an initial value 0 as B must wait first.
Finally, we have the following correct solution:

Semaphore A-enter = 2; // only allow 2 A’s to make a rendezvous


Semaphore A-exit = 0; // block the 2 A’s so that they won’t
// come back for another rendezvous too soon
Semaphore B-enter = 0; // let B to wait for 2 A’s
Semaphore B-mutex = 1; // only allow 1 B to make a rendezvous

A-rendezvous(..) // rendezvous point for threads in group A


{
Wait(A-enter); // A arrives. Only 2 A’s can pass through
Signal(B-enter); // let B knows an A is available
Wait(A-exit); // wait for a rendezvous
}

B-rendezvous(..) // rendezvous point for threads in group B


{
Wait(B-mutex); // an exclusive rendezvous
Wait(B-enter); // wait for the 1st A
Wait(B-enter); // wait for the 2nd A
Signal(A-exit); // both are there, release the 1st A
Signal(A-exit); // release the 2nd A
Signal(A-enter); // rendezvous done and release the lock
Signal(A-enter); // so two more A’s can go for their rendezvous
Signal(B-mutex); // release the mutual exclusion lock
}

We mentioned several times in class that classical problems are important because they serve as mod-
els for solving other problems. This problem is certainly a good example of using the bounded-buffer
problem in the first phase. Then, the solution is simplified to yield the current version. If you tried
to use counters, you are asking for trouble because it is usually not easy to ensure a correct protec-
tion against unintended or potential updates and tests. You should study the one-on-one solution on
ThreadMentor page and the solution above to learn how the ideas are developed and then simplified.

You might also like