0% found this document useful (0 votes)
71 views40 pages

Os Unit 3

The document discusses process synchronization and solutions to the critical section problem in operating systems. It covers: 1. Process synchronization is needed to coordinate access to shared resources and prevent inconsistencies. Mechanisms like semaphores are used to maintain data consistency. 2. The critical section problem arises when processes need exclusive access to a shared resource. Solutions like Peterson's algorithm use flags and turns to ensure mutual exclusion, progress, and bounded waiting. 3. Hardware solutions using atomic instructions like test-and-set and compare-and-swap can also solve the critical section problem by locking access to shared variables.

Uploaded by

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

Os Unit 3

The document discusses process synchronization and solutions to the critical section problem in operating systems. It covers: 1. Process synchronization is needed to coordinate access to shared resources and prevent inconsistencies. Mechanisms like semaphores are used to maintain data consistency. 2. The critical section problem arises when processes need exclusive access to a shared resource. Solutions like Peterson's algorithm use flags and turns to ensure mutual exclusion, progress, and bounded waiting. 3. Hardware solutions using atomic instructions like test-and-set and compare-and-swap can also solve the critical section problem by locking access to shared variables.

Uploaded by

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

OPERATING SYSTEMS

(Common CSE(AI&ML) & CSE(DS))


UNIT-3

Syllabus: PROCESS COORDINATION:


Synchronization: The Critical- section problem, Peterson’s Solution, Synchronization Hardware,
semaphores, classic problems of synchronization, monitors, Synchronization examples, atomic
transactions. Case Study: Linux
Deadlocks: System model, deadlock characterization, Methods for Handling Deadlock, deadlock
prevention, detection and Avoidance, recovery from deadlock.

PROCESS COORDINATION

A cooperating process is one that can affect or be affected by other processes executing in the system.
Cooperating processes can either directly share a logical address space (that is, both code and data)
or be allowed to share data only through files or messages.

Process Synchronization:

Process Synchronization is the task of coordinating the execution of processes in a way that no two
processes can have access to the same shared data and resources. It is specially needed in a multi-
process system when multiple processes are running together, and more than one process try to gain
access to the same shared resource or data at the same time. This can lead to the inconsistency of
shared data. So the change made by one process not necessarily reflected when other processes
accessed the same shared data. To avoid this type of inconsistency of data, the processes need to be
synchronized with each other.

How Process Synchronization works?

Process A changing the data in a memory location while another process B is trying to read the data
from the same memory location. There is a high probability that data read by the second process
will be erroneous.

Concurrent access to shared data may result in data inconsistency


Maintaining data consistency requires mechanisms to ensure the orderly execution of
cooperating processes
Suppose that we wanted to provide a solution to the consumer-producer problem that fills all
the buffers. We can do so by having an integer count that keeps track of the number of full buffers.
Initially, count is set to 0. It is incremented by the producer after it produces a new buffer and
is decremented by the consumerafter it consumes a buffer.

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. The remaining code is the remainder section.
do {
entry section
critical section
exit section
remainder section
} while (true);

The general structure of a typical process Pi is shown above. The entry section and exit section are
enclosed in boxes to highlight these important segments of code.
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 cannot 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.
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.
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.

Peterson’s Solution

One of the Classic software-based solution to the critical-section problem known as Peterson’s
solution. Because of the way modern computer architectures perform basic machine-language
instructions, such as load and store, there are no guarantees that Peterson’s solution will work
correctly on such architectures.

The structure of process Pi in Peterson’s solution

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];

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.

The above shows the structure of process Pi in Peterson's solution.

• Suppose there are N processes (P1, P2, ... PN) and as at some point of time every process
requires to enter in the Critical Section
• A FLAG[] array of size N is maintained here which is by default false. Whenever a process
requires to enter in the critical section, it has to set its flag as true. Example: If Pi wants to
enter it will set FLAG[i]=TRUE.
• Another variable is called TURN and is used to indicate the process number that is currently
waiting to enter into the critical section.
• The process that enters into the critical section while exiting would change the TURN to
another number from the list of processes that are ready.
• Example: If the turn is 3 then P3 enters the Critical section and while exiting turn=4 and
therefore P4 breaks out of the wait loop.

Synchronization Hardware
We explore several more solutions to the critical-section problem using techniques ranging from
hardware to software-based APIs available to both kernel developers and application programmers.
All these solutions are based on the premise of locking —that is, protecting critical regions through
the use of locks.

The critical-section problem could be solved simply in a single-processor environment if we could


prevent interrupts from occurring while a shared variable was being modified. In this way, we could
be sure that the current sequence of instructions would be allowed to execute in order without
preemption. No other instructions would be run, so no unexpected modifications could be made to
the shared variable. This is often the approach taken by non-preemptive kernels.

The definition of the test and set() instruction

Mutual-exclusion implementation with test and set()

Unfortunately, this solution is not as feasible in a multiprocessor environment. Disabling interrupts


on a multiprocessor can be time consuming, since the message is passed to all the processors. This
message passing delays entry into each critical section, and system efficiency decreases. Also
consider the effect on a system’s clock if the clock is kept updated by interrupts.
Many modern computer systems therefore provide special hardware instructions that allow us either
to test and modify the content of a word or to swap the contents of two words atomically—that is,
as one uninterruptible unit. We can use these special instructions to solve the critical-section problem
in a relatively simple manner. we abstract the main concepts behind these types of instructions by
describing the test and set() and compare and swap() instructions.

The test and set() instruction can be defined above. The important characteristic of this instruction
is that it is executed atomically. Thus, if two test and set() instructions are executed simultaneously
(each on a different CPU), they will be executed sequentially in some arbitrary order. If the machine
supports the test and set() instruction, then we can implement mutual exclusion by declaring a
boolean variable lock, initialized to false.

The compare and swap() instruction, in contrast to the test and set() instruction, operates on three
operands; The operand value is set to new value only if the expression (*value == exected) is true.
Regardless, compare and swap() always returns the original value of the variable value. Like the test
and set() instruction, compare and swap() is executed atomically.

Mutual-exclusion implementation with the compare and swap() instruction.


