Unit-3 (OS) Material - 1
Unit-3 (OS) Material - 1
The system consisting of cooperatingsequential processes or threads, all running asynchronously and
possiblysharing data. We illustrated this model with the producer-consumer problem,described how a bounded
buffer could be used to enable processes to sharememory.
Solution allows at most BUFFER.SIZE - 1 items in the buffer at the sametime. Suppose we want to modify
the algorithm to remedy this deficiency. Onepossibility is to add an integer variable counter, initialized to 0.
counter isincremented every time we add a new item to the buffer and is decrementedevery time we remove one
item from the buffer. The code for the producerprocess can be modified as follows:
while (true)
{
/* produce an item in nextProduced */
while (counter == BUFFER.SIZE)
; /* do nothing */
buffer[in] = nextProduced;
in = (in + 1) % BUFFER-SIZE;
counter++;
}
The code for the consumer process can be modified as follows:
while (true)
{
while (counter == 0)
; /* do nothing */
nextConsumed = buffer [out] ,-
out = (out + 1) % BUFFER_SIZE;
counter--;
/* consume the item in nextConsumed */
}
Although both the producer and consumer routines are correct separately,they may not function correctly
when executed concurrently. As an illustration,suppose that the value of the variable counter is currently 5 and
that theproducer and consumer processes execute the statements "counter++" and"counter—" concurrently.
We can show that the value of counter may be incorrect as follows. Notethat the statement "counter++"
may be implemented in machine language (ona typical machine) as
Register1=- counter
Register1 = register1 + 1
counter =register1
where register1is a local CPU register. Similarly, the statement "counter—" isimplemented as follows:
register2=counter
register2 = register2- 1
counter = register2
where again register2is a local CPU register. Even though register1andregister2may be the same physical
register (an accumulator, say), rememberthat the contents of this register will be saved and restored by the
interrupthandler.
The concurrent execution of "counter++" and "counter—" is equivalentto a sequential execution where the
lower-level statements presented previouslyare interleaved in some arbitrary order. One such interleaving is
Register1= counter {register1 = 5}
register 1= register1+ 1 {register1= 6}
register2. = counter {register2 =5}
register2 = registeri — 1 {register2 =4}
counter = register1 {counter = 6}
counter = register2 {counter = 4}
Notice that we have arrived at the incorrect state "counter == 4", indicatingthat four buffers are full, when,
in fact, five buffers are full. If we reversed theorder of the statements, we would arrive at the incorrect
state"counter —— 6".
1
We would arrive at this incorrect state because we allowed both processesto manipulate the variable
counter concurrently. A situation like this, whereseveral processes access and manipulate the same data
concurrently and theoutcome of the execution depends on the particular order in which the accesstakes place, is
called a race condition. To guard against the race conditionabove, we need to ensure that only one process at a
time can be manipulatingthe variable counter. To make such a guarantee, we require that the processes
be synchronized in some way.
1. The Critical-Section Problem
Consider a system consisting of n processes {PQ, PI, ..., P,,~\}. Each processhas a segment of code, called
a critical section, in which the process maybe changing common variables, updating a table, writing a file, and
so on.The important feature of the system is that, when one process is executing inits critical section, no other
process is to be allowed to execute in its criticalsection. That is, no two processes are executing in their critical
sections at thesame time. The critical-section problem is to design a protocol that the processes
can use to cooperate. Each process must request permission to enter its criticalsection. The section of code
implementing this request is the entry section. Thecritical section may be followed by an exit section. The
remaining code is theremainder section. The general structure of a typical process P, is shown inFigure 6.1. The
entry section and exit section are enclosed in boxes to highlightthese important segments of code.
do{
\\entry section
critical section
\\exit section
\\remainder section
} while (TRUE);
General structure of a typical process Pi.
A solution to the critical-section problem must satisfy the following threerequirements:
1. Mutual exclusion. If process P; is executing in its critical section, then noother processes can be executing in
their critical sections.
2. Progress. If no process is executing in its critical section and someprocesses wish to enter their critical
sections, then only those processesthat are not executing in their remainder sections can participate in the
decision on which will enter its critical section next, and this selectioncannot be postponed indefinitely.
3. Bounded waiting. There exists a bound, or limit, on the number of timesthat other processes are allowed to
enter their critical sections after aprocess has made a request to enter its critical section and before that
request is granted.
2. Peterson's Solution
It is a classic software-based solution to the critical-sectionproblem known as Peterson's solution.
Peterson's solution is restricted to two processes that alternate executionbetween their critical sections and
remainder sections. The processes arenumbered Po and Pi. For convenience, when presenting P,-, we use Pj
todenote the other process; that is, j equals 1 — i.
Peterson's solution requires two data items to be shared between the twoprocesses:
int turn;
boolean f l a g [2] •
The variable turn indicates whose turn it is to enter its critical section. That is,if turn == i, then process P;
is allowed to execute in its critical section. Theflag array is used to indicate if a process is ready to enter its critical
section.For example, if f lag[i] is true, this value indicates that P; is ready to enterits critical section.
To enter the critical section, process P, first sets flag[i] to be true andthen sets turn to the value j, thereby asserting
that if the other process wishesto enter the critical section, it can do so. If both processes try to enter at thesame
time, turn will be set to both i and j at roughly the same time. Onlyone of these assignments will last; the other
will occur but will be overwrittenimmediately. The eventual value of turn decides which of the two processesis
allowed to enter its critical section first.
do
{
flag[i] = TRUE;
turn = j ;
while (flag[j] turn == j ) ;
critical section
flag[i] = FALSE;
remainder section
} while (TRUE);
The structure of process P-, in Peterson's solution.
2
We now prove that this solution is correct. We need to show that:
1. Mutual exclusion is preserved.
2. The progress requirement is satisfied.
3. The bounded-waiting requirement is met.
To prove property 1, we note that each P; enters its critical section onlyif either flag[j] == false or turn --
i. Also note that, if both processescan be executing in their critical sections at the same time, then flag [0] ==
flag [1] == true. These two observations imply that Po and Pi could not havesuccessfully executed their while
statements at about the same time, since thevalue of turn can be either 0 or 1 but cannot be both. Hence, one of
the processes—say Pj—must have successfully executed the while statement, whereas P,had to execute at least
one additional statement ("turn == j"). However, since,at that time, f lag[j] == true, and turn == j, and this
condition will persistas long as Pj is in its critical section, the result follows: Mutual exclusion ispreserved.
To prove properties 2 and 3, we note that a process P, can be prevented fromentering the critical section
only if it is stuck in the while loop with the conditionflag [j] == true and turn == j; this loop is the only one
possible. If P; is notready to enter the critical section, then flag [j] == false, and P; can enter itscritical section. If
Pj has set flag [j ] to true and is also executing in its whilestatement, then either turn == i or turn == j . If turn
== i, then P, will enterthe critical section. If turn == j, then Pj will enter the critical section. However,once P;
exits its critical section, it will reset f lag[j] to false, allowing P, toenter its critical section. If Pj resets flag [j ] to
true, it must also set turn to i.
Thus, since P, does not change the value of the variable turn while executingthe while statement, P,- will
enter the critical section (progress) after at mostone entry by P/ (bounded waiting).
3. Synchronization Hardware
We have just described one software-based solution to the critical-sectionproblem. We explore several
more solutions to thecritical-section problem using techniques ranging from hardware to softwarebasedAPIs
available to application programmers. Hardware features can make any programming task easier and improve
system efficiency. In this section, we present some simple hardware instructionsthat are available on many
systems and show how they can be used effectivelyin solving the critical-section problem.
The critical-section problem could be solved simply in a uniprocessor environment, we could be sure that
the current sequenceof instructions would be allowed to execute in order without preemption. Unfortunately, this
solution is not as feasible in a multiprocessor environment.Disabling interrupts on a multiprocessor can be time
consuming, as themessage is passed to all the processors. This message passing delays entry intoeach critical
section, and system efficiency decreases.
boolean TestAndSet(boolean *target) {
boolean rv = *target;
*target = TRUE;
return rv;
The definition of the TestAndSet () instruction.
do {
while (TestAndSetLock(&lock) )
; // do nothing
// critical section
lock = FALSE;
// remainder section
}while (TRUE);
Mutual-exclusion implementation with TestAndSet ( ) .
The TestAndSet() instruction can be defined as shown in Figure . The important characteristic is that this
instruction is executed atomically.Thus, if two TestAndSet C) instructions are executed simultaneously (each ona
different CPU), they will be executed sequentially in some arbitrary order. Ifthe machine supports the TestAndSet
() instruction, then we can implementmutual exclusion by declaring a Boolean variable lock, initialized to
false.The structure of process P, is shown in above Figure.
The SwapO instruction, in contrast to the TestAndSet0 instruction,operates on the contents of two words;
it is defined as shown in Figure .Like the TestAndSet 0 instruction, it is executed atomically. If the
machinesupports the SwapO instruction, then mutual exclusion can be provided asfollows. A global Boolean
variable lock is declared and is initialized to false.In addition, each process has a local Boolean variable key. The
structure ofprocess P, is shown in Figure .
3
void Swap(boolean *a, boolean *b)
{
boolean temp = *a;
*a = *b;
*b = temp;
}
The definition of the Swap () instruction.
do { ,
key = TRUE;
while (key == TRUE)
Swap (&lock, &key) ,-
// critical section
lock = FALSE;
// remainder section
}while (TRUE);
Mutual-exclusion implementation with the SwapO instruction.
4. Semaphores
The various hardware-based solutions to the critical-section problem (usingthe TestAndSetC) and SwapO
instructions) arecomplicated for application programmers to use. To overcome this difficulty,we can use a
synchronization tool called a semaphore.
A semaphore S is an integer variable that, apart from initialization, isaccessed only through two standard
atomic operations: wait () and signal ().The waitO operation was originally termed P (from the Dutch probercn,
"totest"); signal () was originally called V (from verhogen, "to increment"). Thedefinition of wait() is as follows:
wait(S) {
while (S <= 0)
; // no-op
S--;
}
The definition of signal () is as follows:
signal(S) {
S++;
}
All the modifications to the integer value of the semaphore in the wait ()and signal() operations must be
executed indivisibly. That is, when oneprocess modifies the semaphore value, no other process can
simultaneouslymodify that same semaphore value.
In addition, in the case of wait(S), thetesting of the integer value of S (S < 0), and its possible modification
(S—),must also be executed without interruption. We shall see how these operationscan be implemented in
Section 6.5.2; first, let us see how semaphores can beused.
Operating systems often distinguish between counting and binary semaphores.The value of a counting
semaphore can range over an unrestricted domain.The value of a binary semaphore can range only between 0
and 1. On somesystems, binary semaphores are known as mutex locks, as they are locks thatprovide mutual
Exclusion.
We can use binary semaphores to deal with the critical-section problem formultiple processes. The n
processes share a semaphore, mutex, initialized to 1.Each process P, is organized as shown in Figure.
do {
wait(mutex);
// critical section
signal(mutex);
// remainder section
}while (TRUE);
Mutual-exclusion implementation with semaphores.
Counting semaphores can be used to control access to a given resourceconsisting of a finite number of
instances. The semaphore is initialized to thenumber of resources available. Each process that wishes to use a
4
resourceperforms a waitQ operation on the semaphore (thereby decrementing thecount). When a process releases
a resource, it performs a signal () operation(incrementing the count). When the count for the semaphore goes to
0, allresources are being used. After that, processes that wish to use a resource willblock until the count becomes
greater than 0.
The main disadvantage of the semaphore definition given here is that it requiresbusy waiting. While a
process is in its critical section, any other process thattries to enter its critical section must loop continuously in
the entry code. Thiscontinual looping is clearly a problem in a real multiprogramming system,where a single CPU
is shared among many processes. Busy waiting wastesCPU cycles that some other process might be able to use
productively. Thistype of semaphore is also called a spinlock because the process "spins" whilewaiting for the
lock.
To overcome the need for busy waiting, we can modify the definition ofthe wait () and signal () semaphore
operations. When a process executes thewait () operation and finds that the semaphore value is not positive, it
mustwait. However, rather than engaging in busy waiting, the process can blockitself. The block operation places
a process into a waiting queue associatedwith the semaphore, and the state of the process is switched to the
waitingstate. Then control is transferred to the CPU scheduler, which selects anotherprocess to execute.
A process that is blocked, waiting on a semaphore S, should be restartedwhen some other process executes
a signal() operation. The process isrestarted by a wakeup () operation, which changes the process from the
waitingstate to the ready state. The process is then placed in the ready queue.
To implement semaphores under this definition, we define a semaphore asa "C" struct:
typedef struct {
int value;
struct process *list;
} semaphore;
Each semaphore has an integer value and a list of processes l i s t . Whena process must wait on a
semaphore, it is added to the list of processes. Asignal () operation removes one process from the list of waiting
processesand awakens that process.
The wait () semaphore operation can now be defined as
wait(semaphore *S) {
S->value—;
if (S->value < 0) {
add this process to S->list;
block();
} }
5
event in questionis the execution of a signal() operation. When such a state is reached, theseprocesses are said to
be deadlocked.
To illustrate this, we consider a system consisting of two processes, P1 and P2 , each accessing two
semaphores, S and Q, set to the value 1:
P1 P2
wait(S); wait(Q);
wait(Q); wait(S);
--------- -----------
--------- ------------
signal(S); signal(Q);
signal(Q); signal(S);
Suppose that P1executes wait (S) and then P2 executes wait (Q). When P1 executes wait(Q), it must wait
until P2 executes signal(Q). Similarly, whenP2 executes wait(S), it must wait until P1 executes signal(S). Since
thesesignal () operations cannot be executed, P1 and P2 are deadlocked.
We say that a set of processes is in a deadlock state when every process inthe set is waiting for an event
that can be caused only by another process in theset.
Another problem related to deadlocks is indefinite blocking, or starvation,a situation in which processes
wait indefinitely within the semaphore.Indefinite blocking may occur if we add and remove processes from the
listassociated with a semaphore in LIFO (last-in, first-out) order.
5. Classic Problems of Synchronization
In this section, we present a number of synchronization problems as examples. In our solutionsto the
problems, we use semaphores for synchronization.
The Bounded-Buffer Problem
The bounded-buffer problem is commonlyused to illustrate the power of synchronization primitives.
We assume that the pool consists of n buffers, each capable of holdingone item.
The mutex semaphore provides mutual exclusion for accesses to thebuffer pool and is initialized to the
value 1.
The empty and f u l l semaphorescount the number of empty and full buffers. The semaphore empty is
initializedto the value n; the semaphore f u l l is initialized to the value 0.
The code for the producer process is shown in below Figure ; the code forthe consumer process is shown
in below Figure. We can interpret this code as the producerproducing full buffers for the consumer or as the
consumer producing emptybuffers for the producer.
do {
// produce an item in nextp
wait(empty);
wait(mutex);
// add nextp to buffer
signal(mutex);
signal(full);
}while (TRUE) ,-
The structure of the producer process.
do {
wait(full);
wait(mutex);
// remove an item from buffer to nextc
signal(mutex);
signal(empty);
// consume the item in nextc
}while (TRUE);
The structure of the consumer process.
6
Obviously, if two readers access the shared data simultaneously, noadverse affects will result. However,
if a writer and some other thread (eithera reader or a writer) access the database simultaneously, chaos may
ensue.To ensure that these difficulties do not arise, we require that the writershave exclusive access to the shared
database. This synchronization problem isreferred to as the readers-writers problem.
The readers-writers problem, requires that no readerwill be kept waiting unless a writer has already
obtained permission to usethe shared object. In other words, no reader should wait for other readers to
finish simply because a writer is waiting.
In the solution to the readers-writers problem, the reader processesshare the following data structures:
semaphore mutex, wrt;
int readcount;
The semaphores mutex and wrt are initialized to 1; readcount is initializedto 0. The semaphore wrt is
common to both reader and writer processes.
The mutex semaphore is used to ensure mutual exclusion when the variablereadcount is updated. The
readcount variable keeps track of how manyprocesses are currently reading the object.
The semaphore wrt functions as amutual-exclusion semaphore for the writers. It is also used by the first reader
that enters or exits the critical section. It is not used by readers whoenter or exit while other readers are in their
critical sections or last.
do {
wait(wrt);
// writing is performed
signal (wrt) ,-
}while (TRUE);
The structure of a writer process.
do {
wait(mutex);
readcount + + ;
if (readcount == 1)
wait(wrt);
signal(mutex);
// reading is performed
wait (mutex) ,-
readcount--;
if (readcount == 0)
signal(wrt);
signal(mutex);
Jwhile (TRUE);
The structure of a reader process.
The code for a writer process is shown in above Figure; the code for a readerprocess is shown in above
Figure. Note that, if a writer is in the critical sectionand n readers are waiting, then one reader is queued on wrt,
and n — 1 readersare queued on mutex. Also observe that, when a writer executes signal (wrt),we may resume
the execution of either the waiting readers or a single waitingwriter. The selection is made by the scheduler.
The Dining-Philosophers Problem
Consider five philosophers who spend their lives thinking and eating. Thephilosophers share a circular table
surrounded by five chairs, each belongingto one philosopher. In the center of the table is a bowl of rice, and the
table is laidwith five single chopsticks. When a philosopher thinks, she doesnot interact with her colleagues. From
time to time, a philosopher gets hungryand tries to pick up the two chopsticks that are closest to her (the
chopsticksthat are between her and her left and right neighbors). A philosopher may pickup only one chopstick
at a time. Obviously, she cannot pick up a chopstick thatis already in the hand of a neighbor. When a hungry
philosopher has both herchopsticks at the same time, she eats without releasing her chopsticks. Whenshe is
finished eating, she puts down both of her chopsticks and starts thinkingagain.
8
wait(accessWRSeats); # Awake - try to get access to modify # of available seats, otherwise sleep.
numberOfFreeWRSeats +=1# One waiting room chair becomes free.
signal(barberReady) # I am ready to cut.
signal(accessWRSeats) # Don't need the lock on the chairs anymore.
# (Cut hair here.)
} while (true);
def Customer():
do# Run in an infinite loop to simulate multiple customers.
{wait(accessWRSeats) # Try to get access to the waiting room chairs.
if numberOfFreeWRSeats >0: # If there are any free seats:
numberOfFreeWRSeats -=1# sit down in a chair
signal(custReady) # notify the barber, who's waiting until there is a customer
signal(accessWRSeats) # don't need to lock the chairs anymore
wait(barberReady) # wait until the barber is ready
# (Have hair cut here.)
else: # otherwise, there are no free seats; tough luck --
signal(accessWRSeats) # but don't forget to release the lock on the seats!
# (Leave without a haircut.)
} while (true);
6. Monitors
Although semaphores provide a convenient and effective mechanism forprocess synchronization, using
them incorrectly can result in timing errors that are difficult to detect.
To illustrate how, we review the semaphore solution to the criticalsectionproblem. All processes share a
semaphore variable mutex, which is.initialized to 1. Each process must execute wait (mutex) before entering
thecritical section and signal (mutex) afterward. If this sequence is not observed,two processes may be in their
critical sections simultaneously.
Let us examinethe various difficulties that may result.
• Suppose that a process interchanges the order in which the wait(j andsignal () operations on the semaphore
mutex are executed, resulting inthe following execution:
signal(mutex);
critical section
wait(mutex);
In this situation, several processes may be executing in their critical sectionssimultaneously, violating the
rmitual-exclusion requirement.
• Suppose that a process replaces signal (mutex) with wait (mutex). Thatis, it executes
wait(mutex);
critical section
wait(mutex);
In this case, a deadlock will occur.
• Suppose that a process omits the wait (mutex), or the signal (mutex), orboth. In this case, either mutual
exclusion is violated or a deadlock willoccur.
To deal with such errors, researchers have developed high-level languageconstructs. In this section, we
describe one fundamental high-level synchronizationconstruct—the monitor type.
Usage
A type, or abstract data type, encapsulates private data with public methodsto operate on that data. A
monitor type presents a set of programmer-definedoperations that are provided mutual exclusion within the
monitor. The syntax of a monitor is shown in Figure.
monitor monitor name f
{
II shared variable declarations
procedure PI ( . . . ) {
}
p r o c e d u r e P 2 ( . . . ) {…..}
::::::::::::::::::::::::::::::::::::::::
p r o c e d u r e P n ( . . . ) {….}
9
initializationcode(...){
……..
}
}
Syntax of a monitor.
Thus, a procedure defined within a monitor can access onlythose variables declared locally within the
monitor and its formal parameters.Similarly, the local variables of a monitor can be accessed by only the
localprocedures.
The monitor construct ensures that only one process at a time can beactive within the monitor.
Consequently, the monitor construct, as defined so far, is not sufficiently powerful formodeling some
synchronization schemes. For this purpose, we need to defineadditional synchronization mechanisms. These
mechanisms are provided bythe condition construct. A programmer who needs to write a tailor-
madesynchronization scheme can define one or more variables of type condition:
condition x, y;
The only operations that can be invoked on a condition variable are wait ()and signal(). The operation
x.waitO ;
means that the process invoking this operation is suspended until anotherprocess invokes
x.signal();
The x. signal () operation resumes exactly one suspended process. If noprocess is suspended, then the signal ()
operation has no effect; that is, thestate of x is the same as if the operation had never been executed. Contrast this
operation with the signal () operation associated withsemaphores, which always affects the state of the semaphore.
Schematic view of a monitor. Monitor with condition variables.
Now suppose that, when the x. s ignal () operation is invoked by a processP, there is a suspended process
Q associated with condition x. Clearly, if thesuspended process Q is allowed to resume its execution, the signaling
process Pmust wait. Otherwise, both P and Q would be active simultaneously within themonitor. Note, however,
that both processes can conceptually continue withtheir execution. Two possibilities exist:
1. Signal and wait. P either waits until Q leaves the monitor or waits foranother condition.
2. Signal and continue. Q either waits until P leaves the monitor or waitsfor another condition.
Dining-Philosophers Solution Using Monitors
We now illustrate monitor concepts by presenting a deadlock-free solution tothe dining-philosophers
problem. This solution imposes the restriction that aphilosopher may pick up her chopsticks only if both of them
are available. Tocode this solution, we need to distinguish among three states in which we mayfind a philosopher.
For this purpose, we introduce the following data structure:
enum {thinking, hungry, eating} s t a t e [5] ;
Philosopher i can set the variable s t a t e [i] = eating only if her twoneighbors are not eating: ( s t a te [(i+4) °/»
5] != eating) and ( s t a te [(i+1)% 5] != eating).
We also need to declarecondition self [5];
where philosopheri can delay herself when she is hungry but is unable toobtain the chopsticks she needs.
We are now in a position to describe our solution to the dining-philosophersproblem. The distribution of
the chopsticks is controlled by the monitor dp,whose definition is shown in Figure.
dp.pickup(i);
eat
dp.putdown(i);
10
Each philosopher, before starting toeat, must invoke the operation pi ckup (). This may result in the
suspension ofthe philosopher process. After the successful completion of the operation, thephilosopher may eat.
Following this, the philosopher invokes the putdownOoperation. Thus, philosopher i must invoke the operations
pi ckup () andputdownO in the following sequence:
monitor DP
{
enum { THINKING; HUNGRY, EATING) state [5] ;
condition self [5];
void pickup (int i) {
state[i] = HUNGRY;
test(i);
if (state[i] != EATING) self [i].wait;
}
12