You are on page 1of 57

KOTEBE METROPOLITAN UNIVERSITY

DEPARTMENT OF COMPUTER SCEINCE AND TECHNOLOGY


ASSINGMENT ON CLASSICAL SYNCHRONIZATION PROBLEM IN OPERATING
SYSTEM

COMPILED BY :-Beakal yewondwossen


RDCOS/018/08
Section A

MAY 26 2017
The Producer-Consumer Problem

A producer tries to insert data into an empty slot of the buffer. A consumer tries to remove data from a filled slot in the buffer. As you might have guessed by
now, those two processes won’t produce the expected output if they are being executed concurrently.
There needs to be a way to make the producer and consumer work in an independent manner.

Solution:
One solution of this problem is to use semaphores. The semaphores which will be used here are:
 m, a binary semaphore which is used to acquire and release the lock.
 empty, a counting semaphore whose initial value is the number of slots in the buffer, since, initially all slots are empty.
 full, a counting semaphore whose initial value is 0.
At any instant, the current value of empty represents the number of empty slots in the buffer and full represents the number of occupied slots in the buffer.

Producer Operation:
The pseudocode of the producer function looks like this:
do {
wait(empty); // wait until empty>0 and then decrement ‘empty’
wait(mutex); // acquire lock
/* perform the
insert operation in a slot */
signal(mutex); // release lock
signal(full); // increment ‘full’
} while(TRUE)

 Looking at the above code for a producer, we can see that a producer first waits until there is atleast one empty slot.

 Then it decrements the empty semaphore because, there will now be one less empty slot, since the producer is going to insert data in one of those slots.
 Then, it acquires lock on the buffer, so that the consumer cannot access the buffer until producer completes its operation.
 After performing the insert operation, the lock is released and the value of full is incremented because the producer has just filled a slot in the buffer.

Consumer Operation:
The pseudocode of the consumer function looks like this:
do {
wait(full); // wait until full>0 and then decrement ‘full’
wait(mutex); // acquire the lock
/* perform the remove operation
in a slot */
signal(mutex); // release the lock
signal(empty); // increment ‘empty’
} while(TRUE);
 The consumer waits until there is atleast one full slot in the buffer.
 Then it decrements the full semaphore because the number of occupied slots will be decreased by one, after the consumer completes its operation.
 After that, the consumer acquires lock on the buffer.
 Following that, the consumer completes the removal operation so that the data from one of the full slots is removed.
 Then, the consumer releases the lock.
 Finally, the empty semaphore is incremented by 1, because the consumer has just removed data from an occupied slot, thus making it empty
What is the producer consumer problem in operating systems?
In computing, the producer–consumer problem (also known as the bounded-buffer problem) is a classic example of a multi-process synchronization
problem. The problem describes two processes, the producer and the consumer, who share a common, fixed-size buffer used as a queue

• Unbounded buffer

• Producer process writes data to buffer

– Writes to In and moves rightwards

• Consumer process reads data from buffer

– Reads from Out and moves rightwards

– Should not try to consume if there is no data

– Bounded buffer: size ‘N’

– Producer process writes data to buffer

– Should not write more than ‘N’ items

– Consumer process reads data from buffer

– Should not try to consume if there is no data

– A number of applications:
– Compiler’s output consumed by assembler

– Assembler’s output consumed by loader

– Web server produces data consumed by client’s web browser

– Example: pipe ( | ) in Unix

– > cat file | more

– > prog | sort … what happens here?

• First attempt to solve:

Producer