Mutual exclusion can be provided as follows: a global variable (lock) is declared and is initialized
to 0. The first process that invokes compare and swap() will set lock to 1. It will then enter its critical
section, because the original value of lock was equal to the expected value of 0. Subsequent calls to
compare and swap() will not succeed, because lock now is not equal to the expected value of 0.
When a process exits its critical section, it sets lock back to 0, which allows another process to enter
its critical section. The structure of process Pi is shown in above.
Although these algorithms satisfy the mutual-exclusion requirement, they do not satisfy the
bounded-waiting requirement. we present another algorithm using the test and set() instruction that
satisfies all the critical-section requirements. The common data structures are
do {
waiting[i] = true;
key = true;
while (waiting[i] && key)
key = test and set(&lock);
waiting[i] = false;
/* critical section */
j = (i + 1) % n;
while ((j != i) && !waiting[j])
j = (j + 1) % n;
if (j == i)
lock = false;
else
waiting[j] = false;
/* remainder section */
} while (true);
boolean waiting[n];
boolean lock

Bounded-waiting mutual exclusion with test and set().

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 a semaphore. So it is basically a synchronizing tool and is accessed
only through two low standard atomic operations, wait and signal designated
by P(S) and V(S) respectively.

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 classical definitions of wait and signal are:

Wait: This operation decrements the value of its argument S, as soon as it would become non-
negative(greater than or equal to 1). This Operation mainly helps you to control the entry of a task
into the critical section. In the case of the negative or zero value, no operation is
executed. wait() operation was originally termed as P; so it is also known as P(S) operation. The
definition of wait operation is as follows.

wait(S)

{
while (S <= 0) ; // busy wait
S--;
}
When one process modifies the value of a semaphore then, no other process can simultaneously
modify that same semaphore's value. In the above case the integer value of S(S<=0) as well as the
possible modification that is S-- must be executed without any interruption.

Signal: Increments the value of its argument S, as there is no more process blocked on the queue.
This Operation is mainly used to control the exit of a task from the critical section. signal() operation
was originally termed as V; so it is also known as V(S) operation.:

The definition of signal() is as follows:


signal(S) {
S++;
}

Properties of Semaphores
1. It's simple and always have a non-negative integer value.
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 in Operating system:

Binary Semaphore:

It is a special form of semaphore used for implementing mutual exclusion, hence it is often called
a Mutex. A binary semaphore is initialized to 1 and only takes the values 0 and 1 during the
execution of a program. In Binary Semaphore, the wait operation works only if the value of
semaphore = 1, and the signal operation succeeds when the semaphore= 0. Binary Semaphores are
easier to implement than counting semaphores.

Counting Semaphores:

These are used to implement bounded concurrency. The Counting semaphores can range over
an unrestricted domain. These can be used to control access to a given resource that consists of a
finite number of Instances. Here the semaphore count is used to indicate the number of available
resources. If the resources are added then the semaphore count automatically gets incremented and
if the resources are removed, the count is decremented. Counting Semaphore has no mutual
exclusion.

Advantages of Semaphores

• Benefits of using Semaphores are as given below:


• With the help of semaphores, there is a flexible management of resources.
• Semaphores are machine-independent and they should be run in the machine-independent
code of the microkernel.
• Semaphores do not allow multiple processes to enter in the critical section.
• They allow more than one thread to access the critical section.
• As semaphores follow the mutual exclusion principle strictly and these are much more
efficient than some other methods of synchronization.
• No wastage of resources in semaphores because of busy waiting in semaphores as
processor time is not wasted unnecessarily to check if any condition is fulfilled in order
to allow a process to access the critical section.
Disadvantages of Semaphores

• One of the biggest limitations is that semaphores may lead to priority inversion; where
low priority processes may access the critical section first and high priority processes may
access the critical section later.
• To avoid deadlocks in the semaphore, the Wait and Signal operations are required to be
executed in the correct order.
• Using semaphores at a large scale is impractical; as their use leads to loss of modularity
and this happens because the wait() and signal() operations prevent the creation of the
structured layout for the system.
• 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 detail in coming lessons.

Semaphore Usage:

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. Thus, binary semaphores can be used instead for providing mutual
exclusion. Counting semaphores can be used to control access to a given resource consisting of a
finite number of instances. The semaphore is initialized to the number of resources available. Each
process that wishes to use a resource performs a wait() operation on the semaphore (thereby
decrementing the count). When a process releases a resource, it performs a signal() operation
(incrementing the count). When the count for the semaphore goes to 0, all resources are being used.
After that, processes that wish to use a resource will block until the count becomes greater than 0.

Semaphore Implementation

• Must guarantee that no two processes can execute wait () and signal () on thesame semaphore
at the same time
• Thus, implementation becomes the critical section problem where the wait and signal code are
placed in the critical section.
Could now have busy waiting in critical section implementation
But implementation code is short

Little busy waiting if critical section rarely occupied

Note that applications may spend lots of time in critical sections and thereforethis is not a good
solution.
• With each semaphore there is an associated waiting queue. Each entry in awaiting queue has
two data items:
value (of type integer)
pointer to next record in the list.
Two operations:
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 a wakeup() operation, which changes the
process from the waiting state to the ready state.

Block: place the process invoking the operation on the appropriate waiting queue.
Wakeup – remove one of processes in the waiting queue and place it inthe ready queue

To implement semaphores under this definition, we define a semaphore as follows:


typedef struct {
int value;
struct process *list;
} semaphore;
Each semaphore has an integer value and a list of processes list. 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, the wait() semaphore operation can be
defined as
wait(semaphore *S)
{
S->value--;
if (S->value < 0)
{
add this process to S->list; block();
}
}

Note that in this implementation, semaphore values may be negative, whereas semaphore values
are never negative under the classical definition of semaphores 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.

Classical Problems of Synchronization

Semaphore can be used in other synchronization problems besides Mutual Exclusion. Below are
some of the classical problems depicting flaws of process synchronization in systems where
cooperating processes are present.

➢ Bounded-Buffer Problem( producer-Consumer Problem)

➢ Readers and Writers Problem

➢ Dining-Philosophers Problem
Bounded-Buffer Problem
The pool consists of n buffers, each capable of holding one item. The mutex semaphore provides
mutual exclusion for accesses to the buffer pool and is initialized to the value 1. The empty and full
semaphores count the number of empty and full buffers. The semaphore empty is initialized to the
value n; the semaphore full is initialized to the value 0.

What is the Problem Statement?

There is a buffer of n slots and each slot is capable of storing one unit of data. There are two
processes running, namely, producer and consumer, which are operating on the buffer.

Bounded Buffer 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.

Here's a 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.

The Producer Operation

The pseudocode of the producer function looks like this:


do
{
// wait until empty > 0 and then decrement 'empty'
wait(empty);
// acquire lock
wait(mutex);
/* perform the insert operation in a slot */
// release lock
signal(mutex);
// increment 'full'
signal(full);
}
while(TRUE)

• Looking at the above code for a producer, we can see that a producer first waits until there is at
least 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.

The Consumer Operation

The pseudo code for the consumer function looks like this:

do
{
// wait until full > 0 and then decrement 'full'
wait(full);
// acquire the lock
wait(mutex);
/* perform the remove operation in a slot */
// release the lock
signal(mutex);
// increment 'empty'
signal(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

The Readers –Writers Problem:

Suppose that a database is to be shared among several concurrent processes. Some of these
processes may want only to read the database, whereas others may want to update (that is, to read
and write) the database. We distinguish between these two types of processes by referring to the
former as readers and to the latter as writers. Obviously, if two readers access the shared data
simultaneously, no adverse effects will result. However, if a writer and some other process (either
a reader or a writer) access the database simultaneously, chaos may ensue.

The Problem Statement:

There is a shared resource which should be accessed by multiple processes. There are two types of
processes in this context. They are reader and writer. Any number of readers can read from the
shared resource simultaneously, but only one writer can write to the shared resource. When
a writer is writing data to the resource, no other process can access the resource. A writer cannot
write to the resource if there are non zero number of readers accessing the resource at that time.

The Solution:

From the above problem statement, it is evident that readers have higher priority than writer. If a
writer wants to write to the resource, it must wait until there are no readers currently accessing that
resource.

Here, we use one mutex m and a semaphore w. An integer variable read_count is used to maintain
the number of readers currently accessing the resource. The variable read_count is initialized to 0.
A value of 1 is given initially to m and w.

Instead of having the process to acquire lock on the shared resource, we use the mutex m to make
the process to acquire and release lock whenever it is updating the read_count variable.

The code for the writer process looks like this:

while(TRUE)
{
wait(w);

/* perform the write operation */

signal(w);
}
And, the code for the reader process looks like this:
while(TRUE)
{
//acquire lock
wait(m);
read_count++;
if(read_count == 1)
wait(w);
//release lock
signal(m);
/* perform the reading operation */

// acquire lock
wait(m);
read_count--;
if(read_count == 0)
signal(w);
// release lock
signal(m);
}

• As seen above in the code for the writer, the writer just waits on the w semaphore until it gets a
chance to write to the resource.
• After performing the write operation, it increments w so that the next writer can access the
resource.
• On the other hand, in the code for the reader, the lock is acquired whenever the read_count is
updated by a process.
• When a reader wants to access the resource, first it increments the read_count value, then
accesses the resource and then decrements the read_count value.
• The semaphore w is used by the first reader which enters the critical section and the last reader
which exits the critical section.
• The reason for this is, when the first readers enters the critical section, the writer is blocked from
the resource. Only new readers can access the resource now.
• Similarly, when the last reader exits the critical section, it signals the writer using
the w semaphore because there are zero readers now and a writer can have the chance to access
the resource.

Dining-Philosophers Problem:

Consider five philosophers who spend their lives thinking and eating. The philosophers share
a circular table surrounded by five chairs, each belonging to one philosopher. In the center of
the table is a bowl of rice, and the table is laid with five single chopsticks). When a
philosopher thinks, she does not interact with her colleagues. From time totime, a
philosopher gets hungry and tries to pick up the two chopsticks that are closestto her (the
chopsticks that are between her and her left and right neighbors). A philosopher may pick up
only one chopstick at a time. Obviously, she cannot pick up a chopstick that is already in
the hand of a neighbor. When a hungry philosopher has bothher chopsticks at the same time,
she eats without releasing the chopsticks. When she isfinished eating, she puts down both
chopsticks and starts thinking again.

What is the Problem Statement?

Consider there are five philosophers sitting around a circular dining table. The dining table has five
chopsticks and a bowl of rice in the middle as shown in the below figure.

At any instant, a philosopher is either eating or thinking. When a philosopher wants to eat, he uses
two chopsticks - one from their left and one from their right. When a philosopher wants to think, he
keeps down both chopsticks at their original place.

Here's the Solution

From the problem statement, it is clear that a philosopher can think for an indefinite amount of time.
But when a philosopher starts eating, he has to stop at some point of time. The philosopher is in an
endless cycle of thinking and eating.

An array of five semaphores, stick[5], for each of the five chopsticks.

The code for each philosopher looks like:


while(TRUE)
{
wait(stick[i]);
/*
mod is used because if i=5, next
chopstick is 1 (dining table is circular)
*/
wait(stick[(i+1) % 5]);

/* eat */
signal(stick[i]);

signal(stick[(i+1) % 5]);

/* think */
}

When a philosopher wants to eat the rice, he will wait for the chopstick at his left and picks up
that chopstick. Then he waits for the right chopstick to be available, and then picks it too. After
eating, he puts both the chopsticks down.

• But if all five philosophers are hungry simultaneously, and each of them pickup one
chopstick, then a deadlock situation occurs because they will be waiting for another
chopstick forever. The possible solutions for this are:
• A philosopher must be allowed to pick up the chopsticks only if both the left and right
chopsticks are available.
• Allow only four philosophers to sit at the table. That way, if all the four philosophers pick
up four chopsticks, there will be one chopstick left on the table. So, one philosopher can
start eating and eventually, two chopsticks will be available. In this way, deadlocks can
be avoided.

Monitors:
Monitors are a programming language component that aids in the regulation of shared data access.
The Monitor is a package that contains shared data structures, operations, and synchronization
between concurrent procedure calls. Therefore, a monitor is also known as a synchronization
tool. Java, C#, Visual Basic, Ada, and concurrent Euclid are among some of the languages that
allow the use of monitors. Processes operating outside the monitor can't access the monitor's internal
variables, but they can call the monitor's procedures.

For example, synchronization methods like the wait() and notify() constructs are available in the
Java programming language.

Syntax of monitor in OS:


Monitor monitorName{
variables_declaration;
condition_variables;

procedure p1{ ... };


procedure p2{ ... };
...
procedure pn{ ... };

{
initializing_code;
}

}
• 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
Schematic view of a Monitor:

Characteristics of Monitors in OS

• A monitor in os has the following characteristics:


• We can only run one program at a time inside the monitor.
• Monitors in an operating system are defined as a group of methods and fields that are
combined with a special type of package in the os.
• A program cannot access the monitor's internal variable if it is running outside the
monitor. Although, a program can call the monitor's functions.
• Monitors were created to make synchronization problems less complicated.
• Monitors provide a high level of synchronization between processes.

Components of Monitor in an operating system

The monitor is made up of four primary parts:

• Initialization: The code for initialization is included in the package, and we just need it
once when creating the monitors.
• Private Data: It is a feature of the monitor in an operating system to make the data private.
It holds all of the monitor's secret data, which includes private functions that may only be
utilized within the monitor. As a result, private fields and functions are not visible outside
of the monitor.
• Monitor Procedure: Procedures or functions that can be invoked from outside of the
monitor are known as monitor procedures.
• Monitor Entry Queue: Another important component of the monitor is the Monitor
Entry Queue. It contains all of the threads, which are commonly referred to as procedures
only.

Condition Variables

There are two sorts of operations we can perform on the monitor's condition variables:

• Wait
• Signal

Consider a condition variable (y) is declared in the monitor:

y.wait(): The activity/process that applies the wait operation on a condition variable will be
suspended, and the suspended process is located in the condition variable's block queue.

y.signal(): If an activity/process applies the signal action on the condition variable, then one of the
blocked activity/processes in the monitor is given a chance to execute.

Advantages of Monitor in OS

• Monitors offer the benefit of making concurrent or parallel programming easier and less
error-prone than semaphore-based solutions.
• It helps in process synchronization in the operating system.
• Monitors have built-in mutual exclusion.
• Monitors are easier to set up than semaphores.
• Monitors may be able to correct for the timing faults that semaphores cause.
Disadvantages of Monitor in OS

• Monitors must be implemented with the programming language.


• Monitor increases the compiler's workload.
• The monitor requires to understand what operating system features are available
for controlling crucial sections in the parallel procedures.

Monitor Implementation Using Semaphores

Variables
semaphore mutex; // (initially = 1) semaphore
next; // (initially = 0)int next-count = 0;
Each procedure F will be replaced by
wait(mutex);

body of F;

if (next_count > 0)
signal(next)
else
signal(mutex);
Mutual exclusion within a monitor is ensured.
Monitor Implementation
For each condition variable x, we have:
semaphore x_sem; // (initially = 0)int x-count = 0;
The operation x.wait can be implemented as:

x-count++;
if (next_count > 0)
signal(next);
else
signal(mutex);
wait(x_sem);x-count--;
The operation x.signal can be implemented as:

if (x-count > 0)
{

next_count++; signal(x_sem);
wait(next);
next_count--;
}

Synchronization Examples
Synchronization mechanisms provided by the Windows, Linux, and Solaris operating systems, as
well as the Pthreads API. We have chosen these three operating systems because they provide good
examples of different approaches to synchronizing the kernel, and we have included the Pthreads
API because it is widely used for thread creation and synchronization by developers on UNIX and
Linux systems.
Synchronization in Windows :
The Windows operating system is a multithreaded kernel that provides support for real-time
applications and multiple processors. When the Windows kernel accesses a global resource on a
single-processor system, it temporarily masks interrupts for all interrupt handlers that may also
access the global resource. On a multiprocessor system, Windows protects access to global resources
using spinlocks, although the kernel uses spinlocks only to protect short code segments.
Furthermore, for reasons of efficiency, the kernel ensures that a thread will never be preempted while
holding a spinlock.
For thread synchronization outside the kernel, Windows provides dispatcher objects. Using a
dispatcher object, threads synchronize according to several different mechanisms, including mutex
locks, semaphores, events, and timers. The system protects shared data by requiring a thread to gain
ownership. Events are similar to condition variables; that is, they may notify a waiting thread when
a desired condition occurs. Finally, timers are used to notify one (or more than one) thread that a
specified amount of time has expired.
Dispatcher objects may be in either a signaled state or a non-signaled state. An object in a signaled
state is available, and a thread will not block when acquiring the object. An object in a non-signaled
state is not available, and a thread will block when attempting to acquire the object.

Synchronization in Linux:
Linux was a non-preemptive kernel, meaning that a process running in kernel mode could not be
preempted—even if a higher-priority process became available to run. Now, however, the Linux
kernel is fully preemptive, so a task can be preempted when it is running in the kernel. Linux
provides several different mechanisms for synchronization in the kernel.
Linux also provides spinlocks and semaphores (as well as reader–writer versions of these two locks)
for locking in the kernel. On SMP machines, the fundamental locking mechanism is a spinlock, and
the kernel is designed so that the spinlock is held only for short durations. On single-processor
machines, such as embedded systems with only a single processing core, spinlocks are inappropriate
for use and are replaced by enabling and disabling kernel preemption. That is, on single-processor
systems, rather than holding a spinlock, the kernel disables kernel preemption; and rather than
releasing the spinlock, it enables kernel preemption.
Atomic transactions:
The simplest synchronization technique within the Linux kernel is an atomic integer, which is
represented using the opaque data type atomic t. As the name implies, all math operations using
atomic integers are performed without interruption. The following code illustrates declaring an
atomic integer counter and then performing various atomic operations:
atomic t counter;
int value;
atomic set(&counter,5); /* counter = 5 */
atomic add(10, &counter); /* counter = counter + 10 */
atomic sub(4, &counter); /* counter = counter - 4 */
atomic inc(&counter); /*
counter = counter + 1 */ value = atomic read(&counter); /* value = 12 */
Atomic integers are particularly efficient in situations where an integer variable —such as a
counter—needs to be updated, since atomic operations do not require the overhead of locking
mechanisms. However, their usage is limited to these sorts of scenarios. In situations where there
are several variables contributing to a possible race condition, more sophisticated locking tools must
be used. Mutex locks are available in Linux for protecting critical sections within the kernel. Here,
a task must invoke the mutex lock() function prior to entering a critical section and the mutex
unlock() function after exiting the critical section. If the mutex lock is unavailable, a task calling
mutex lock() is put into a sleep state and is awakened when the lock’s owner invokes mutex unlock().
Deadlocks

In a multiprogramming environment, several processes may compete for a finite number of


resources. A process requests resources; if the resources are not available at that time, the process
enters a waiting state. Sometimes, a waiting process is never again able to change state, because the
resources it has requested are held by other waiting processes. This situation is called a Deadlock.
System Model:
A system consists of a finite number of resources to be distributed among a number of competing
processes. The resources may be partitioned into several types (or classes), each consisting of some
number of identical instances. CPU cycles, files, and I/O devices (such as printers and DVD drives)
are examples of resource types. If a system has two CPUs, then the resource type CPU has two
instances. Similarly, the resource type printer may have five instances. If a process requests an
instance of a resource type, the allocation of any instance of the type should satisfy the request. If it
does not, then the instances are not identical, and the resource type classes have not been defined
properly. For example, a system may have two printers. These two printers may be defined to be in
the same resource class if no one cares which printer prints which output. However, if one printer is
on the ninth floor and the other is in the basement, then people on the ninth floor may not see both
printers as equivalent, and separate resource classes may need to be defined for each printer.

Under the normal mode of operation, a process may utilize a resource in only the following sequence:
1. Request. The process requests the resource. If the request cannot be granted immediately (for
example, if the resource is being used by another process), then the requesting process must wait
until it can acquire the resource.
2. Use. The process can operate on the resource (for example, if the resource is a printer, the process
can print on the printer).
3. Release. The process releases the resource
Deadlock Characterization
In a deadlock, processes never finish executing, and system resources are tied up, preventing other
jobs from starting. Before we discuss the various methods for dealing with the deadlock problem,
we look more closely at features that characterize deadlocks.
Necessary Conditions
A deadlock situation can arise if the following four conditions hold simultaneously in a system:
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, a resource can be released only
voluntarily by the process holding it, after that process has completed its task.
4. Circular wait. A set {P0, P1, ..., Pn} of waiting processes must exist such that P0 is waiting for a
resource held by P1, P1 is waiting for a resource held by P2, ..., Pn−1 is waiting for a resource held
by Pn, and Pn is waiting for a resource held by P0.

Resource Allocation Graph

• A set of vertices V and a set of edges E.

• V is partitioned into two types:

P = {P1, P2, …, Pn}, the set consisting of all the processes in the system

R = {R1, R2, …, Rm}, the set consisting of all resource types in the system

• request edge – directed edge P1 Rj

• assignment edge – directed edge Rj Pi

• Process

• Resource Type with 4 instances

• Pi requests instance of Rj

• Pi is holding an instance of Rj
Fig: RAG Fig: RAG with a deadlock

• If graph contains no cycles no deadlock

• If graph contains a cycle

if only one instance per resource type, then deadlock

if several instances per resource type, possibility of deadlock

Methods for Handling Deadlocks


Generally speaking, we can deal with the deadlock problem in one of three ways:

• We can use a protocol to prevent or avoid deadlocks, ensuring that the system will never enter a
deadlocked state.

• We can allow the system to enter a deadlocked state, detect it, and recover.

• We can ignore the problem altogether and pretend that deadlocks never occur in the system. The
third solution is the one used by most operating systems, including Linux and Windows. It is then
up to the application developer to write programs that handle deadlocks.

To ensure that deadlocks never occur, the system can use either deadlock prevention or a deadlock-
avoidance scheme. Deadlock prevention provides a set of methods to ensure that at least one of the
necessary conditions cannot hold. Deadlock avoidance requires that the
Deadlock Prevention

If we simulate deadlock with a table which is standing on its four legs then we can also simulate four
legs with the four conditions which when occurs simultaneously, cause the deadlock.

However, if we break one of the legs of the table then the table will fall definitely. The same happens
with deadlock, if we can be able to violate one of the four necessary conditions and don't let them
occur together then we can prevent the deadlock.

Let's see how we can prevent each of the conditions.

1. Mutual Exclusion

Mutual section from the resource point of view is the fact that a resource can never be used by more
than one process simultaneously which is fair enough but that is the main reason behind the deadlock.
If a resource could have been used by more than one process at the same time then the process would
have never been waiting for any resource.

However, if we can be able to violate resources behaving in the mutually exclusive manner then the
deadlock can be prevented.

Spooling

For a device like printer, spooling can work. There is a memory associated with the printer which
stores jobs from each of the process into it. Later, Printer collects all the jobs and print each one of
them according to FCFS. By using this mechanism, the process doesn't have to wait for the printer
and it can continue whatever it was doing. Later, it collects the output when it is produced.

Although, Spooling can be an effective approach to violate mutual exclusion but it suffers from two
kinds of problems.

• This cannot be applied to every resource.


• After some point of time, there may arise a race condition between the processes to get
space in that spool.
We cannot force a resource to be used by more than one process at the same time since it will not be
fair enough and some serious problems may arise in the performance. Therefore, we cannot violate
mutual exclusion for a process practically.

2. Hold and Wait

Hold and wait condition lies when a process holds a resource and waiting for some other resource
to complete its task. Deadlock occurs because there can be more than one process which are holding
one resource and waiting for other in the cyclic order.

However, we have to find out some mechanism by which a process either doesn't hold any resource
or doesn't wait. That means, a process must be assigned all the necessary resources before the
execution starts. A process must not wait for any resource once the execution has been started.

!(Hold and wait) = !hold or !wait (negation of hold and wait is, either you don't hold or you
don't wait)

This can be implemented practically if a process declares all the resources initially. However, this
sounds very practical but can't be done in the computer system because a process can't determine
necessary resources initially.

Process is the set of instructions which are executed by the CPU. Each of the instruction may demand
multiple resources at the multiple times. The need cannot be fixed by the OS.

The problem with the approach is:

• Practically not possible.


• Possibility of getting starved will be increases due to the fact that some process may hold
a resource for a very long time.

3. No Preemption

Deadlock arises due to the fact that a process can't be stopped once it starts. However, if we take the
resource away from the process which is causing deadlock then we can prevent deadlock.

This is not a good approach at all since if we take a resource away which is being used by the process
then all the work which it has done till now can become inconsistent.

Consider a printer is being used by any process. If we take the printer away from that process and
assign it to some other process then all the data which has been printed can become inconsistent and
ineffective and also the fact that the process can't start printing again from where it has left which
causes performance inefficiency.

4. Circular Wait

To violate circular wait, we can assign a priority number to each of the resource. A process can't
request for a lesser priority resource. This ensures that not a single process can request a resource
which is being utilized by some other process and no cycle will be formed.
Among all the methods, violating Circular wait is the only approach that can be implemented
practically.

Deadlock avoidance

In deadlock avoidance, the request for any resource will be granted if the resulting state of the system
doesn't cause deadlock in the system. The state of the system will continuously be checked for safe
and unsafe states.

In order to avoid deadlocks, the process must tell OS, the maximum number of resources a process
can request to complete its execution.

The simplest and most useful approach states that the process should declare the maximum number
of resources of each type it may ever need. The Deadlock avoidance algorithm examines the resource
allocations so that there can never be a circular wait condition.

1. Banker's Algorithm

Banker algorithm used to avoid deadlock and allocate resources safely to each process in the
computer system. The 'S-State' examines all possible tests or activities before deciding whether the
allocation should be allowed to each process. It also helps the operating system to successfully
share the resources between all the processes. The banker's algorithm is named because it checks
whether a person should be sanctioned a loan amount or not to help the bank system safely simulate
allocation resources

Suppose the number of account holders in a particular bank is 'n', and the total money in a bank is
'T'. If an account holder applies for a loan; first, the bank subtracts the loan amount from full cash
and then estimates the cash difference is greater than T to approve the loan amount. These steps are
taken because if another person applies for a loan or withdraws some amount from the bank, it helps
the bank manage and operate all things without any restriction in the functionality of the banking
system.

Based on these criteria, the operating system decides which process sequence should be executed
or waited so that no deadlock occurs in a system. Therefore, it is also known as deadlock avoidance
algorithm or deadlock detection in the operating system.

Advantages

Following are the essential characteristics of the Banker's algorithm:

1. It contains various resources that meet the requirements of each process.


2. Each process should provide information to the operating system for upcoming resource
requests, the number of resources, and how long the resources will be held.
3. It helps the operating system manage and control process requests for each type of
resource in the computer system.
4. The algorithm has a Max resource attribute that represents indicates each process can hold
the maximum number of resources in a system.

Disadvantages
1. It requires a fixed number of processes, and no additional processes can be started in the
system while executing the process.
2. The algorithm does no longer allows the processes to exchange its maximum needs while
processing its tasks.
3. Each process has to know and state their maximum resource requirement in advance for
the system.
4. The number of resource requests can be granted in a finite time, but the time limit for
allocating the resources is one year.

When working with a banker's algorithm, it requests to know about three things:

1. How much each process can request for each resource in the system. It is denoted by the
[MAX] request.
2. How much each process is currently holding each resource in a system. It is denoted by
the [ALLOCATED] resource.
3. It represents the number of each resource currently available in the system. It is denoted
by the [AVAILABLE] resource.

Following are the important data structures terms applied in the banker's algorithm as follows:

Suppose n is the number of processes, and m is the number of each type of resource used in a
computer system.

1. Available: It is an array of length 'm' that defines each type of resource available in the
system. When Available[j] = K, means that 'K' instances of Resources type R[j] are
available in the system.
2. Max: It is a [n x m] matrix that indicates each process P[i] can store the maximum number
of resources R[j] (each type) in a system.
3. Allocation: It is a matrix of m x n orders that indicates the type of resources currently
allocated to each process in the system. When Allocation [i, j] = K, it means that process
P[i] is currently allocated K instances of Resources type R[j] in the system.
4. Need: It is an M x N matrix sequence representing the number of remaining resources for
each process. When the Need[i] [j] = k, then process P[i] may require K more instances
of resources type Rj to complete the assigned work.
Nedd[i][j] = Max[i][j] - Allocation[i][j].
5. Finish: It is the vector of the order m. It includes a Boolean value (true/false) indicating
whether the process has been allocated to the requested resources, and all resources have
been released after finishing its task.

The Banker's Algorithm is the combination of the safety algorithm and the resource request
algorithm to control the processes and avoid deadlock in a system:

2. Safety Algorithm

It is a safety algorithm used to check whether or not a system is in a safe state or follows the safe
sequence in a banker's algorithm:

1. There are two vectors Wok and Finish of length m and n in a safety algorithm.

Initialize: Work = Available


Finish[i] = false; for I = 0, 1, 2, 3, 4… n - 1.

2. Check the availability status for each type of resources [i], such as:

Need[i] <= Work


Finish[i] == false
If the i does not exist, go to step 4.

3. Work = Work +Allocation(i) // to get new resource allocation

Finish[i] = true

Go to step 2 to check the status of resource availability for the next process.

4. If Finish[i] == true; it means that the system is safe for all processes.

3. Resource Request Algorithm

A resource request algorithm checks how a system will behave when a process makes each type
of resource request in a system as a request matrix.

Let create a resource request array R[i] for each process P[i]. If the Resource Request i [j] equal
to 'K', which means the process P[i] requires 'k' instances of Resources type R[j] in the system.
1. When the number of requested resources of each type is less than the Need resources, go to
step 2 and if the condition fails, which means that the process P[i] exceeds its maximum claim
for the resource. As the expression suggests:

If Request(i) <= Need


Go to step 2;

2. And when the number of requested resources of each type is less than the available resource
for each process, go to step (3). As the expression suggests:

If Request(i) <= Available


Else Process P[i] must wait for the resource since it is not available for use.

3. When the requested resource is allocated to the process by changing state:

Available = Available - Request


Allocation(i) = Allocation(i) + Request (i)
Needi = Needi - Requesti

When the resource allocation state is safe, its resources are allocated to the process P(i). And if
the new state is unsafe, the Process P (i) has to wait for each type of Request R(i) and restore the
old resource-allocation state.

Example: Consider a system that contains five processes P1, P2, P3, P4, P5 and the three resource
types A, B and C. Following are the resources types: A has 10, B has 5 and the resource type C
has 7 instances.

Process Allocation Max Available


A B C A B C A B C

P1 0 1 0 7 5 3 3 3 2

P2 2 0 0 3 2 2

P3 3 0 2 9 0 2

P4 2 1 1 2 2 2

P5 0 0 2 4 3 3

Answer the following questions using the banker's algorithm:

1. What is the reference of the need matrix?


2. Determine if the system is safe or not.
3. What will happen if the resource request (1, 0, 0) for process P1 can the system accept
this request immediately?

Ans. 2: Context of the need matrix is as follows:

Need [i] = Max [i] - Allocation [i]


Need for P1: (7, 5, 3) - (0, 1, 0) = 7, 4, 3
Need for P2: (3, 2, 2) - (2, 0, 0) = 1, 2, 2
Need for P3: (9, 0, 2) - (3, 0, 2) = 6, 0, 0
Need for P4: (2, 2, 2) - (2, 1, 1) = 0, 1, 1
Need for P5: (4, 3, 3) - (0, 0, 2) = 4, 3, 1

Process Need
A B C

P1 7 4 3

P2 1 2 2

P3 6 0 0

P4 0 1 1

P5 4 3 1

Hence, we created the context of need matrix.

Ans. 2: Apply the Banker's Algorithm:

Available Resources of A, B and C are 3, 3, and 2.

Now we check if each type of resource request is available for each process.

Step 1: For Process P1:

Need <= Available

7, 4, 3 <= 3, 3, 2 condition is false.

So, we examine another process, P2.

Step 2: For Process P2:


Need <= Available

1, 2, 2 <= 3, 3, 2 condition true

New available = available + Allocation

(3, 3, 2) + (2, 0, 0) => 5, 3, 2

Similarly, we examine another process P3.

Step 3: For Process P3:

P3 Need <= Available

6, 0, 0 < = 5, 3, 2 condition is false.

Similarly, we examine another process, P4.

Step 4: For Process P4:

P4 Need <= Available

0, 1, 1 <= 5, 3, 2 condition is true

New Available resource = Available + Allocation

5, 3, 2 + 2, 1, 1 => 7, 4, 3

Similarly, we examine another process P5.

Step 5: For Process P5:

P5 Need <= Available

4, 3, 1 <= 7, 4, 3 condition is true

New available resource = Available + Allocation

7, 4, 3 + 0, 0, 2 => 7, 4, 5

Now, we again examine each type of resource request for processes P1 and P3.

Step 6: For Process P1:

P1 Need <= Available

7, 4, 3 <= 7, 4, 5 condition is true


New Available Resource = Available + Allocation

7, 4, 5 + 0, 1, 0 => 7, 5, 5

So, we examine another process P2.

Step 7: For Process P3:

P3 Need <= Available

6, 0, 0 <= 7, 5, 5 condition is true

New Available Resource = Available + Allocation

7, 5, 5 + 3, 0, 2 => 10, 5, 7

Hence, we execute the banker's algorithm to find the safe state and the safe sequence like
P2, P4, P5, P1 and P3.

Ans. 3: For granting the Request (1, 0, 2), first we have to check that Request <= Available, that
is (1, 0, 2) <= (3, 3, 2), since the condition is true. So the process P1 gets the request immediately

Safe and Unsafe States

The resource allocation state of a system can be defined by the instances of available and allocated
resources, and the maximum instance of the resources demanded by the processes.

A state of a system recorded at some random time is shown below.


Resources Assigned:

Process Type1 Type2` Type3 Type4

A 3 0 2 2

B 0 0 1 1

C 1 1 1 0

D 2 1 4 0

Resources still needed

Process Type1 Type2` Type3 Type4

A 1 1 0 0

B 0 1 1 2

C 1 2 1 0

D 2 1 1 2

1. E = (7 6 8 4)
2. P = (6 2 8 3)
3. A = (1 4 0 1)

Above tables and vector E, P and A describes the resource allocation state of a system. There are 4
processes and 4 types of the resources in a system. Table 1 shows the instances of each resource
assigned to each process.

Table 2 shows the instances of the resources, each process still needs. Vector E is the representation
of total instances of each resource in the system.

Vector P represents the instances of resources that have been assigned to processes. Vector A
represents the number of resources that are not in use.

A state of the system is called safe if the system can allocate all the resources requested by all the
processes without entering into deadlock.
If the system cannot fulfill the request of all processes then the state of the system is called unsafe.

The key of Deadlock avoidance approach is when the request is made for resources then the request
must only be approved in the case if the resulting state is also a safe state.

Resource Allocation Graph:

The resource allocation graph is the pictorial representation of the state of a system. As its name
suggests, the resource allocation graph is the complete information about all the processes which
are holding some resources or waiting for some resources.

It also contains the information about all the instances of all the resources whether they are
available or being used by the processes.

In Resource allocation graph, the process is represented by a Circle while the Resource is
represented by a rectangle. Let's see the types of vertices and edges in detail.

Vertices are mainly of two types, Resource and process. Each of them will be represented by
a different shape. Circle represents process while rectangle represents resource.

A resource can have more than one instance. Each instance will be represented by a dot inside the
rectangle.
Edges in RAG are also of two types, one represents assignment and other represents the wait of
a process for a resource. The above image shows each of them.

A resource is shown as assigned to a process if the tail of the arrow is attached to an instance to
the resource and the head is attached to a process.

A process is shown as waiting for a resource if the tail of an arrow is attached to the process while
the head is pointing towards the resource.

Example

Let's consider 3 processes P1, P2 and P3, and two types of resources R1 and R2. The resources
are having 1 instance each.

According to the graph, R1 is being used by P1, P2 is holding R2 and waiting for R1, P3 is waiting
for R1 as well as R2.

The graph is deadlock free since no cycle is being formed in the graph.
Deadlock Detection using RAG

If a cycle is being formed in a Resource allocation graph where all the resources have the single
instance then the system is deadlocked.

In Case of Resource allocation graph with multi-instanced resource types, Cycle is a necessary
condition of deadlock but not the sufficient condition.

The following example contains three processes P1, P2, P3 and three resources R2, R2, R3. All
the resources are having single instances each.

If we analyze the graph then we can find out that there is a cycle formed in the graph since the
system is satisfying all the four conditions of deadlock.

Allocation Matrix

Allocation matrix can be formed by using the Resource allocation graph of a system. In Allocation
matrix, an entry will be made for each of the resource assigned. For Example, in the following
matrix, en entry is being made in front of P1 and below R3 since R3 is assigned to P1.

Process R1 R2` R3

P1 0 0 1

P2 1 0 0

P3 0 1 0

Request Matrix
In request matrix, an entry will be made for each of the resource requested. As in the following
example, P1 needs R1 therefore an entry is being made in front of P1 and below R1.

Process R1 R2` R3

P1 1 0 0

P2 0 1 0

P3 0 0 1

Avial = (0,0,0)

Neither we are having any resource available in the system nor a process going to release. Each
of the process needs at least single resource to complete therefore they will continuously be
holding each one of them.

We cannot fulfill the demand of at least one process using the available resources therefore the
system is deadlocked as determined earlier when we detected a cycle in the graph.

Deadlock Detection and Recovery

In this approach, The OS doesn't apply any mechanism to avoid or prevent the deadlocks.
Therefore the system considers that the deadlock will definitely occur. In order to get rid of
deadlocks, The OS periodically checks the system for any deadlock. In case, it finds any of the
deadlock then the OS will recover the system using some recovery techniques.

The main task of the OS is detecting the deadlocks. The OS can detect the deadlocks with the
help of Resource allocation graph.
In single instanced resource types, if a cycle is being formed in the system then there will
definitely be a deadlock. On the other hand, in multiple instanced resource type graph, detecting
a cycle is not just enough. We have to apply the safety algorithm on the system by converting the
resource allocation graph into the allocation matrix and request matrix.

In order to recover the system from deadlocks, either OS considers resources or processes.

For Resource
Preempt the resource

We can snatch one of the resources from the owner of the resource (process) and give it to the
other process with the expectation that it will complete the execution and will release this resource
sooner. Well, choosing a resource which will be snatched is going to be a bit difficult.

Rollback to a safe state

System passes through various states to get into the deadlock state. The operating system can
rollback the system to the previous safe state. For this purpose, OS needs to implement check
pointing at every state.

The moment, we get into deadlock, we will rollback all the allocations to get into the previous
safe state.

For Process

Kill a process

Killing a process can solve our problem but the bigger concern is to decide which process to kill.
Generally, Operating system kills a process which has done least amount of work until now.

Kill all process


This is not a suggestible approach but can be implemented if the problem becomes very serious.
Killing all process will lead to inefficiency in the system because all the processes will execute
again from starting.

Recovery from Deadlock


When a detection algorithm determines that a deadlock exists, several alternatives are available.
One possibility is to inform the operator that a deadlock has occurred and to let the operator deal
with the deadlock manually. Another possibility is to let the system recover from the deadlock
automatically. There are two options for breaking a deadlock. One is simply to abort one or more
processes to break the circular wait. The other is to preempt some resources from one or more of
the deadlocked processes

1 Process Termination To eliminate deadlocks by aborting a process, we use one of two methods.
In both methods, the system reclaims all resources allocated to the terminated processes.

• Abort all deadlocked processes. This method clearly will break the deadlock cycle, but at great
expense. The deadlocked processes may have computed for a long time, and the results of these
partial computations must be discarded and probably will have to be recomputed later.

• Abort one process at a time until the deadlock cycle is eliminated. This method incurs
considerable overhead, since after each process is aborted, a deadlock-detection algorithm must
be invoked to determine whether any processes are still deadlocked.

Aborting a process may not be easy. If the process was in the midst of updating a file, terminating
it will leave that file in an incorrect state. Similarly, if the process was in the midst of printing
data on a printer, the system must reset the printer to a correct state before printing the next job.

If the partial termination method is used, then we must determine which deadlocked process (or
processes) should be terminated. This determination is a policy decision, similar to CPU-
scheduling decisions. The question is basically an economic one; we should abort those processes
whose termination will incur the minimum cost. Unfortunately, the term minimum cost is not a
precise one. Many factors may affect which process is chosen, including:

1. What the priority of the process is

2. How long the process has computed and how much longer the process will compute before
completing its designated task

3. How many and what types of resources the process has used (for example, whether the
resources are simple to preempt)

4. How many more resources the process needs in order to complete

5. How many processes will need to be terminated

6. Whether the process is interactive or batch


2 Resource Preemption :To eliminate deadlocks using resource preemption, we successively
preempt some resources from processes and give these resources to other processes until the
deadlock cycle is broken. If preemption is required to deal with deadlocks, then three issues need
to be addressed:

1. Selecting a victim. Which resources and which processes are to be preempted? As in process
termination, we must determine the order of preemption to minimize cost. Cost factors may
include such parameters as the number of resources a deadlocked process is holding and the
amount of time the process has thus far consumed.

2. Rollback. If we preempt a resource from a process, what should be done with that process?
Clearly, it cannot continue with its normal execution; it is missing some needed resource. We
must roll back the process to some safe state and restart it from that state.

You might also like