while (true) {

/* produce an item in nextProduced*/

while (counter == N)

; /* do nothing */

buffer[in] = nextProduced;

in = (in + 1) % N;

counter++;

Shared: int counter;

any_t buffer[N];

Init: counter = 0;
Consumer

while (true) {

while (counter == 0)

; /* do nothing */

nextConsumed = buffer[out];

out = (out + 1) % N;

counter--;

/* consume an item in nextConsumed*/

Shared: Semaphores mutex, empty, full;

Init: mutex = 1; /* for mutual exclusion*/

empty = N; /* number empty bufs */

full = 0; /* number full bufs *

Producer

do {

...

// produce an item in nextp

...

P(empty);
P(mutex);

...

// add nextp to buffer

...

V(mutex);

V(full);

} while (true);

Consumer

do {

P(full);

P(mutex);

...

// remove item to nextc

...

V(mutex);

V(empty);

...

// consume item in nextc

...
} while (true);

Producer-consumer solution
Here is the producer code from my solution.

Producer solution

event = waitForEvent ()

mutex . wait ()

buffer . add ( event )

items . signal ()

mutex . signal ()

The producer doesn’t have to get exclusive access to the buffer until it gets an event. Several threads can run wait For Event concurrently.The items
semaphore keeps track of the number of items in the buffer. Each time the producer adds an item, it signals items, incrementing it by one.The
consumer code is similar

Consumer solution

items . wait ()

mutex . wait ()

event = buffer . get ()

mutex . signal ()

event . process ()

Again, the buffer operation is protected by a mutex, but before the consumer gets to it, it has to decrement items. If items is zero or negative, the
consumer blocks until a producer signals.Although this solution is correct, there is an opportunity to make one small improvement to its
performance. Imagine that there is at least one consumer in queue when a producer signals items. If the scheduler allows the consumer to run, what
happens next? It immediately blocks on the mutex that is (still) held by the producer.

Banker’s Algorithm
Banker’s algorithm is a deadlock avoidance algorithm. It is named so because this algorithm is used in banking systems to determine whether a loan can be
granted or not.
Consider there are n account holders in a bank and the sum of the money in all of their accounts is S. Everytime a loan has to be granted by the bank, it
subtracts the loan amount from the total money the bank has. Then it checks if that difference is greater than S. It is done because, only then, the bank would
have enough money even if all the n account holders draw all their money at once.
Banker’s algorithm works in a similar way in computers. Whenever a new process is created, it must exactly specify the maximum instances of each resource
type that it needs.
Let us assume that there are n processes and m resource types. Some data structures are used to implement the banker’s algorithm. They are:
 Available: It is an array of length m. It represents the number of available resources of each type. If Available[j] = k, then there are k instances
available, of resource type Rj.
 Max: It is an n x m matrix which represents the maximum number of instances of each resource that a process can request. If Max[i][j] = k, then the
process Pi can request atmost k instances of resource type Rj.
 Allocation: It is an n x m matrix which represents the number of resources of each type currently allocated to each process. If Allocation[i][j] = k, then
process Pi is currently allocated k instances of resource type Rj.
 Need: It is an n x m matrix which indicates the remaining resource needs of each process. If Need[i][j] = k, then process Pi may need k more instances
of resource type Rj to complete its task.
Need[i][j] = Max[i][j] - Allocation [i][j]

Assume we have nine tape drives. Consider whether or not the following states are safe or unsafe.
State Current Loan Maximum Need
Process A 0 3
Process B 3 5
Process C 4 7
 Since only 7 (3+4) tape drives are currently on loan (allocated), two (2) tape drives are still available.
 Process B can finish with only two additional tape drives.
 Once Process B is done, it will release all 5 tape drives, making the number of available tape drives = 5.
 With only three of these tape drives, either Process A or Process C may complete and release its tape drives.
 This means that there are two possible safe sequences: <Process B, Process A, Process C> and <Process B, Process C, Process A>.
 Thus, we say that this is a safe state.

Again assume we have nine tape drives. Consider whether or not the following states are safe or unsafe.
State Current Loan Maximum Need
Process A 5 7
Process B 2 5
Process C 1 3

 Since 8 (5+2+1) tape drives are currently on loan (allocated), only one tape drive is still available.
 None of the three processes can complete with only one additional tape drive.
 This means that there are no safe sequences possible.
 Thus, we say that this is an unsafe state.

Now return to the first example.


Suppose that Process C requests one tape drive.
If this request is granted, will we still be in a safe state?
State Current Loan Maximum Need
Process A 0 3
Process B 3 5
Process C 5 7

 The number of available tape drives is reduced to one (1).


 No process can be granted enough tape drives to complete.
 This means that there will be no safe sequences possible, if we grant Process C's request.
 Thus, granting this request will take us from a safe state to an unsafe state.

According to Deitel:
"An unsafe state does not imply the existence of deadlock. What an unsafe state does imply is simply that some unfortunate sequence of events might
lead to deadlock."
The Banker's algorithm:
Allows:
 mutual exclusion
 wait and hold
 no preemption
Prevents:
 circular wait
User process may only request one resource at a time.
System grants request only if the request will result in a safe state.

The Banker's algorithm: An Example


Assume we have the following resources:
 5 tape drives
 2 graphic displays
 4 printers
 3 disks
We can create a vector representing our total resources: Total = (5, 2, 4, 3).
Consider we have already allocated these resources among four processes as demonstrated by the following matrix named Allocation.
Process Name Tape Drives Graphics Printers Disk Drives
Process A 2 0 1 1
Process B 0 1 0 0
Process C 1 0 1 1
Process D 1 1 0 1

The vector representing the allocated resources is the sum of these columns:
Allocated = (4, 2, 2, 3).
We also need a matrix to show the number of each resource still needed for each process; we call this matrix Need.
Process Name Tape Drives Graphics Printers Disk Drives
Process A 1 1 0 0
Process B 0 1 1 2
Process C 3 1 0 0
Process D 0 0 1 0

The vector representing the available resources will be the sum of these columns subtracted from the Allocated vector: Available = (1, 0, 2, 0).

The Banker's algorithm:


1. Find a row in the Need matrix which is less than the Available vector. If such a row exists, then the process represented by that row may complete with
those additional resources. If no such row exists, eventual deadlock is possible.
2. You want to double check that granting these resources to the process for the chosen row will result in a safe state. Looking ahead, pretend that that
process has acquired all its needed resources, executed, terminated, and returned resources to the Available vector. Now the value of the Available
vector should be greater than or equal to the value it was previously.
3. Repeat steps 1 and 2 until
4. all the processes have successfully reached pretended termination (this implies that the initial state was safe); or
5. deadlock is reached (this implies the initial state was unsafe).

Following the algorithm sketched above,


 Iteration 1:
1. Examine the Need matrix. The only row that is less than the Available vector is the one for Process D.
2. Need(Process D) = (0, 0, 1, 0) < (1, 0, 2, 0) = Available
3. If we assume that Process D completes, it will turn over its currently allocated resources, incrementing the Available vector.
(1, 0, 2, 0) Current value of Available
+ (1, 1, 0, 1) Allocation (Process D)
````````````````
(2, 1, 2, 1) Updated value of Available

 Iteration 2:
1. Examine the Need matrix, ignoring the row for Process D. The only row that is less than the Available vector is the one for Process A.
2. Need(Process A) = (1, 1, 0, 0) < (2, 1, 2, 1) = Available
3. If we assume that Process A completes, it will turn over its currently allocated resources, incrementing the Available vector.
(2, 1, 2, 1) Current value of Available
+ (2, 0, 1, 1) Allocation (Process A)
````````````````
(4, 1, 3, 2) Updated value of Available

 Iteration 3:
1. Examine the Need matrix without the row for Process D and Process A. The only row that is less than the Available vector is the one for Process
B.
2. Need(Process B) = (0, 1, 1, 2) < (4, 1, 3, 2) = Available
3. If we assume that Process B completes, it will turn over its currently allocated resources, incrementing the Available vector.
(4, 1, 3, 2) Current value of Available
+ (0, 1, 0, 0) Allocation (Process B)
````````````````
(4, 2, 3, 2) Updated value of Available

 Iteration 4:
1. Examine the Need matrix without the rows for Process A, Process B, and Process D. The only row left is the one for Process C, and it is less
than the Available vector.
2. Need(Process C) = (3, 1, 0, 0) < (4, 2, 3, 2) = Available
3. If we assume that Process C completes, it will turn over its currently allocated resources, incrementing the Available vector.
(4, 2, 3, 3) Current value of Available
+ (1, 0, 1, 1) Allocation (Process C)
````````````````
(5, 2, 4, 3) Updated value of Available

Notice that the final value of the Available vector is the same as the original Total vector, showing the total number of all resources:
Total = (5, 2, 4, 2) < (5, 2, 4, 2) = Available
This means that the initial state represented by the Allocation and Need matrices is a safe state.
The safe sequence that assures this safe state is <D, A, B, C>.

Note: The Banker's algorithm can also be used in the detection of deadlock.
Disadvantages of the Banker's Algorithm
 It requires the number of processes to be fixed; no additional processes can start while it is executing.
 It requires that the number of resources remain fixed; no resource may go down for any reason without the possibility of deadlock occurring.
 It allows all requests to be granted in finite time, but one year is a finite amount of time.
 Similarly, all of the processes guarantee that the resources loaned to them will be repaid in a finite amount of time. While this prevents absolute
starvation, some pretty hungry processes might develop.
 All processes must know and state their maximum resource need in advance.
Example
Total system resources are:
A B C D
6 5 7 6
Available system resources are:
A B C D
3 1 1 2
Processes (currently allocated resources):
A B C D
P1 1 2 2 1
P2 1 0 3 3
P3 1 2 1 0
Processes (maximum resources):
A B C D
P1 3 3 2 2
P2 1 2 3 4
P3 1 3 5 0
Need = maximum resources - currently allocated resources
Processes (possibly needed resources):
A B C D
P1 2 1 0 1
P2 0 2 0 1
P3 0 1 4 0

Safe and Unsafe States


A state (as in the above example) is considered safe if it is possible for all processes to finish executing (terminate). Since the system cannot know when a
process will terminate, or how many resources it will have requested by then, the system assumes that all processes will eventually attempt to acquire their
stated maximum resources and terminate soon afterward. This is a reasonable assumption in most cases since the system is not particularly concerned with how
long each process runs (at least not from a deadlock avoidance perspective). Also, if a process terminates without acquiring its maximum resource it only makes
it easier on the system. A safe state is considered to be the decision maker if it's going to process ready queue.
Given that assumption, the algorithm determines if a state is safe by trying to find a hypothetical set of requests by the processes that would allow each to
acquire its maximum resources and then terminate (returning its resources to the system). Any state where no such set exists is an unsafe state.

We can show that the state given in the previous example is a safe state by showing that it is possible for each process to acquire its maximum resources and
then terminate.
1. P1 acquires 2 A, 1 B and 1 D more resources, achieving its maximum
 [available resource: <3 1 1 2> - <2 1 0 1> = <1 0 1 1>]
 The system now still has 1 A, no B, 1 C and 1 D resource available
2. P1 terminates, returning 3 A, 3 B, 2 C and 2 D resources to the system
 [available resource: <1 0 1 1> + <3 3 2 2> = <4 3 3 3>]
 The system now has 4 A, 3 B, 3 C and 3 D resources available
3. P2 acquires 2 B and 1 D extra resources, then terminates, returning all its resources
 [available resource: <4 3 3 3> - <0 2 0 1> + <1 2 3 4> = <5 3 6 6>]
 The system now has 5 A, 3 B, 6 C and 6 D resources
4. P3 acquires 1 B and 4 C resources and terminates
 [available resource: <5 3 6 6> - <0 1 4 0> + <1 3 5 0> = <6 5 7 6>]
 The system now has all resources: 6 A, 5 B, 7 C and 6 D
5. Because all processes were able to terminate, this state is safe
For an example of an unsafe state, consider what would happen if process 2 was holding 2 units of resource B at the beginning.
Semaphores
In 1965, Dijkstra proposed a new and very significant technique for managing concurrent processes by using the value of a simple integer variable to
synchronize the progress of interacting processes. This integer variable is called semaphore. So it is basically a synchronizing tool and is accessed only through
two low standard atomic operations, wait and signal designated by P() and V() respectively.
The classical definition of wait and signal are :
 Wait : decrement the value of its argument S as soon as it would become non-negative.
 Signal : increment the value of its argument, S as an individual operation.
A semaphore S is an integer variable that, apart from initialization, is
accessed only through two standard atomic operations :wait() and signal().
The wait() operation was originally termed P(from the Dutch proberen,“to
test”) ; signal() was originally called V (from verhogen,“to increment”). The
definition of wait() is as follows:
wait(S) {
while (S<=0)
; // busy wait
S--;
}
The definition of signal() is as follows :
signal(S){
S++;
}
All modifications to the integer value of the semaphore in thewait()and
signal()operations must be executed indivisibly. That is, when one process
modifies the semaphore value, no other process can simultaneously modify
that same semaphore value. In addition, in the case ofwait(S), the testing of
the integer value ofS(S≤0),aswellasitspossiblemodification(S--), must
be executed without interruption.
Properties of Semaphores
1. Simple
2. Works with many processes
3. Can have many different critical sections with different semaphores
4. Each critical section has unique access semaphores
5. Can permit multiple processes into the critical section at once, if desirable.
Types of Semaphores
Semaphores are mainly of two types:
1. Binary Semaphore
2. It is a special form of semaphore used for implementing mutual exclusion, hence it is often called Mutex. A binary semaphore is initialized to 1
and only takes the value 0 and 1 during execution of a program.
3. Counting Semaphores
4. These are used to implement bounded concurrency.
Limitations of Semaphores
1. Priority Inversion is a big limitation of semaphores.
2. Their use is not enforced, but is by convention only.
With improper use, a process may block indefinitely. Such a situation is called Deadlock. We will be studying deadlocks in details in coming lessons
Uses of semaphores
Semaphores can be used for more than simply protecting a critical section.
 to protect acccess to a critical section (e.g., the database in the R/Ws problem)
 as a mutex lock (e.g., to protect the shared variables used in solving a probem such as readerCount in the R/Ws problem above)
 to protect a relationship (e.g., empty and full as used in the P/C problem)
 to support atomicity (e.g., you pickup either both chopsticks as the same time or neither; you cannot pickup just one)
 to enforce an order on the interleaving of the statements in the processes to by synchronized (e.g., Thread2 printing before Thread1 in Order.java)
Semaphore Implementation
The definitions of the wait() and signal() semaphore operations just described present the same problem. To overcome the need
for busy waiting, we can modify the definition of the wait()and signal() operations as follows: When a process executes
The wait()operation and finds that the semaphore value is not positive,it must wait. However, rather than engaging in busy
waiting, the process can block itself. The block operation places a process into a waiting queue associated with the semaphore, and
the state of the process is switched to the waiting state. Then control is transferred
to the CPU scheduler, which selects another process to execute.A process that is blocked, waiting on a semaphore S,should be
restarted when some other process executes a signal()operation. The process is restarted by awake up()operation, which changes
the process from the waiting state to the ready state. The process is then placed in the ready queue. (The CPU may or may not be
switched from the running process to the newly ready process, depending on theCPU-scheduling algorithm.).To implement
semaphores under this definition, we define a semaphore as
follows:
typedef struct{
int value;
struct process *list;
}semaphore;
Each semaphore has an integervalueand a list of processeslist.When a process must wait on a semaphore, it is added to the list of
processes. A signal()operation removes one process from the list of waiting processes and awakens that process.
Now, thewait()semaphore operation can be defined as
wait(semaphore *S){
S->value--;
if (S->value < 0){
add this process toS->list;
block();
}
}
and thesignal()semaphore operation can be defined as
signal(semaphore *S){
S->value++;
if (S->value <= 0){
remove a processPfromS->list;
wakeup(P);
}
The block() operation suspends the process that invokes it. The wakeup (P) operation resumes the execution of a blocked
process P. These two operations are provided by the operating system as basic system calls.Note that in this
implementation,semaphore values may be negative,where as semaphore values are never negative under the classical definition
ofsemaphores with busy waiting. If a semaphore value is negative, its magnitude is the number of processes waiting on that
semaphore. This fact results from switching the order of the decrement and the test in the implementation of the
wait() operation.The list of waiting processes can be easily implemented by a link field in each process control block (PCB). Each
semaphore contains an integer value and a pointer to a list of PCBs. One way to add and remove processes from the list so as to
ensure bounded waiting is to use a FIFO queue, where the semaphore contains both head and tail pointers tothe queue. In general,
however, the list can use any queueing strategy. Correct usage of semaphores does not depend on a particular queueing strategy
for the semaphore lists.
It is critical that semaphore operations be executed atomically. We must guarantee that no two processes can execute wait() and
signal() operations on the same semaphore at the same time. This is a critical-section problem;and in a single-processor
environment, we can solve it by simply inhibiting interrupts during the time thewait()andsignal()operations are executing.This
scheme works in a single-processor environment because, once interrupts are inhibited, instructions from different processes
cannot be interleaved. Only the currently running process executes until interrupts are reenabled and the scheduler can regain
control.In a multiprocessor environment, interrupts must be disabled on every processor. Otherwise, instructions from different
processes (running on different processors) may be interleaved in some arbitrary way. Disabling interrupts on every processor can
be a difficult task and furthermore can seriously diminish performance. Therefore ,SMP systems must provide alternative locking
techniques—such as compare and swap() or spinlocks—to ensure thatwait() and signal() are performed atomically.It is important
to admit that we have not completely eliminated busy waiting with this definition of the wait() and signal() operations. Rather,we
have moved busy waiting from the entry section to the critical sections of application programs.
Advantages
In semaphores there is no spinning, hence no waste of resources due to no busy waiting. That is because threads intending to access the
critical section are queued. And could access the priority section when the are de-queued, which is done by the semaphore
implementation itself, hence, unnecessary CPU time is not spent on checking if a condition is satisfied to allow the thread to access the
critical section.
Semaphores permit more than one thread to access the critical section, in contrast to alternative solution of synchronization like
monitors, which follow the mutual exclusion principle strictly. Hence, semaphores allow flexible resource management.
Finally, semaphores are machine independent, as they are implemented in the machine independent code of the microkernel services.
Disadvantages
Problem 1: Programming using Semaphores makes life harder as utmost care must be taken to ensure Ps and Vs are inserted
correspondingly and in the correct order so that mutual exclusion and deadlocks are prevented. In addition, it is difficult to produce a
structured layout for a program as the Ps and Vs are scattered all over the place. So the modularity is lost. Semaphores are quite
impractical when it comes to large scale use.
Problem 2: Semaphores involve a queue in its implementation. For a FIFO queue, there is a high probability for a priority inversion to
take place wherein a high priority process which came a bit later might just have to wait when a low priority one is in the critical section.
For example, consider a case when a new smoker joins and is desperate to smoke. What if the agent who handles the distribution of the
ingredients follows a FIFO queue (wherein the desperate smoker is last according to FIFO) and chooses the ingredients apt for another
smoker who would rather wait some more time for a next puff?
The Critical-Section Problem
Consider a system consisting of n processes{P0,P1, ...,Pn−1}. Each process has a segment of code, called a critical section,in which the
process may be 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 in its critical section, no other process is allowed to execute in its critical section. That is, no two
processes are executing in their critical sections at the same 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 critical section. The section of code implementing this
request is the entry section. The critical section may be followed by an exit
section.
Solution to critical section problem
A solution to the critical-section problem must satisfy the following three
requirements:
1. Mutual exclusion:-If process Pi is executing in its critical section, then no
other processes can be executing in their critical sections.
2. Progress:- If no process is executing in its critical section and some
processes wish to enter their critical sections, then only those processes
that are not executing in their remainder sections can participate in
deciding which will enter its critical section next, and this selection can not
be postponed indefinitely.
3. Bounded waiting:- There exists a bound, or limit, on the number of times
that other processes are allowed to enter their critical sections after a
process has made a request to enter its critical section and before that
request is granted.
We assume that each process is executing at a nonzero speed. However, we can
make no assumption concerning the relative speed of then processes.
At a given point in time, many kernel-mode processes may be active in
the operating system. As a result, the code implementing an operating system
(kernel code) is subject to several possible race conditions. Consider as an
example a kernel data structure that maintains a list of all open files in the
system. This list must be modified when a new file is opened or closed (adding
the file to the list or removing it from the list). If two processes were to open files
simultaneously, the separate updates to this list could result in a race condition.
Other kernel data structures that are prone to possible race conditions include
structures for maintaining memory allocation, for maintaining process lists,
and for interrupt handling. It is up to kernel developers to ensure that the
operating system is free from such race conditions.
How to handle critical section problem ?
Two general approaches are used to handle critical sections in operating
systems: preemptive kernels and non preemptive kernels. A preemptive
kernel allows a process to be preempted while it is running in kernel mode. A
non preemptive kernel does not allow a process running in kernel mode to be
preempted; a kernel-mode process will run until it exits kernel mode, blocks,
or voluntarily yields control of the CPU.
Obviously, a non preemptive kernel is essentially free from race conditions
on kernel data structures, as only one process is active in the kernel at a time.
We cannot say the same about preemptive kernels, so they must be carefully
designed to ensure that shared kernel data are free from race conditions.
Preemptive kernels are especially difficult to design for SMP architectures,
since in these environments it is possible for two kernel-mode processes to run
simultaneously on different processors.
Why, then, would any one favor a preemptive kernel over a non preemptive
one? A preemptive kernel may be more responsive, since there is less risk that a
kernel-mode process will run for an arbitrarily long period before relinquishing
the processor to waiting processes. (Of course, this risk can also be minimized
by designing kernel code that does not behave in this way.) Furthermore, a
preemptive kernel is more suitable for real-time programming, as it will allow
a real-time process to preempt a process currently running in the kernel.
Logical Conditions to Implement Critical Region
For correct critical region implementation the following four conditions are required:
1. No two procesess may be simultaneously inside their critical regions.
2. No assumptions may be made about speeds or the number of CPUs.
3. No process running outside the critical region may block other processes.
4. No process should have to wait forever to enter its critical region.
Mechanisms Implementing Mutual Exclusion
Two approaches:
1. Mechanisms with busy waiting for accessing critical region:
(a) disabling interrupts,
(b) lock variables (incorrect ),
(c) strict alternation (incorrect ),
(d) Peterson’s solution,
(e) TSL instruction.
2. Mechanisms with suspension of the waiting process:
(a) sleep and wakeup (incorrect ),
(b) semaphores,
(c) monitors,
(d) message passing
Mechanisms with Busy Waiting
.Disabling interrupts each process entering critical region disables interrupts,
Advantage: process inside critical region may update shared resources without any risk of races,
Disadvantage: if after leaving the region interrupts are not reenabled there will be a crash of the system. Moreover: useless
in multiprocessor architectures,
.may be used inside operating system kernel when some system structures are to be updated, but is not recommended for
implementation of mutual exclusion in user space
do {
entry section
critical section
exit section
remainder section
} while (TRUE);
Example:-
#include <omp.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
/* sequential code */
int v = 0;
#pragma omp parallel shared(v)
{
CSCI 315 Operating Systems Design 12
{
#pragma omp critical (addv)
{
v ++;
}
printf("I am a parallel region\n");
}
/* sequential code */
printf("value of v = %d\n", v);
return 0;
}
How to check a solution
Informally, we should consider three important
cases:
1. One thread intends to enter its critical section,
and the other thread is not in its critical section or
in its entry section.
2. One thread intends to enter its critical section,
and the other thread is in its critical section.
3. Both threads intend to enter their critical
Sections
Readers-Writers Problem
A data object (such as a file or a record) is to be shared among several concurrent
processes. Some of these processes, called readers, may want only to read the content of the shared object where as others, called writers, may want
to update (that is to read and write) the shared object. Obviously, if two readers access the data simultaneously, no adverse effects will result.
However, if a writer and some other process (whether a writer or some readers) access the shared object simultaneously, chaos may ensue. To ensure
these difficulties do not arise, we require that the writers have exclusive access to the shared object. This synchronization problem is referred to the
readers-writers problem. Since it was originally stated, it has been used to test nearly every new synchronization primitive. The readers-writers
problem has several variations, all involving priorities. The simplest one, referred to as the first readers-writers problem, requires that no reader will
be kept waiting unless a writer has already obtained permission to use the shared object. In other words, no reader should wait for other readers to
finish simply because a writer is waiting. The second readers-writers problem requires that once a writer is ready, that writer performs its write as
soon as possible. In other words, if a writer is waiting to access the object, no new readers may start reading.
A solution to either problem may result in starvation. In the first case, writers may
starve; in the second case, readers may starve. For this reason, other variants of the
problem have been proposed. In the solution to the first readers-writers problem, processes share the following data structures.
semaphore mutex, wrt;
int readcount;
The semaphores mutex and wrt are initialized to 1; read count is initialized to 0. The
semaphore wrt is common to both the reader and writer processes. The mutex semaphore is used to ensure mutual exclusion when the reader
processes update the read count variable. The read count variable keeps track of how many processes are currently reading the object. The wrt
semaphore is used to ensure mutual exclusion for writers or a writer and readers. This semaphore is also used by the first and last readers to block
entry of a writer into its critical section and to allow open access to the wrt semaphore, respectively. It is not used by readers who enter or exit, while
at least one reader is in its critical sections.
The codes for reader and writer processes are shown below:
wait(mutex);
readcount++;
if(readcount == 1)
wait(wrt);
signal(mutex);
...
reading is performed
...
wait(mutex);
readcount--;
if(readcount == 0)
signal(wrt);
signal(mutex);
wait(wrt);
...
writing is performed
...
signal(wrt);
Note that, if a writer is in the critical section and n readers are waiting, then one reader is queued on wrt, and n-1 readers are 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 waiting writer; the selection is
made by the CPU scheduler.
•  Correctness criteria:
–  Each read or write of the shared data must happen within a critical section.
–  Guarantee mutual exclusion for writers.
–  Allow multiple readers to execute in the critical section at once.
The structure of a writer process
do
{
wait(rw_mutex); mutual exclusion for writers
...
/* writing is performed */
...
signal(rw_mutex);
}
The structure of a reader process
do
{
wait(mutex);
read_count++; critical section when read_count being modified
if (read_count == 1)
wait(rw_mutex); in case a writer is writing
signal(mutex);
...
/* reading is performed */
...
wait(mutex);
read count--; critical section when read_count being modified
if (read_count == 0)
signal(rw_mutex); release lock
signal(mutex); release lock
}
while (true);
Readers-Writers Problem Variations
– The first and simplest one, referred to as the first readers/writers problem, requires that no reader will be kept waiting unless a writer has
already obtained permission to use the shared object (i.e., no reader should wait for other readers to finish simply because a writer is waiting)
NOTE: writers may starve
– The second readers/writers problem requires that, once a writer is ready, that writer performs its write as soon as possible (i.e., if a writer is
waiting, no new readers may start reading) NOTE: readers may starve
– Both may have starvation leading to even more variations
Readers/Writers Solution
• Implementation notes:
1. The first reader blocks if there is a writer; any other readers who try to
enter block on mutex.
2. The last reader to exit signals a waiting writer.
3. When a writer exits, if there is both a reader and writer waiting, which
goes next depends on the scheduler.
4. If a writer exits and a reader goes next, then all readers that are waiting
will fall through (at least one is waiting on wrtand zero or more can be
waiting on mutex).
5. Does this solution guarantee all threads will make progress?
• Alternative desirable semantics:
– Let a writer enter its critical section as soon as possible.
The Bakery Algorithm problem
The bakery algorithm is due to Leslie Lamport and is based on a scheduling algorithm commonly used in bakeries, ice-cream stores, and other
locations where order must be made out of chaos. On entering the store, each customer receives a number. The customer with the lowest
number is served next. Before entering its critical section, process receives a ticket number. Holder of the smallest ticket number enters its
critical section. Unfortunately, the bakery algorithm can not guarantee that two processes (customers) will not receive the same number. In
the case of a tie, the process with the lowest ID is served first. If processes Pi and Pj receive the same number, if i < j, then Pi is served first; else
Pj is served first. The ticket numbering scheme always generates numbers in the increasing order of enumeration; i.e., 1, 2, 3, 4, 5 ...
Since process names are unique and totally ordered, our algorithm is completely deterministic. The common data structures are:
boolean choosing [n];
int number[n];
Initially these data structures are initializedto false and 0, respectively. The following notation is defined for convenience:
. (ticket #, process id #)
.(a,b) < (c,d) if a<c or if a= =c and b<d.
. max(a0, …an-1 ) is a number, k, such that k>= ai for i=0,…n-1
The structure of process Pi used in the bakery algorithm is as follows
do
{
choosing[i] = true;
number[i] = max(number[0],number[1],..number[n-1])+1;
choosing[i] = false;
for(j=0; j<n; j++) {
while(choosing[j]);
while((number[j]!=0) && ((number[j],j) < (number[i],i)));
}
Critical section
number[i]=0;
Remainder section
} while(1);
To prove that the bakery algorithm is correct, we need to first show that if Pi is in its critical section and Pk has already chosen its number k!=0,
then ((number [i],i) < (number[k],k)). Consider Pi in its critical section and Pk trying to enter its critical section. When process Pk executes the
second while statement for j= = i it finds that,
ƒ number[i] != 0
ƒ (number[i],i) < (number[k],k)
Thus it keeps looping in the while statement until Pi leaves the Pi critical section. Hence mutual exclusion is preserved. For progress and
bounded wait we observe that the processes enter their critical section on a first come first serve basis. Following is an example of how the
Bakery algorithm works. In the first table, we show that there are five processes, P0 through P4. P1’s number is 0 because it is not interested in
getting into its critical section at this time. All other processes are interested in entering their critical sections and have chosen non-zero
numbers by using the max() function in their entry sections.
Solution to mutual exclusion for nprocesses
•  assign numbers for c.s.; process with lowest number enters first
•  Pi and Pj with same numbers:
Pienters before Pjif i < j
•  ensure that: current numer >= previously assigned numbers
var choosing: array[0..n-1] of boolean;
number: array[0..n-1] of integer;
•  defined and proven by Leslie Lamport
•  prove on his homepage
•  lack of lower level mutual exclusion
–  each memory location written only by one process
–  reads shared
•  algorithm works even if read is overlapped by write!
–  independent of read value
•  was discovered during proof
◈Approach → The entering process checks all other processes sequentially, and waits for each one which has a lower number. Ties are
possible; these are resolved using process IDs.
◈Initialization:
typedef char boolean;
...
Shared boolean choosing[n]
Shared int num[n];
...
for (j=0; j < n; j++) {
num[j] = 0;
}
•  threads and bakery analogy
–  when thread wants to enter critical section it
has to make sure it has the smallest number.
•  however, with threads it may not be true that
only one thread gets the same number
–  e.g., if number operation is non-atomic
•  if more that one thread has the smallest number
then the thread with lowest id can enter
•  use pair (number, ID)
–  In this context (a,b) < (c,d) is equivalent to
–  (a<c) or ((a==c) and (b<d))
N-process Solution: Bakery Algorithm
• Before entering their CS, each Pi receives a number ().
• Holder of smallest number enter CS (like in bakeries,
ice-cream stores...)
• When Pi and Pj receives same number:
–if I < j then Pi is served first, else Pj is served first
• Pi resets its number to 0 in the exit section
• Notation:
–(a,b) < (c,d) if a < c or if a = c and b < d
–max(a0,...ak) is a number b such that
• b >= ai for i=0,..k
The Dining-Philosophers Problem
The Dining Philosophers Problem was proposed by Dijkstra in 1965, when dinosaurs ruled the earth [3]. It appears in a number of variations,
but the standard features are a table with five plates, five forks (or chopsticks) and a big bowl of spaghetti. Five philosophers, who represent
interacting threads, come to the table and execute the following loop:
Basic philosopher loop
1 while True :
2 think ()
3 get_forks ()
4 eat ()
5 put_forks ()
The forks represent resources that the threads have to hold exclusively in
order to make progress. The thing that makes the problem interesting, unrealistic, and unsanitary, is that the philosophers need two forks to
eat, so a hungry philosopher might have to wait for a neighbor to put down a fork.
Assume that the philosophers have a local variable i that identifies each
philosopher with a value in (0..4). Similarly, the forks are numbered from 0 to
4, so that Philosopher i has fork i on the right and fork i + 1 on the left. Here
is a diagram of the situation:
Assuming that the philosophers know how to think and eat, our job is to
write a version of get forks and put forks that satisfies the following constraints:
• Only one philosopher can hold a fork at a time.
• It must be impossible for a deadlock to occur.
• It must be impossible for a philosopher to starve waiting for a fork.
• It must be possible for more than one philosopher to eat at the same time
The last requirement is one way of saying that the solution should be efficient that is, it should allow the maximum amount of concurrency.
We make no assumption about how long eat and think take, except that
eat has to terminate eventually. Otherwise, the third constraint is impossible if a philosopher keeps one of the forks forever, nothing can
prevent the neighbors from starving.
To make it easy for philosophers to refer to their forks, we can use the
functions left and right:
Which fork?
1 def left ( i ): return i
2 def right (i ): return (i + 1) % 5
The % operator wraps around when it gets to 5, so (4 + 1) % 5 = 0.
Since we have to enforce exclusive access to the forks, it is natural to use a
list of Semaphores, one for each fork. Initially, all the forks are available.
Variables for dining philosophers
1 forks = [ Semaphore (1) for i in range (5)]
This notation for initializing a list might be unfamiliar to readers who don’t
use Python. The range function returns a list with 5 elements; for each element of this list, Python creates a Semaphore with initial value 1 and
assembles the result in a list named forks.Here is an initial attempt at get fork and put fork Dining philosophers non-solution
1 def get_forks (i ):
2 fork [ right ( i )]. wait ()
3 fork [ left (i )]. wait ()
4
5 def put_forks (i ):
6 fork [ right ( i )]. signal ()
7 fork [ left (i )]. signal ()
Dining Philosopher’s Problem
• Find an algorithm by which they can all eat
• Must avoid deadlocks
• Dijkstra
• Philosophers eat/think
• Eating needs two forks
• Pick one fork at a time
Dining Philosophers Solutions
• Allow only 4 philosophers to sit simultaneously
• Asymmetric solution
– Odd philosopher picks left fork followed by right
– Even philosopher does vice versa
• Pass a token
• Allow philosopher to pick fork only if both available
• Several possible remedies to the deadlock problem are replaced by:
• •Allow at most four philosophers to be sitting simultaneously at the table.
• •Allow a philosopher to pick up her chopsticks only if both chopsticks are
• available (to do this, she must pick them up in a critical section).
• •Use an asymmetric solution—that is, an odd-numbered philosopher picks

• up first her left chopstick and then her right chopstick, whereas an even numbered philosopher picks up her right chopstick and then her left
• chopstick.
do {
wait(chopstick[i]);
wait(chopstick[(i+1) % 5]);
...
/* eat for awhile */
...
signal(chopstick[i]);
signal(chopstick[(i+1) % 5]);
...
/* think for awhile */
...
} while (true)
Monitors
• are high-level language constructs that provide equivalent functionality to that of semaphores but are easier to control
• found in many concurrent programming languages
• Concurrent Pascal, Modula-3, uC++, Java...
• can be implemented by semaphores...
Monitor is a software module containing:
• one or more procedures
• an initialization sequence, and
• local data variables
Characteristics:
• local variables accessible only by monitor’s procedures
• a process enters the monitor by invoking one of it’s procedures
• only one process can be in the monitor at any one time
• The monitor ensures mutual exclusion: no need to program this constraint explicitly
• Hence, shared data are protected by placing them in the monitor
• The monitor locks the shared data on process entry
• Process synchronization is done by the programmer by using condition variables that represent conditions a process may need to wait for before
executing in the monitor
Condition variables:- Monitor is a software module containing:
• one or more procedures
• an initialization sequence, and
• local data variables
Characteristics:
• local variables accessible only by monitor’s procedures
• a process enters the monitor by invoking one of it’s procedures
• only one process can be in the monitor at any one time
• The monitor ensures mutual exclusion: no need to program this constraint explicitly
• Hence, shared data are protected by placing them in the monitor
• The monitor locks the shared data on process entry
• Process synchronization is done by the programmer by using condition variables that represent conditions a process may need to wait for before
executing in the monitor
Monitor mechanism, main features:
• • wait()/signal() function pair protects against loosing signals (what may happen with
• sleep()/wakeup()),
• • not all higher-level languages offer monitors (Euclid, Concurrent Pascal),
• • some languages offer incomplete mechanisms (Java and synchronized ),
• • solutions not dedicated for distributed environment because of required accessibility of shared memory.

kinds of monitors:
1.Hoare
2.Mesa
• Hoare monitors: signal(c) means
– run waiter immediately
- signaller blocks immediately
• condition guaranteed to hold when waiter runs
• but, signaller must restore monitor invariantsbefore signalling!
– cannot leave a mess for the waiter, who will run immediately!
• Mesa monitors: signal(c) means
– waiter is made ready, but the signaller continues
• waiter runs when signaller leaves monitor (or waits)
– signaller need not restore invariant until it leaves the monitor
– being woken up is only a hint that something has changed
• must recheck conditional case
• Hoare monitors
– if (not Ready)
•wait(c)
• Mesa monitors
– while(not Ready)
•wait(c)
• Mesa monitors easier to use
– more efficient
– fewer switches
– directly supports broadcast
• Hoare morefenitors leave less to chance
– when wake up, condition guaranteed to be what you expect
Runtime system calls for Mesa monitors
• EnterMonitor(m) {guarantee mutual exclusion}
•ExitMonitor(m) {hit the road, letting someone else run}
•Wait(c) {step out until condition satisfied}
• Signal(c){if someone’s waiting, give him a shot after I’m done}
– if queue c is occupied, move one thread from queue c to queue m
– return to caller

Monitor Usage
An abstract data type—or ADT—encapsulates data with a set of functions to operate on that data that are independent of any specific implementation of
the ADT.A monitor type is an ADT that includes a set of programmer defined operations that are provided with mutual exclusion within the monitor.The
monitor type also declares the variables whose values define the state of an instance of that type, along with the bodies of functions that operate on
those variables. The representation of a monitor type cannot be used directly by the various processes. Thus, a function defined within a monitor can
access only those variables declared locally with in the monitor and its formal parameters. Similarly, the local variables of a monitor can be accessed by
only the local functions.The monitor construct ensures that only one process at a time is active with in the monitor. However, the monitor construct, as
defined so far, is not sufficiently powerful for modeling some synchronization schemes. For this purpose, we need to define additional synchronization
mechanisms. These mechanisms are provided by the condition construct. A programmer who needs to write a tailor-made synchronization 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.wait();
means that the process invoking this operation is suspended until another process invokes x.signal();
Thex. signal() operation resumes exactly one suspended process. If no process is suspended, then the signal() operation has no effect; Contrast this
operation with the signal() operation associated with semaphores, which always affects the state of the semaphore.

Now suppose that, when thex .signal() operation is invoked by a process P, there exists a suspended process Q associated with conditionx. Clearly, if the
suspended process Q is allowed to resume its execution, the signaling process P must wait. Otherwise, both P and Q would be active simultaneously with
in the monitor. Note, however, that conceptually both processes can continue with their execution. Two possibilities exist:
1. Signal and wait. P either waits until Q leaves the monitor or waits for another condition.
2. Signal and continue. Q either waits until P leaves the monitor or waits for another condition
There are reasonable arguments in favor of adopting either option. On the one hand, since P was already executing in the monitor, the signal-and
continue method seems more reasonable. On the other, if we allow thread P to continue, then by the time Q is resumed, the logical condition for which Q
was waiting may no longer hold. A compromise between these two choices was adopted in the language Concurrent Pascal. When thread P executes the
signal operation, it immediately leaves the monitor. Hence,Q is immediately resumed.
Monitor Properties
„ Shared data can only be accessed by monitors procedures
„ Only oneprocess at a time can execute in the monitor (executing a
monitor procedure)
Shared data may contain condition variables
A monitor solution to the dining-philosopher problem
monitor DiningPhilosophers
{
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();
}
void putdown(int i) {
state[i] = THINKING;
test((i + 4) % 5);
test((i + 1) % 5);
}
void test(int i) {
if ((state[(i + 4) % 5] != EATING) &&
(state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING)){
state[i] = EATING;
self[i].signal();

}
}
initializationcode() {
for(inti=0;i<5;i++)

state[i] = THINKING;
}
}

Race Condition problem


Race condition – situation in which two or more processes perform some operation on shared resources and the final result of this
operation depends on the moment of realization of the operation.
–The outcome of the execution depends on the particular interleaving of instructions. since errors can be non-repeatable.

A Possible Problem: Race Condition


• Assume we had 5items in the buffer
• Then: – Assume producer has just produced a new item and put it into buffer and is about to increment the count.
– Assume the consumer has just retrieved an item from buffer and is about the decrement the count.
– Namely: Assume producer and consumer is now about to execute count++ and count– statements
Non atomic manipulation of count shared variable:
• count++ could be implemented as (atomic operations)
register1 = count
register1 = register1 + 1
count = register1
• count--could be implemented as (atomic operations)
register2 = count
register2 = register2 - 1
count = register2
 Race condition = concurrent access to variable + result depends on order
 Solution: synchronization; only one process at a time accesses data

The situation where two or more processes are reading or


writing some shared data & the final results depends on who runs precisely when are called race conditions.To see how interprocess communication
works in practice, let us consider a simple but common example, a print spooler. When a process wants to print a file, it enters the file name in a special
spooler directory. Another process, the printer daemon, periodically checks to see if there are any files to be printed, and if there are, it prints them and
removes their names from the directory. Imagine that our spooler directory has a large number of slots, numbered 0, 1, 2, ..., each one capable of holding
a file name. Also imagine that there are two shared variables,
out:which points to the next file to be printed
in:which points to the next free slot in the directory.
At a certain instant, slots 0 to 3 are empty (the files have already been printed) and slots 4 to 6 are full (with the names of files to be printed). More or less
simultaneously, processes A and B decide they want to queue a file for printing as shown in the fig.Process A reads in and stores the value, 7, in a local
variable called next_free_slot. Just then a clock interrupt occurs and the CPU decides that process A has run long enough, so it switches to process B.Process
B also reads in, and also gets a 7, so it stores the name of its file in slot 7 and updates in to be an 8. Then it goes off and does other things. Eventually, process
A runs again, starting from the place it left off last time. It looks at next_free_slot, finds a 7 there, and writes its file name in slot 7, erasing the name that
process B just put there. Then it computes next_free_slot + 1, which is 8, and sets into 8. The spooler directory is now internally consistent, so the printer
daemon will not notice anything wrong, but process B will never receive any output.

Techniques for avoiding Race Condition:


1. Disabling Interrupts

2. Lock Variables

3. Strict Alteration

4. Peterson's Solution

5. TSL instruction

1.Disabling Interrupts:

The simplest solution is to have each process disable all interrupts just after entering its critical region and re-enable them just before leaving it. With
interrupts disabled, no clock interrupts can occur.The CPU is only switched from process to process as a result of clock or other interrupts, after all, and with
interrupts turned off the CPU will not be switched to another process. Thus, once a process has disabled interrupts, it can examine and update the shared
memory without fear that any other process will intervene.
Disadvantages:
1. It is unattractive because it is unwise to give user processes the power to turn off interrupts. Suppose that one of them did, and then never turned them on
again?

2.Furthermore, if the system is a multiprocessor, with two or more CPUs, disabling interrupts affects only the CPU that executed the disable instruction. The
other ones will continue running and can access the shared memory.

Advantages:
it is frequently convenient for the kernel itself to disable interrupts for a few instructions while it is updating variables or lists. If an interrupt occurred while
the list of ready processes, for example, was in an inconsistent state, race conditions could occur

2.Lock Variables

– A single, shared, (lock) variable, initially 0.

– When a process wants to enter its critical region, it first tests the lock.

– If the lock is 0, the process sets it to 1 and enters the critical region. If the lock is already 1, the process just waits until it becomes 0. Thus, a 0 means that no
process is in its critical region, and a 1 means that some process is in its critical region.

Drawbacks:

Unfortunately, this idea contains exactly the same fatal flaw that we saw in the spooler directory.Suppose that one process reads the lock and sees that it is
0. Before it can set the lock to 1, another process is scheduled, runs, and sets the lock to 1. When the first process runs again, it will also set the lock to 1, and
two processes will be in their critical regions at the same time.

3.Strict Alteration:

while (TRUE){ while (TRUE) {

while(turn != 0) /* loop* /; while(turn != 1) /* loop* /;

critical_region(); critical_region();
turn = 1; turn = 0;

noncritical_region(); noncritical_region();

} }

(a) (b)

A proposed solution to the critical region problem. (a) Process 0. (b) Process 1. In both cases, be sure to note the semicolons terminating the while
statements.It keeps track of whose turn it is to enter the critical region and examine or update the shared memory.Initially, process 0 inspects turn, finds it to
be 0, and enters its critical region. Process 1 also finds it to be 0 and therefore sits in a tight loop continually testing turn to see when it becomes
1.Continuously testing a variable until some value appears is called busy waiting. It should usually be avoided, since it wastes CPU time. Only when there is a
reasonable expectation that the wait will be short is busy waiting used. A lock that uses busy waiting is called a spin lock. When process 0 leaves the critical
region, it sets turn to 1, to allow process 1 to enter its critical region. This way no two process can enters critical region simultaneously.

Drawbacks:

Taking turn is is not a good idea when one of the process is much slower than other. This situation requires that two processes strictly alternate in entering
their critical region.

Example:

● Process 0 finishes the critical region it sets turn to 1 to allow process 1 to enter critical region.

● Suppose that process 1 finishes its critical region quickly so both process are in their non

critical region with turn sets to 0.

● Process 0 executes its whole loop quickly, exiting its critical region & setting turn to 1. At this point turn is 1 and both processes are executing in their
noncritical regions.

● Suddenly, process 0 finishes its noncritical region and goes back to the top of its loop.Unfortunately, it is not permitted to enter its critical region now since
turn is 1 and process 1 is busy with its noncritical region.This situation violates the condition 3 set above: No process running outside the critical region may
block other process. In fact the solution requires that the two processes strictly alternate in entering their critical region.

4.Peterson's Solution:
#define FALSE 0

#define TRUE 1

#define N 2 /* number of processes */

int turn; /* whose turn is it? */

int interested[N]; /* all values initially 0 (FALSE)*/

void enter_region(int process) /* process is 0 or 1 */

int other; /* number of the other process */

other = 1 - process; /* the opposite of process */

interested[process] = TRUE; /* show that you are interested */

turn = process; /* set flag */

while (turn == process && interested[other] == TRUE) /* null statement */;

void leave_region(int process) /* process: who is leaving */

interested[process] = FALSE; /* indicate departure from critical region */

Peterson's solution for achieving mutual exclusion.

Initially neither process is in critical region. Now process 0 calls enter_region. It indicates its interest by setting its array element and sets turn to 0. Since
process 1 is not interested, enter_region returns immediately. If process 1 now calls enter_region, it will hang there until interested[0] goes to FALSE,an event
that only happens when process 0 calls leave_region to exit the critical region. Now consider the case that both processes call enter_region almost
simultaneously. Both will store their process number in turn. Whichever store is done last is the one that counts; the first one is lost. Suppose that process 1
stores last, so turn is 1. When both processes come to the while statement, process 0 executes it zero times and enters its critical region. Process 1 loops and
does not enter its critical region.

5. The TSL Instruction

TSL RX,LOCK

(Test and Set Lock) that works as follows: it reads the contents of the memory word LOCK into register RX and then stores a nonzero value at the memory
address LOCK. The operations of reading the word and storing into it are guaranteed to be indivisible no other processor can access the memory word until
the instruction is finished. The CPU executing the TSL instruction locks the memory bus to prohibit other CPUs from accessing memory until it is done.

enter_region:

TSL REGISTER,LOCK |copy LOCK to register and set LOCK to 1

CMP REGISTER,#0 |was LOCK zero?

JNE enter_region |if it was non zero, LOCK was set, so loop

RET |return to caller; critical region entered

leave_region:

MOVE LOCK, #0 |store a 0 in LOCK

RET |return to caller

Peterson-Algorithm problem
do{

flag[i] = true;

turn = j;
while (flag[j] && turn == j);

critical section

flag[i] = false;

remainder section

}while (true)

Peterson’s solution is restricted to two processes that alternate execution between their critical sections and remainder sections. The processes are
numbered P0 and P1. For convenience, when presenting Pi,we use Pj to denote the other process; that is, j equals 1−i.Peterson’s solution requires the two
processes to share two data items:

int turn;

boolean flag[2];

The variable turn indicates whose turn it is to enter its critical section. That is, if turn == i,then process Pi is allowed to execute in its critical section. The Flag
array is used to indicate if a process is ready to enter its critical section. For example, If flag [I] is true,this value indicates that Pi is ready to enter its critical
section. To enter the critical section, process Pi first set flag[i] to be true and then sets turn to the value j, there by asserting that if the other process wishes
to enter the critical section, it can do so. If both processes try to enter at the same time,turn will be set to both I and j at roughly the same time. Only one of
these assignments will last; the other will occur but will be overwritten immediately.The eventual value of turn determines which of the two processes is
allowedto enter its critical section first.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 Pi enters its critical section only if either flag[j]==false or turn==i. Also note that, if both processes can be executing in
their critical sections at the same time, then flag[0]==flag[1]==true. These two observations imply that P0 and P1 could not have successfully executed their
while statements at about the same time, since the value of turn can be either 0 or 1 but can not be both.Hence, one of the processes
—say,Pj must have successfully executed the while statement, where as Pi had to execute at least one additional statement (“turn==j”). However, at that time,
flag[j]==true and turn==j, and this condition will persist as long as Pj is in its critical section; as a result, mutual exclusion is preserved.To prove properties 2
and 3, we note that a process Pi can be prevented from entering the critical section only if it is stuck in the while loop with the condition flag[j]==true and
turn==j; this loop is the only one possible. If Pj is not ready to enter the critical section, then flag[j]==false, and Pi can enter its critical section. If Pj has set
flag[j] to true and is also executing in its while statement, then either turn==io return==j.Ifturn==i,thenPi will enter the critical section. Ifturn==j,then Pj will
enter the critical section. However, once Pj exits its critical section, it will reset flag [j] to false, allowing Pi to enter its critical section. If Pj resets flag [j ] to
true,It must also set turn to i.Thus, since Pi does not change the value of the variable turn while executing the while statement, Pi will enter the critical section
(progress) after at most one entry by Pj (bounded waiting).

Good algorithmic description of solving the problem

Two process solution

Assume that the load and store machine-language instructions are atomic; that is, cannot be interrupted

The two processes share two variables:

l int turn;

l Boolean flag[2]

l The variable turn indicates whose turn it is to enter the critical section

The flag array is used to indicate if a process is ready to enter the critical section. flag[i] = true implies that process Pi is ready!

Limitation to Peterson’s Solution


• Strict order of execution

• Variable updates (turn and flag) could still be problematic

Where Are the Sources of the Problem?


The root cause of the problem is that we are unable to control which part of the code can be executed in parallel, which part can only CSCI 315 Operating
Systems Design 6 p, p y be executed in sequence. For example, the instructions that update the value of a shared variable should only be allowed to execute in
sequence.

Peterson’s Solution (Cont.)


Provable that the three CS requirement are met:

1. Mutual exclusion is preserved

Pi enters CS only if:

either flag[j] = false or turn = i

2. Progress requirement is satisfied

3. Bounded-waiting requirement is met

The Deadlock Problem


A set of blocked processes each holding a resource and waiting to acquire a resource held by another process in the set. Here’s an example: System has 2
tape drives. P1 and P2 each hold one tape drive and each needs another one. Another deadlock situation can occur when the poor use of semaphores, We
reproduce that situation here. Assume that two processes, P0 and P1, need to access two semaphores, A and B, before executing their critical sections.
Semaphores are initialized to 1 each. The following code snippets show how a situation can arise where P0 holds semaphore A, P1 holds semaphore B, and
both wait for the other semaphore.

In the first solution for the dining philosophers problem, if all philosophers become hungry at the same time, they will pick up the chopsticks on their right
and wait for getting the chopsticks on their left. This causes a deadlock. Yet another example of a deadlock situation can occur on a one-way bridge, as shown
below. Traffic flows only in one direction, and each section of a bridge can be viewed as a resource. If a deadlock occurs, it can be resolved if one car backs up
(preempt resources and rollback). Several cars may have to be backed up if a deadlock occurs. Starvation is possible.

Deadlock Characterization
The following four conditions must hold simultaneously for a deadlock to occur:

1. Mutual exclusion: At least one resource must be held in a non-sharable mode that is only one process at a time can use the resource. If another process
requests that resource, the requesting process must be delayed until the resource has been released.

2. Hold and wait: A process must be holding at least one resource and waiting to acquire additional resources that are currently being held by other
processes.

3. No preemption: Resources cannot be preempted. That is, after using it a process releases a resource only voluntarily.

4. Circular wait: A set {P0, P1… Pn} of waiting processes must exist such that P0 is waiting for a resource that is held by P1, P1 is waiting for a resource that is
held by P2, and so on, Pn-1 is waiting for a resource held by Pn, and Pn is waiting for a resource held by P0.

Deadlocks can be described more precisely in terms of a directed graph called a system resource allocation graph. This graph consists of a set of vertices V
and a set of edges E. The set of vertices is portioned into two different types of nodes P={P0, P1… Pn}, the set of the active processes in the system, and
R={R0, R1… Rn}, the set consisting of all resource types in the system. A directed edge from a process Pi to resource type Rj signifies that process Pi requested
an instance of Rj and is waiting for that resource. A directed edge from Rj to Pi signifies that an instance of Rj has been allocated to Pi. We will use the
following symbols in a resource allocation graph.

Deadlock Handling
We can deal with deadlocks in a number of ways:

Ensure that the system will never enter a deadlock state.


Allow the system to enter a deadlock state and then recover from deadlock.

Ignore the problem and pretend that deadlocks never occur in the system.

These three ways result in the following general methods of handling deadlocks:

1. Deadlock prevention: is a set of methods for ensuring that at least one of the necessary conditions cannot hold. These methods prevent deadlocks by
constraining how processes can request for resources.

2. Deadlock Avoidance : This method of handling deadlocks requires that processes give advance additional information concerning which resources they will
request and use during their lifetimes. With this information, it may be decided whether a process should wait or not.

3. Allowing Deadlocks and Recovering: One method is to allow the system to enter a deadlocked state, detect it, and recover.

Deadlock Prevention
By ensuring that one of the four necessary conditions for a deadlock does not occur, we may prevent a deadlock.

Mutual exclusion
The mutual exclusion condition must hold for non-sharable resources, e.g., printer. Sharable resources do not require mutually exclusive access and thus
cannot be involved in a deadlock, e.g., read-only files. Also, resources whose states can be saved and restored can be shared, such as a CPU. In general, we
cannot prevent deadlocks by denying the mutual exclusion condition, as some resources are intrinsically non-sharable.

Hold and Wait


To ensure that the hold and wait condition does not occur in a system, we must guarantee that whenever a process requests a resource, it does not hold any
other resources. One protocol that can be used requires each process to request and be allocated all its resources before it begins execution. We can
implement this provision by requiring that system calls requesting resources for a process precede all other system calls. An alternative protocol requires a
process to request resources only when it has none. A process may request some resources and use them. But it must release these before requesting more
resources.

The two main disadvantages of these protocols are: firstly, resource utilization may below, since many resources may be allocated but unused for a
long time. Secondly starvation is possible. A process that needs several popular resources may have to wait indefinitely, because at least one of the resources
that it needs is always allocated to some other process.
No preemption
To ensure that this condition does not hold we may use the protocol: if a process is holding some resources and requests another that cannot be allocated
immediately to it, then all resources currently being held by the process are preempted. These resources are implicitly released, and added to the list of
resources for which the process is waiting. The process will be restarted when it gets all its old, as well as the newly requested resources.

Deadlock Avoidance
One method for avoiding deadlocks is to require additional information about how resources may be requested. Each request for resources by a process
requires that the system consider the resources currently available, the resources currently allocated to the process, and the future requests and releases of
each process, to decide whether thecurrent request can be satisfied or must wait to avoid a possible future deadlock. The simplest and most useful model
requires that each process declare the maximum number of resources of each type that it may need. Given a priori information about the maximum number
of resources of each type that may be requested by each process, it is possible to construct an algorithm that ensures that the system will never enter a
deadlocked state. A deadlock avoidance algorithm dynamically examines the resource-allocation state to ensure that a circular wait condition can never exist.

Starvation
In the previous solution, is there any danger of deadlock? In order for a deadlock to occur, it must be possible for a thread to wait on a semaphore while
holding another, and there by prevent itself from being signaled.In this example, deadlock is not possible, but there is a related problem that is almost as
bad: it is possible for a writer to starve.If a writer arrives while there are readers in the critical section, it might wait in queue forever while readers come and
go. As long as a new reader arrives before the last of the current readers departs, there will always be at least one reader in the room.This situation is not a
deadlock, because some threads are making progress,but it is not exactly desirable. A program like this might work as long as the load on the system is low,
because then there are plenty of opportunities for the writers. But as the load increases the behavior of the system would deteriorate quickly (at least from
the point of view of writers).Puzzle: Extend this solution so that when a writer arrives, the existing readers can finish, but no additional readers may enter.

Starvation – indefinite blocking


A process may never be removed from the semaphore queue in which it is suspended

Summary
Monitors

  A high-level abstraction that provides a convenient and effective mechanism for process synchronization
  Only one process may be active within the monitor at a time

Banker's algorithm
The Banker's algorithmis a resource allocation & deadlock avoidance algorithm developed by Edsger Dijkstra that tests for safety by simulating the
allocation of pre-determined maximum possible amounts of all resources, and then makes a "safe-state" check to test for possible deadlock
conditions for all other pending activities, before deciding whether allocation should be allowed to continue.

The algorithm was developed in the design process for the THE operating system and originally described (in Dutch) in EWD108

. The name is by analogy with the way that bankers account for liquidity constraints.

Semaphore
•  mechanism for synchronization, which does not need busy waiting (provided by OS)

•  Semaphore S: Integer-Variable, which can be accessed only via two atomic functions: wait and signal

.  instead of busy waiting: blocking of waiting process in blocked queues

Dining Philosophers Problem


Several possibilities that remedy the deadlock situation discussed in the last lecture are listed. Each results in a good solution for the problem.

ƒ Allow at most four philosophers to be sitting simultaneously at the table.

ƒ Allow a philosopher to pick up her chopsticks only if both chopsticks are available (to do this she must pick them up in a critical section)

ƒ Use an asymmetric solution; that is, an odd philosopher picks up first her left chopstick, whereas an even philosopher picks up her right chopstick
and then her left chopstick.

Critical Section
A section of code or collection of operations in which only one process may be executing at a given time, which we want to make atomic.Atomic
operations are used to ensure that cooperating processes execute correctly.

Readers-Writers Problem
-File/Record is to be shared among several concurrent processes

-Many readers, Exclusively one writer at a tim

Peterson’sSolution
• Thetwo processes share two variables:

– int turn;

– Boolean flag[2]

• Thevariable turn indicates whoseturn it is to enter the

critical section.

• The flag array is used to indicateif a process is ready to enter the critical section. flag[i] =true implies that process Pi is ready!

Deadlock and Starvation


n Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes

n Let S and Q be two semaphores initialized to 1

P0 P1

wait(S); wait(Q);

wait(Q); wait(S);

... ...
signal(S); signal(Q);

signal(Q); signal(S);

n Starvation – indefinite blocking

l A process may never be removed from the semaphore queue in which it is suspended

n Priority Inversion – Scheduling problem when lower-priority process holds a lock needed by higher-priority process

l Solved via priority-inheritance protocol

Bakery Algorithm
•  Also called Lamport’s bakery algorithm

–  after Leslie Lamport

–  A New Solution of Dijkstra's Concurrent Programming Problem

Communications of the ACM 17, 8 (August 1974), 453-455.

•  This is a mutual exclusion algorithm to

prevent concurrent threads from entering

critical sections concurrently


References
1.wikipedia
2.Google
3.www.tutorialpoint.com
4.modern operating system
5.www.w3schools.com

You might also like