OS-III UNIT (Inter Processes Communication)
OS-III UNIT (Inter Processes Communication)
Critical Section, Race Conditions, Mutual Exclusion, Hardware Solution, Strict Alternation, Peterson’s
Solution- The Producer Consumer Problem-Semaphores, Event Counters, Monitors, Message
Passing-Classical IPC Problems- Reader’s & Writer Problem, Dinning Philosopher Problem etc.
Deadlocks-Definition, Necessary and sufficient conditions for Deadlock- Deadlock Prevention, Deadlock
Avoidance-Banker’s algorithm-Deadlock detection and Recovery.
Independent process.
Co-operating process.
An independent process is not affected by the execution of other processes while a co-operating process
can be affected by other executing processes. Though one can think that those processes, which are
running independently, will execute very efficiently, in reality, there are many situations when co-
operative nature can be utilised for increasing computational speed, convenience and modularity. Inter
process communication (IPC) is a mechanism which allows processes to communicate with each other
and synchronize their actions. The communication between these processes can be seen as a method of
co-operation between them. Processes can communicate with each other through both:
1. Shared Memory
2. Message passing
The Figure 1 below shows a basic structure of communication between processes via the shared
memory method and via the message passing method.
An operating system can implement both method of communication. First, we will discuss the shared
memory methods of communication and then message passing. Communication between processes
using shared memory requires processes to share some variable and it completely depends on how
programmer will implement it. One way of communication using shared memory can be imagined like
this: Suppose process1 and process2 are executing simultaneously and they share some resources or use
some information from another process. Process1 generate information about certain computations or
resources being used and keeps it as a record in shared memory. When process2 needs to use the
shared information, it will check in the record stored in shared memory and take note of the information
generated by process1 and act accordingly. Processes can use shared memory for extracting information
as a record from another process as well as for delivering any specific information to other processes.
Let’s discuss an example of communication between processes using shared memory method.
#define buff_max 25
#define mod %
struct item{
item nextProduced;
while(1){
shared_buff[free_index] = nextProduced;
free_index = (free_index + 1) mod buff_max;
}
item nextConsumed;
while(1){
nextConsumed = shared_buff[full_index];
full_index = (full_index + 1) mod buff_max;
}
In the above code, the Producer will start producing again when the (free_index+1) mod buff max will be
free because if it it not free, this implies that there are still items that can be consumed by the Consumer
so there is no need to produce more. Similarly, if free index and full index point to the same index, this
implies that there are no items to consume.
Now, We will start our discussion of the communication between processes via message passing. In this
method, processes communicate with each other without using any kind of shared memory. If two
processes p1 and p2 want to communicate with each other, they proceed as follows:
Establish a communication link (if a link already exists, no need to establish it again.)
3. How many links can there be between every pair of communicating processes?
4. What is the capacity of a link? Is the size of a message that the link can accommodate fixed or
variable?
A link has some capacity that determines the number of messages that can reside in it temporarily for
which every link has a queue associated with it which can be of zero capacity, bounded capacity, or
unbounded capacity. In zero capacity, the sender waits until the receiver informs the sender that it has
received the message. In non-zero capacity cases, a process does not know whether a message has been
received or not after the send operation. For this, the sender must communicate with the receiver
explicitly. Implementation of the link depends on the situation, it can be either a direct communication
link or an in-directed communication link.
Direct Communication links are implemented when the processes uses a specific process identifier for
the communication, but it is hard to identify the sender ahead of time.
For example: the print server.
In-direct Communication is done via a shared mailbox (port), which consists of a queue of messages.
The sender keeps the message in mailbox and the receiver picks them up.
In Direct message passing, The process which want to communicate must explicitly name the recipient
or sender of communication.
e.g. send(p1, message) means send the message to p1.
similarly, receive(p2, message) means receive the message from p2.
In this method of communication, the communication link gets established automatically, which can be
either unidirectional or bidirectional, but one link can be used between one pair of the sender and
receiver and one pair of sender and receiver should not possess more than one pair of links. Symmetry
and asymmetry between sending and receiving can also be implemented i.e. either both process will
name each other for sending and receiving the messages or only the sender will name receiver for
sending the message and there is no need for receiver for naming the sender for receiving the message.
The problem with this method of communication is that if the name of one process changes, this
method will not work.
In Indirect message passing, processes use mailboxes (also referred to as ports) for sending and
receiving messages. Each mailbox has a unique id and processes can communicate only if they share a
mailbox. Link established only if processes share a common mailbox and a single link can be associated
with many processes. Each pair of processes can share several communication links and these links may
be unidirectional or bi-directional. Suppose two process want to communicate though Indirect message
passing, the required operations are: create a mail box, use this mail box for sending and receiving
messages, then destroy the mail box. The standard primitives used are: send(A, message) which means
send the message to mailbox A. The primitive for the receiving the message also works in the same way
e.g. received (A, message). There is a problem in this mailbox implementation. Suppose there are more
than two processes sharing the same mailbox and suppose the process p1 sends a message to the
mailbox, which process will be the receiver? This can be solved by either enforcing that only two
processes can share a single mailbox or enforcing that only one process is allowed to execute the receive
at a given time or select any process randomly and notify the sender about the receiver. A mailbox can
be made private to a single sender/receiver pair and can also be shared between multiple
sender/receiver pairs. Port is an implementation of such mailbox which can have multiple sender and
single receiver. It is used in client/server applications (in this case the server is the receiver). The port is
owned by the receiving process and created by OS on the request of the receiver process and can be
destroyed either on request of the same receiver process or when the receiver terminates itself.
Enforcing that only one process is allowed to execute the receive can be done using the concept of
mutual exclusion. Mutex mailbox is create which is shared by n process. Sender is non-blocking and
sends the message. The first process which executes the receive will enter in the critical section and all
other processes will be blocking and will wait.
Now, lets discuss the Producer-Consumer problem using message passing concept. The producer places
items (inside messages) in the mailbox and the consumer can consume an item when at least one
message present in the mailbox. The code is given below:
Producer Code
void Producer(void){
int item;
Message m;
while(1){
receive(Consumer, &m);
item = produce();
build_message(&m , item ) ;
send(Consumer, &m);
Consumer Code
void Consumer(void){
int item;
Message m;
while(1){
receive(Producer, &m);
item = extracted_item();
send(Producer, &m);
consume_item(item);
Process Synchronization
Process Synchronization means sharing system resources by processes in a such a way that, Concurrent
access to shared data is handled thereby minimizing the chance of inconsistent data. Maintaining data
consistency demands mechanisms to ensure synchronized execution of cooperating processes.
Process Synchronization was introduced to handle problems that arose while multiple process
executions. Some of the problems are discussed below.
A Critical Section is a code segment that accesses shared variables and has to be executed as an atomic
action. It means that in a group of cooperating processes, at a given point of time, only one process
must be executing its critical section. If any other process also wants to execute its critical section, it
must wait until the first one finishes.
Solution to Critical Section Problem
A solution to the critical section problem must satisfy the following three conditions:
1. Mutual Exclusion
Out of a group of cooperating processes, only one process can be in its critical section at a given point of
time.
2. Progress
If no process is in its critical section, and if one or more threads want to execute their critical section
then any one of these threads must be allowed to get into its critical section.
3. Bounded Waiting
After a process makes a request for getting into its critical section, there is a limit for how many other
processes can get into their critical section, before this process's request is granted. So after the limit is
reached, system must grant the process permission to get into its critical section.
Synchronization Hardware
Many systems provide hardware support for critical section code. The critical section problem could be
solved easily in a single-processor environment if we could disallow interrupts to occur while a shared
variable or resource is being modified.
In this manner, we could be sure that the current sequence of instructions would be allowed to execute
in order without pre-emption. Unfortunately, this solution is not feasible in a multiprocessor
environment.
Disabling interrupt on a multiprocessor environment can be time consuming as the message is passed to
all the processors.
This message transmission lag, delays entry of threads into critical section and the system efficiency
decreases.
Mutex Locks
As the synchronization hardware solution is not easy to implement for everyone, a strict software
approach called Mutex Locks was introduced. In this approach, in the entry section of code, a LOCK is
acquired over the critical resources modified and used inside critical section, and in the exit section that
LOCK is released.
As the resource is locked while a process executes its critical section hence no other process can access
it.
Intro
Ever since computers have had the ability to run multiple processes, there is a desire for the processes
to be able to communicate somehow. Communication and synchronization are closely related
challenges. Some of the most common and important problem types in computer applications involve
these challenges. Importantly, the same issues arise whether processes are sharing resources (which
usually must be done explicitly), or threads are sharing resources, which they normally do by default.
Inter-Process Communication
The term IPC stands for inter-process communication, but it refers not only to communication but
synchronization, as well. Some examples of processes needing to communicate include:
Basic information passing, as via signals and messages, does not ordinarily lead to problems, but
operating on shared resources and data certainly does.
Terms
o Critical Section: A section of code within a process that requires access to shared resources, and
must not be executed while another process is in a corresponding section of code.
o Mutual Exclusion: When one process is in a critical section, no other process may be in a critical
section that accesses any of the same shared resources.
o Race Condition: When the outcome of a series of operations depends not only on the data, but
also on the order of execution.
o Deadlock: Two or more processes are unable to proceed because each is waiting for one of the
others to do something.
o Livelock: Two or more processes continuously change their states in response to changes in the
other process(es), without doing any useful work.
o Starvation: A runnable process is overlooked indefinitely by the scheduler. The process is able to
proceed, but it is never chosen.
Race Conditions
Example 1. Code. Consider the following program. There is a main program and two threads. Each
thread appends a different string to the end of a main input string. There is a random number generated
that determines which thread runs first. Although this may seem artificial, it is a realistic artifact because
in reality, absent explicit ordering, there is no way for an application process to know the relative order
of execution of its threads, or be assured that the order will always be the same. In this case, the random
number generation simulates the non-deterministic nature of CPU scheduling by the OS.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#define MAXLENGTH 32
strcat(str,navystring);
return NULL;
strcat(str,armystring);
return NULL;
srand((unsigned int)time(NULL));
printf("r = %f\n",r);
if(r > .5) {
sleep(.01);
else {
sleep(.01);
sleep(.2);
printf("%s\n", hello);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
If we run the program repeatedly, the output changes, depending on the order of thread execution.
Therefore, a race condition exists in this example.
Example 2. Consider two example sections of code, called T1 and T2, which might be threads that
operate on the shared variables a and b.
// T1
a = a + 1;
b = b + 1;
// T2
b = b * 2;
a = a * 2;
If either T1 or T2 runs in isolation, the property a==b is preserved, as in the following examples:
a = a + 1;
b = b + 1;
b = b * 2;
a = a * 2; // a==b==4
b = b * 2;
a = a * 2;
a = a + 1;
b = b + 1; // a==b==3
However, suppose the code execution is somehow interleaved between T1 and T2. If initially a==1 and
b==1, the following interleaved execution could occur:
a = a + 1; // T1 (a==b==1)
b = b * 2; // T2
b = b + 1; // T1
a = a * 2; // T2 (a==4, b==3)
At the end of the interleaved execution, a==4 and b==3, so a==b is no longer true. We observe that
the value of the output depends on the order of execution, not just on the value of the input data. It
might be very rare that T1 and T2 ever execute in such a way, but when they do, the program no longer
executes the same. This example involves integer variables, but can be applied in principle to any shared
data or object.
Security Vulnerabilites. These examples are innocuous, but a race condition can also lead to a security
vulnerability. Suppose the following situation exists somewhere in an application:
# Elevate privileges
Situations like this can, and actually do, occur in real code. An attacker who identifies such a situation
may be able to exploit it to cause his own code to execute with elevated privileges.
Critical Sections
In the previous example, the problem could have ben prevented if we could guarantee that, when either
T1 or T2 runs, it maintains exclusive access to the shared objects (a and b). This is the idea behind the
concept of a critical section. When a process (or thread) is executing in its own critical section, no other
process (or thread) may be execute in its own same critical section. Critical sections are a mechanism by
which mutual exclusion can be enforced.
Mutual Exclusion
o No two processes may be simultaneously inside their critical sections, and a process remains
inside its critical section for a finite time
o A process must not be denied access to a critical resource when there is no other process using
it
o No assumptions are made about relative process speeds, or the number of processes or CPUs
In this section we briefly review a few hardware-based approaches to the problem of mutual exclusion.
Disabling Interrupts
This is possibly the simplest approach. Disable all interrupts before a process/thread enters its critical
section, then re-enable them after the process/thread exits its critical section. On the good side, this
approach does actually enforce mutual exclusion. The running process can't be interrupted in its critical
section, even by the OS!
while (true)
{
/* disable interrupts */
/* critical section */
/* enable interrupts */
/* remainder */
o An infinite loop or error in the critical section could hang or crash the whole OS.
Besides disabling interrupts, there are other hardware-based mutual exclusion strategies. In each case,
there are one or more hardware instructions that, when properly used, can implement mutual exclusion
correctly.
This approach is based on the use of a special hardware instruction provided by a given hardware
architecture, typically one that can perform multiple actions atomically (indivisibly). One example is an
"exchange" instruction, which swaps the values of two variables.
Inf the folowing example assembly code, the XCHG instruction is used to solve the critical section
problem. Note that REGISTER is like a local variable that can only be accessed by the executing process,
while LOCK is shared. Here, LOCK == 0 implies no process is in the critical section, while LOCK == 1
implies that the critical section is occupied.
enter_critical:
MOV REGISTER, 1
leave_critical:
int key = 1;
while (true) {
/* critical section */
lock = 0;
/* remainder */
As long as the lock is locked (i.e., the lock variable holds a 1), then the action of swapping has no effect --
it continually swaps a 1 with another 1. Only when the lock variable is 0 does the swap have an effect --
the lock is quickly reset to 1, and the calling function gets returned a 0 value, indicating it has
successfully gained access. The calling process knows it is the only one gaining access, because the
exchange is atomic. This is generally done by briefly locking the memory bus in hardware.
The Intel x86/x64 architecture implements a version of Exchange. It is described in the architectural
manual as follows:
Intel XCHG machine instruction for x86/x64. (source: Intel)
There are many other similar instructions. One mentioned in the Tanenbaum text, TSL (Test and Set
Lock), functions almost exactly the same as the Exchange example.
Hardware-based solutions can implement mutual exclusion correctly, but they rely on busy waiting.
They also fail if processes do not cooperate, and both deadlock and starvation are still possible.
There are two software-based approaches to mutual exclusion that involve busy waiting. While one
process is in its critical section, other processes actively wait, repeatedly checking for their own
opportunity to proceed.
In this approach, there is a shared 'lock' variable. If its value is 0, no process is in its critical region. If the
value is 1, a process is in its critical region and all others must wait. A process needing to enter its critical
section repeatedly checks the value, while the value is 1. As soon as the lock value is 0 again, the process
enters its critical section and sets the lock value to 1.
while (true) {
while (lock == 1)
lock = 1;
/* critical section */
lock = 0;
/* remainder */
The problem with this approach is that it relies on a race condition. If two processes read the lock value
as 0 and both proceed into their critical sections before the lock value can be set to 1, then mutual
exclusion is not enforced successfully.
Strict Alternation
The integer variable turn keeps track of whose turn it is to enter the critical section. Each process will
busy wait until its own turn.
while (true) {
while (turn != 0)
/* busy wait */ ;
/* critical section */
turn = 1;
/* remainder */
while (true) {
while (turn != 1)
/* busy wait */ ;
/* critical section */
turn = 0;
/* remainder */
}
The problem with this approach is that it requires strict alternation (neither process can execute two or
more times in a row). If one process is significantly faster, it ends up spending a lot of time blocked,
waiting on the slower process to finish. Also, it becomes complex to scale beyond two processes. The
approach provides mutual exclusion, but is very inefficient.
Peterson's Solution
In 1981, G.L. Peterson devised a software-based solution to mutual exclusion that does not require strict
alternation. There are a few variations of how to code Peterson's Solution; the one shown is from the
Tanenbaum text.
There are two function calls, one each for entering and leaving a process' criticial region. Each process
must call enter_region with its own process number as an argument. This will cuase it to wait, if
necessary, until it is safe to enter. After accessing the shared variables, it calls leave_region to exit the
critical region.
#define FALSE 0
#define TRUE 1
#define N 2
int other;
If both processes call enter_region simultaneously, they will each try to modify the variable turn. The
value of turn is overwritten by the second process, but by doing so it causes the second process to hang
on the while loop and busy wait, until the earlier processes finishes and leaves its criticial region.
Peterson's solution works correctly if implemented properly, allowing mutual exclusion by two
cooperating processes. It can also be scaled to work with more than two processes. However, Peterson's
solution employs busy waiting, an inefficient use of the CPU.
o Whenever processes or threads share resources, correct operation requires that mutual
exclusion be enforced.
o No two processes may be simultaneously inside their critical sections, and a process
remains inside its critical section for a finite time
o A process must not be denied access to a critical resource when there is no other
process using it
o No assumptions are made about relative process speeds or the number of processes or
CPUs
We have discussed a number of approaches to mutual exclusion and the challenges of each. The
following table is a summary:
Bounded buffer problem, which is also called producer consumer problem, is one of the classic
problems of synchronization. Let's start by understanding the problem here, before moving on to the
solution and program code.
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.
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:
empty, a counting semaphore whose initial value is the number of slots in the buffer, since,
initially all slots are empty.
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.
do
wait(empty);
// acquire lock
wait(mutex);
// 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
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.
do
wait(full);
wait(mutex);
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.
Finally, the empty semaphore is incremented by 1, because the consumer has just removed
data from an occupied slot, thus making it empty.
Producer-Consumer Problem in OS
The Producer-Consumer problem is a classical problem. The Producer-Consumer problem is used for
multi-process synchronization, which means synchronization between more than one processes.
In this problem, we have one producer and one consumer. The producer is the one who produces
something, and the consumer is the one who consumes something, produced by the producer. The
producer and consumer both share the common memory buffer, and the memory buffer is of fixed-size.
The task performed by the producer is to generate the data, and when the data gets generated, and
then it put the data into the buffer and again generates the data. The task performed by the consumer is
to consume the data which is present in the memory buffer.
1. At the same time, the producer and consumer cannot access the buffer.
2. The producer cannot produce the data if the memory buffer is full. It means when the memory
buffer is not full, then only the producer can produce the data.
3. The consumer can only consume the data if the memory buffer is not vacant. In a condition
where memory buffer is empty, the consumer is not allowed to take data from the memory
buffer.
There are three semaphore variables that we use to solve the problems that occur in the Producer-
Consumer problem.
1. Semaphore S
2. Semaphore E
3. Semaphore F
Semaphore S: – With the help of the Semaphore ‘S’ variable, we can achieve mutual exclusion among
the processes. By using the Semaphore variable ‘S,’ the producer or consumer can access and use the
shared buffer at a specific time. Initially, the value of the Semaphore variable ‘S’ is set to 1.
Semaphore E: – With the help of the Semaphore ‘E’ variable, we can define the vacant (empty) space in
the memory buffer. Initially, the value of the Semaphore ‘E’ variable is set to ‘n’ because initially, the
memory buffer is empty.
Semaphore F: – We use Semaphore ‘F’ variable to define the filled space, which is filled by the producer.
Firstly, we set the value of Semaphore variable ‘F’ to 0 because in starting, no space is filled by the
producer.
With the help of these Semaphore variables, we can solve the problems that occur in the Producer-
Consumer problem. We can also use two types of functions to solve this problem, and the functions are
wait() and signal().
1. wait(): – By using wait() function, we can decrease the value of the Semaphore variable by 1.
2. signal(): – By using signal() function, the value of the Semaphore variable is incremented by 1.
2. produce(): – We called the produce() function to tell producer to produce the data.
3. wait(E): – With the help wait() function, the value of the Semaphore variable ‘E’ can be
decremented by 1. If a producer produces something, then we have to decrease the value of the
Semaphore variable ‘E’ by 1.
4. wait(S): – The wait(S) function is used to set the value of the Semaphore variable ‘S’ to ‘0’, so
that other processes cannot enter into the critical section.
5. append(): – By using append() function, new data is added in the memory buffer.
6. signal(S): – We used the signal(S) function to set the value of the Semaphore variable ‘S’ to 1, so
that another process can enter into the critical section.
7. signal(F): – By using the signal(F) function, the value of the Semaphore variable ‘F’ is
incremented by one. In this, we increment the value by 1 because if we add the data into the
memory buffer, there is one space that is filled in the memory buffer. So, we have to update the
variable ‘F’.
1. while(): – By using while(), the data can be consumed again and again.
2. wait(F): – We used wait(F) function to decrease the value of the Semaphore variable ‘F’ by 1. It
is because if the consumer consumes some data, we have to reduce the value of the Semaphore
variable ‘F’ by 1.
3. wait(S): – The wait(S) function is used to set the value of the Semaphore variable ‘S’ to ‘0’, so
that other processes cannot enter into the critical section.
4. take():- We used take() function to take the data from the memory buffer by the consumer.
5. signal(S): – We used the signal(S) function to set the value of the Semaphore variable ‘S’ to 1, so
that other processes can enter into the critical section.
6. signal(E): – The signal(E) function is used to increment the value of the Semaphore variable ‘E’
by 1. It is because after taking data from the memory buffer, space is freed from the buffer, and
it is must to increase the value of the Semaphore variable ‘E’.
7. use(): – By using the use() function, we can use the data taken from the memory buffer, so that
we can perform some operations.
Semaphore is defined as an integer variable which is used to solve the problem of the critical section in
process synchronization. In semaphore, we use two types of atomic operations, and that operations
are wait and signal.
Wait: – In wait operation, the argument ‘S’ value is decrement by 1 if the value of the ‘S’ variable is
positive. If the value of the argument variable ‘S’ is zero or negative, no operation is performed.
Signal: – In Signal atomic operation, the value of the argument variable ‘S’ is incremented.
Characteristic of Semaphore
Types of Semaphores
1. Counting Semaphores
2. Binary Semaphores
2. Binary Semaphores: – Binary Semaphores are also called Mutex lock. There are two values of
binary semaphores, which are 0 and 1. The value of binary semaphore is initialized to 1. We use
binary semaphore to remove the problem of the critical section with numerous processes.
Advantages of Semaphore
1. In the Semaphore, only one process is allowed to enter into the critical section. In this, the
principle of mutual exclusion is to be followed strictly. And the semaphore mechanism is a more
efficient mechanism than other methods which we use in process synchronization.
3. With the help of the semaphore, the resources are managed flexibly.
4. In semaphore, there is a busy waiting, so there is no wastage of resources and process time.
5. In the semaphore, more threads are permitted to enter into the critical section.
Disadvantages of Semaphore
1. There is a priority inversion in the semaphore, and this is the biggest drawback of the
semaphore.
2. In the semaphore, due to program error violation of mutual exclusion, deadlock can be
happened.
4. For the purpose of large-scale use, the semaphore is not a practical method, and due to this,
there may be loss of modularity.
5. The Programming of the semaphore is tough, so there may be a possibility of not achieving
mutual exclusion.
6. In the semaphore, the OS has to preserve all calls to wait and signal semaphore.
Difference between Counting Semaphore and Binary Semaphore
In counting semaphore, there is no mutual exclusion. In binary semaphore, there is mutual exclusion.
In the counting semaphore, any integer value can be possible. It contains only two integer values that are 1 and 0.
In counting semaphore, there are more than one slots. In the binary semaphore, there is only one slot.
The counting process offers a set of processes. Binary semaphore contains mutual exclusion mecha
In semaphore, wait() and signal() operations are performed In mutex, locked or unlocked operation is
Operation
to modify the value of semaphore. performed.
Monitors are used for process synchronization. With the help of programming languages, we can use a
monitor to achieve mutual exclusion among the processes. Example of monitors: Java Synchronized
methods such as Java offers notify() and wait() constructs.
In other words, monitors are defined as the construct of programming language, which helps in
controlling shared data access.
The Monitor is a module or package which encapsulates shared data structure, procedures, and the
synchronization between the concurrent procedure invocations.
Characteristics of Monitors.
2. Monitors are the group of procedures, and condition variables that are merged together in a
special type of module.
3. If the process is running outside the monitor, then it cannot access the monitor’s internal variable.
But a process can call the procedures of the monitor.
6. There is only one process that can be active at a time inside the monitor.
Components of Monitor
1. Initialization
2. Private data
3. Monitor procedure
Initialization: – Initialization comprises the code, and when the monitors are created, we use this code
exactly once.
Private Data: – Private data is another component of the monitor. It comprises all the private data, and
the private data contains private procedures that can only be used within the monitor. So, outside the
monitor, private data is not visible.
Monitor Procedure: – Monitors Procedures are those procedures that can be called from outside the
monitor.
Monitor Entry Queue: – Monitor entry queue is another essential component of the monitor that
includes all the threads, which are called procedures.
Syntax of monitor
Condition Variables
There are two types of operations that we can perform on the condition variables of the monitor:
1. Wait
2. Signal
Wait Operation
a.wait(): – The process that performs wait operation on the condition variables are suspended and
locate the suspended process in a block queue of that condition variable.
Signal Operation
a.signal() : – If a signal operation is performed by the process on the condition variable, then a chance is
provided to one of the blocked processes.
Advantages of Monitor
It makes the parallel programming easy, and if monitors are used, then there is less error-prone as
compared to the semaphore.
Monitors Semaphore
In monitors, wait always block the caller. In semaphore, wait does not always block the caller.
The monitors are comprised of the shared variables and The semaphore S value means the number of shared resour
the procedures which operate the shared variable. are present in the system.
Condition variables are present in the monitor. Condition variables are not present in the semaphore.
A semaphore S is an integer variable that can be accessed only through two standard operations : wait()
and signal().
The wait() operation reduces the value of semaphore by 1 and the signal() operation increases its value
by 1.
wait(S){
signal(S){
S++;
1. Binary Semaphore – This is similar to mutex lock but not the same thing. It can have only two
values – 0 and 1. Its value is initialized to 1. It is used to implement the solution of critical section
problem with multiple processes.
2. Counting Semaphore – Its value can range over an unrestricted domain. It is used to control
access to a resource that has multiple instances.
Problem Statement – We have a buffer of fixed size. A producer can produce an item and can place in
the buffer. A consumer can pick items and can consume them. We need to ensure that when a producer
is placing an item in the buffer, then at the same time consumer should not consume any item. In this
problem, buffer is the critical section.
To solve this problem, we need two counting semaphores – Full and Empty. “Full” keeps track of
number of items in the buffer at any given time and “Empty” keeps track of number of unoccupied slots.
Initialization of semaphores –
mutex = 1
Full = 0 // Initially, all slots are empty. Thus full slots are 0
Empty = n // All slots are empty initially
do{
//produce an item
wait(empty);
wait(mutex);
//place in buffer
signal(mutex);
signal(full);
}while(true)
When producer produces an item then the value of “empty” is reduced by 1 because one slot will be
filled now. The value of mutex is also reduced to prevent consumer to access the buffer. Now, the
producer has placed the item and thus the value of “full” is increased by 1. The value of mutex is also
increased by 1 beacuse the task of producer has been completed and consumer can access the buffer.
do{
wait(full);
wait(mutex);
signal(mutex);
signal(empty);
// consumes item
}while(true)
As the consumer is removing an item from buffer, therefore the value of “full” is reduced by 1 and the
value is mutex is also reduced so that the producer cannot access the buffer at this moment. Now, the
consumer has consumed the item, thus increasing the value of “empty” by 1. The value of mutex is also
increased so that producer can access the buffer now.
Semaphore was proposed by Dijkstra in 1965 which is a very significant technique to manage concurrent
processes by using a simple integer value, which is known as a semaphore. Semaphore is simply a
variable which is non-negative and shared between threads. This variable is used to solve the critical
section problem and to achieve process synchronization in the multiprocessing environment.
1. Binary Semaphore – This is also known as mutex lock. It can have only two values – 0 and 1. Its
value is initialized to 1. It is used to implement the solution of critical section problem with
multiple processes.
2. Counting Semaphore – Its value can range over an unrestricted domain. It is used to control
access to a resource that has multiple instances.
First, look at two operations which can be used to access and change the value of the semaphore
variable.
1. P operation is also called wait, sleep or down operation and V operation is also called signal,
wake-up or up operation.
2. Both operations are atomic and semaphore(s) is always initialized to one.Here atomic means
that variable on which read, modify and update happens at the same time/moment with no pre-
emption i.e. in between read, modify and update no other operation is performed that may
change the variable.
3. A critical section is surrounded by both operations to implement process synchronization.See
below image.critical section of Process P is in between P and V operation.
Now, let us see how it implements mutual exclusion. Let there be two processes P1 and P2 and a
semaphore s is initialized as 1. Now if suppose P1 enters in its critical section then the value of
semaphore s becomes 0. Now if P2 wants to enter its critical section then it will wait until s > 0, this can
only happen when P1 finishes its critical section and calls V operation on semaphore s. This way mutual
exclusion is achieved. Look at the below image for details which is Binary semaphore.
filter_none
brightness_4
struct semaphore {
Queue<process> q;
} P(semaphore s)
if (s.value == 1) {
s.value = 0;
else {
q.push(P)
sleep();
V(Semaphore s)
if (s.q is empty) {
s.value = 1;
else {
s.value = 1;
q.pop();
wakeup();
The description above is for binary semaphore which can take only two values 0 and 1 and ensure the
mutual exclusion. There is one other type of semaphore called counting semaphore which can take
values greater than one.
Now suppose there is a resource whose number of instance is 4. Now we initialize S = 4 and rest is same
as for binary semaphore. Whenever process wants that resource it calls P or wait function and when it is
done it calls V or signal function. If the value of S becomes zero then a process has to wait until S
becomes positive. For example, Suppose there are 4 process P1, P2, P3, P4 and they all call wait
operation on S(initialized with 4). If another process P5 wants the resource then it should wait until one
of the four processes calls signal function and value of semaphore becomes positive.
Limitations
2. Deadlock, suppose a process is trying to wake up another process which is not in sleep
state.Therefore a deadlock may block indefinitely.
3. The operating system has to keep track of all calls to wait and to signal the semaphore.
Whenever any process waits then it continuously checks for semaphore value (look at this line while
(s==0); in P operation) and waste CPU cycle. To avoid this another implementation is provided below.
Implementation of counting semaphore
filter_none
brightness_4
struct Semaphore {
int value;
Queue<process> q;
} P(Semaphore s)
s.value = s.value - 1;
if (s.value < 0) {
q.push(p);
block();
else
return;
V(Semaphore s)
s.value = s.value + 1;
if (s.value <= 0) {
q.pop();
wakeup(p);
else
return;
In this implementation whenever process waits it is added to a waiting queue of processes associated
with that semaphore. This is done through system call block() on that process. When a process is
completed it calls signal function and one process in the queue is resumed. It uses wakeup() system call.
The monitor is one of the ways to achieve Process synchronization. The monitor is supported by
programming languages to achieve mutual exclusion between processes. For example Java Synchronized
methods. Java provides wait() and notify() constructs.
1. It is the collection of condition variables and procedures combined together in a special kind of
module or a package.
2. The processes running outside the monitor can’t access the internal variable of the monitor but
can call procedures of the monitor.
Syntax:
Condition Variables:
Two different operations are performed on the condition variables of the monitor.
Wait.
signal.
let say we have 2 condition variables
condition x, y; // Declaring variable
Wait operation
x.wait() : Process performing wait operation on any condition variable are suspended. The suspended
processes are placed in block queue of that condition variable.
Signal operation
x.signal(): When a process performs signal operation on condition variable, one of the blocked processes
is given chance.
// Ignore signal
else
Advantages of Monitor:
Monitors have the advantage of making parallel programming easier and less error prone than using
techniques such as semaphore.
Disadvantages of Monitor:
Monitors have to be implemented as part of the programming language . The compiler must generate
code for them. This gives the compiler the additional burden of having to know what operating system
facilities are available to control access to critical sections in concurrent processes. Some languages that
do support monitors are Java,C#,Visual Basic,Ada and concurrent Euclid.
IPC is a capability supported by operating system that allows one process to communicate with
another process. The processes can be running on the same computer or on different computers
connected through a network . IPC enables one application to control another application, and for
several application to share the same data without interfering with one another.
We need a well structured way to facilitate inter process communication which
Race Conditions:
The situation where two or more processes reading or writing some share data and the final results
depends on who runs precisely when are called RACE CONDITIONS.
Process A reads in and store 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, it switches to process B.
Process B also reads in, and also gets a 7, so it stores the name of its in slot 7 and update into be an 8.
Then it goes off and does other things.
Eventually, process A once again, starting from the place at left off last time. It looks next free slot,
finds a7 there, and writes its file name 7 in slot 7, erasing the name that process B just put there. Then
it computes next-free-slot+1 , which is 8, and sets in to 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.
Critical Section and Mutual Exclusion:
That part of the program where the shared memory is accessed is called critical section.
Mutual Exclusion: It is some way of making sure that if one process is using a shared variable or file,
the other process will be excluded from doing the somethings.
The difficulty in printer spooler occurs because process B started using one of the shared variables
before process A was finished with it. If we could arrange matter such that no two processes were
ever in there critical regions at at the same time, we could avoid race conditions.
•3) no process running outside a critical region may block other processes.
•4) no process should have to wait forever to enter its critical regions.
Example : Process A enters its critical region at time Tj. A little later, at time T1, Process B attempt to
enter its critical region. But fails because another process is already in its critical region any we allow
Peterson's solution is used for mutual exclusion and allowed to processes to share a single use
resource without conflict. It uses only shared memory for communication. Peterson's solution
originally worked only with two processes, but has been generalized for more than two.
Before using the shared variable(i.e, before entering its critical region) each process calls enter region
with its own process number 0 to 1 as parameter. This call will cause it to wait, if need be until it is
safe to enter.
ANSI C Code
sdefine false= 0
sdefine true= 1
#define n
int turn;
int tnterested[N]
int other
1-process;
interestedjprocess=TRUE;
turn= process;
voidleave-region(int process)
interestedfprocess-FALSE
}
• After it has finished with the shared variables, the process calls "Leave-region" to indicate that it is
done and to allow the other process to enter.
• Process 0 calls enter region by setting its array element and sets turn to 0.
• If process 1 now makes a calls to enter region , it will hang there until interested[0] go to FALSE, an
event that only happens when process 0 calls Leave-reason to exit the critical reason.
• Now consider the case that both process call "Enter Region" almost simultaneously. Then both will
store the process number in 'turn'. Which ever store is done last is the one that counts the first one is
overwritten and 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 a critical region until process 0 exit its critical region.
:- The problem describe two processes, User and Consumer, who share a common fixed size
buffer. Producer consumer problem also known as the "Bounded Buffer Problem" is a multi-process
synchronization problem.
Producer: The producer's job is to generate a bit of data, put it into the buffer and start again.
Consumer: The consumer is consuming the data(i.e remaining it from the buffer) one piece at a time.
If the buffer is empty, then a consumer should not try to access the data item from it.
Similarly, a producer should not produce any data item if the buffer is full.
Counter: It counts the data items in the buffer. or to track whether the buffer is empty or full. Counter
is shared between two processes and updated by both.
How it works?
• If counter is 1 or greater than 1 then start executing the process and updates the counters.
• Similarly producer check the buffer for the value of Counter for adding data.
• If the counter is less than its maximum values, it means that there is some space in Buffer.
It starts executing for producing the data items and update the counter by implementing it by one.
In this buffer is full producer has to wait until consumer set counter by commenting its value by 1.
In this situation, buffer is empty, that is counter =0, and the producer is busy executive other
instructions or has not been allotted its time slice yet. At this consumer is ready to consume an item
from the buffer.
SEMAPHORES:
What is Semaphore ?
It is a very popular tool used for process synchronization. Semaphore is used to protect any resources
such as Global shared memory that needs to be accessed and updated by many processes
simultaneously.
Whenever a process needs to access the resource, it first needs to take permission from the
semaphore. Semaphore give permission to access a resource if resource is free otherwise process has
to wait.
Counter, a waiting list of processes and two methods( eg. Functions) signal and wait with integer
values.
The semaphore is accessed by only two indivisible operations known as 'wait' ad 'signal' operationa
which is denoted by p and v.
Example
- semaphore allow process to enter critical section is not being used by any process otherwise deny it.
-Initially the count of semaphore is 1. And if it is accessed then count decremented and become
zero(0).
- when a process exits the CS, if perform the signal operations which is an exit criterion.
In this way, the solution to CS using semaphore satisfied that designed protocols.
The semaphore whose value either 0 or 1 is known as binary semaphore. This concept is basically used
in mutual exclusion.
do
wait (semaphore)
critical section
Signal (semaphore)
----
----
When there is more processes 3 which want to access more available resources say 3-4 memories unit
then the semaphore is taken to guard all the three memory location with value 3. It means that 3
processes at the same time can access the semaphore. After giving the access to third process the
value of account become 0.
This type of semaphore that takes a value greater than one is known as 'counting semaphore'.
Monitors
monitor is same as a class type: Like object of class are created, the variable of monitor type are
defined.
Critical regions are written as procedures and encapsulated together in a single module.
The synchronization among the process is provided through the monitor entry procedure.
***Wait and signal introduced in semaphores are also implemented in monitor through the use of
'condition' variables.
Condition variable are different from normal variable because each of them has an associated queue.
Process Calling wait on a particular condition is put into the queue associated with the condition
variable.
A process calling the signal causes the waiting process in the queue to enter the monitor. These all
variables are declared as:
To force a process to exit immediately after the signal operation, signal-to-exit monitor is used. If a
process needs to be inside the monitor for some more time after signalling, then signal-and-
continue monitor in used. It means that the signalling has been done by the process, but it still
maintains a lock on the semaphore.
Message Passing
In this a sender or a source process send a message to a non receiver or destination process. Message
has a predefined structure and message passing uses two system call: Send and Receive
In this calls, the sender and receiver processes address each other by names. Mode of communication
between two process can take place through two methods
1) Direct Addressing
2) Indirect Addressing
Direct Addressing:
In this type that two processes need to name other to communicate. This become easy if they have the
same parent.
Example
send(B, message);
Receive(A, message);
By message passing a link is established between A and B. Here the receiver knows the Identity of
sender message destination. This type of arrangement in direct communication is known as Symmetric
Addressing.
Another type of addressing known as asymmetric addressing where receiver does not know the ID of
the sending process in advance.
diagram:
Indirect addressing:
In this message send and receive from a mailbox. A mailbox can be abstractly viewed as an object into
which messages may be placed and from which messages may be removed by processes. The sender
and receiver processes should share a mailbox to communicate.
- One to One link: one sender wants to communicate with one receiver. Then single link is established.
- Many to Many link: Multiple Sender want to communicate with single receiver.Example in client
server system, there are many crying processes and one server process. The mailbox is here known as
PORT.
- One to Many link: One sender wants to communicate with multiple receiver, that is to broadcast
message.
Below are some of the classical problem depicting flaws of process synchronaization in systems where
cooperating processes are present.
This problem is generalised in terms of the Producer Consumer problem, where a finite buffer
pool is used to exchange messages between producer and consumer processes.
Because the buffer pool has a maximum size, this problem is often called the Bounded buffer problem.
Solution to this problem is, creating two counting semaphores "full" and "empty" to keep track
of the current number of full and empty buffers respectively.
The dining philosopher's problem involves the allocation of limited resources to a group of
processes in a deadlock-free and starvation-free manner.
There are five philosophers sitting around a table, in which there are five chopsticks/forks kept
beside them and a bowl of rice in the centre, 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.
In this problem there are some processes(called readers) that only read the shared data, and
never change it, and there are other processes(called writers) who may change the data in
addition to reading, or instead of reading it.
There are various type of readers-writers problem, most centred on relative priorities of readers
and writers
The dining philosophers problem is another classic synchronization problem which is used to evaluate
situations where there is a need of allocating multiple resources to multiple processes.
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.
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.
while(TRUE)
wait(stick[i]);
/*
*/
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.
The Dining Philosophers Problem
The Dining Philosophers problems is a classic synchronization problem (E. W. Dijkstra. Co-operating
Sequential Processes. In F. Genuys (ed.) Programming Languages, Academic Press, London, 1965)
introducing semaphores as a conceptual synchronization mechanism. The problem is discussed in just
about every operating systems textbook.
Dining Philosophers. There is a dining room containing a circular table with five chairs. At each
chair is a plate, and between each plate is a single chopstick. In the middle of the table is a bowl of
spaghetti. Near the room are five philosophers who spend most of their time thinking, but who
occasionally get hungry and need to eat so they can think some more.
In order to eat, a philosopher must sit at the table, pick up the two chopsticks to the left and right of a
plate, then serve and eat the spaghetti on the plate.
process P[i]
while true do
{ THINK;
EAT;
A philosopher may THINK indefinately. Every philosopher who EATs will eventually finish. Philosophers
may PICKUP and PUTDOWN their chopsticks in either order, or nondeterministically, but these are
atomic actions, and, of course, two philosophers cannot use a single CHOPSTICK at the same time.
The problem is to design a protocol to satisfy the liveness condition: any philosopher who tries to EAT,
eventually does.
Discussion. Of course, the best thing is to go off and try to solve this problem on your own by exploring
various protocols philosophers might use to acquire chopsticks. You are likely to quickly encounter the
same deadlock and livelock scenarios we saw in the mutual exclusion problem, but you will quickly see in
this case that mutual exclusion is too primitive a synchronization mechanism for solving this problem.
In his textbook Modern Operating Systems (Prentice-Hall, 1992) Tannenbaum gives the pseudo-code for
a solution to the dining philosophers problem that is shown below. Similar solutions are found in most
operating systems textbooks. All of them are renditions of the original treatment by Dijkstra, which
motivated the semaphore mechanism he was introducing in his original article. A \emph{semaphore} is
an integer or boolean value, S, with two associated atomic operations:
UP(S) increment S
In time-sharing systems, "waiting" is implemented by the operating system, which may put processes on
a wait-list for later execution. In hardware, "waiting" may be accomplished by busy-waiting or by some
form of explicit signaling, such as token passing.
Tannenbaum's Solution. This solution uses only boolean semaphors. There is one global semaphore to
provide mutual exclusion for exectution of critical protocols. There is one semaphore for each chopstick.
In addition, a local two-phase prioritization scheme is used, under which philosophers defer to their
neighbors who have declared themselves "hungry." All arithmetic is modulo 5.
system DINING_PHILOSOPHERS
VAR
procedure philosopher(i)
while TRUE do
THINKING;
take_chopsticks(i);
EATING;
drop_chopsticks(i);
The take_chopsticks procedure involves checking the status of neighboring philosophers and then
declaring one's own intention to eat. This is a two-phase protocol; first declaring the status HUNGRY,
then going on to EAT.
procedure take_chopsticks(i)
{
pflag[i] := HUNGRY;
test[i];
if ( pflag[i] == HUNGRY
then
pflag[i] := EAT;
UP(s[i])
Once a philosopher finishes eating, all that remains is to relinquish the resources---its two chopsticks---
and thereby release waiting neighbors.
void drop_chopsticks(int i)
The protocol is fairly elaborate, and Tannenbaum's presentation is made more subtle by its coding style.
A Simpler Solution. Other authors, including Dijkstra, have posed simpler solutions to the dining
philosopher problem than that proposed by Tannenbaum (depending on one's notion of "simplicity," of
course). One such solution is to restrict the number of philosophers allowed access to the table. If there
are N chopsticks but only N-1 philosophers allowed to compete for them, at least one will succeed, even
if they follow a rigid sequential protocol to acquire their chopsticks.
This solution is implemented with an integer semaphore, initialized to N-1. Both this and Tannenbaum's
solutions avoid deadlock a situation in which all of the philosophers have grabbed one chopstick and are
deterministically waiting for the other, so that there is no hope of recovery. However, they may still
permit starvation, a scenario in which at least one hungry philosopher never gets to eat.
Starvation occurs when the asynchronous semantics may allow an individual to eat repeatedly, thus
keeping another from getting a chopstick. The starving philosopher runs, perhaps, but doesn't make
progress. The observation of this fact leads to some further refinement of what fairness means. Under
some notions of fairness the solutions given above can be said to be correct.
repeat
wait(chopstick[i]);
wait(chopstick[(i+1) mod 5]);
...
eat
...
signal(chopstick[i]);
signal(chopstick[(i+1) mod 5]);
...
think
...
until false;
Can suffer from deadlock (e.g. all philosophers decide to eat at the same time and all pick up their left
chopstick first) and/or starvation. Some ways to avoid deadlock are:
Readers writer problem is another example of a classic synchronization problem. There are many
variants of this problem, one of which is examined below.
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.
while(TRUE)
wait(w);
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);
// 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.
If one of the people tries editing the file, no other person should be reading or writing at the
same time, otherwise changes will not be visible to him/her.
However if some person is reading the file, then others may read it at the same time.
Problem parameters:
Once a writer is ready, it performs its write. Only one writer may write at a time
Here priority means, no reader should wait if the share is currently opened for reading.
Three variables are used: mutex, wrt, readcnt to implement solution
2. int readcnt; // readcnt tells the number of processes performing read in the critical section,
initially 0
Writer process:
2. If allowed i.e. wait() gives a true value, it enters and performs the write. If not allowed, it keeps
on waiting.
do {
wait(wrt);
signal(wrt);
} while(true);
Reader process:
2. If allowed:
it increments the count of number of readers inside the critical section. If this reader is
the first reader entering, it locks the wrt semaphore to restrict the entry of writers if any
reader is inside.
It then, signals mutex as any other reader is allowed to enter while others are already
reading.
After performing reading, it exits the critical section. When exiting, it checks if no more
reader is inside, it signals the semaphore “wrt” as now, writer can enter the critical
section.
do {
wait(mutex);
readcnt++;
if (readcnt==1)
wait(wrt);
signal(mutex);
readcnt--;
} while(true);
Thus, the semaphore ‘wrt‘ is queued on both readers and writers in a manner such that preference is
given to readers if writers are also there. Thus, no reader is waiting simply because a writer has
requested to enter the critical section.
The main objective of an operating system is to provide proper communication between hardware and
software resources and also give common services to programs. When an operating system process
wants to access any resource, it firstly sends a request to the particular resource which it wants to
access, then it utilizes the resource and finally releases the resource after using. For suppose many
processes are trying to access one resource at the same time, it becomes difficult to provide one
resource to all the processes at a time in such a situation the concept named deadlock arises. Hence this
article describes how deadlock occurs and how to overcome this deadlock situation.
Definition: Dead-Lock is a situation where two or more processors are waiting for some event to
happen, but such events that don’t happen is a deadlock condition, and the processors are said to be in
a deadlock state. For instance, let us assume a real-time scenario, where there are two cars A & B,
driven by two individual drivers on a one-way road. Now the situation arises where Car A driver says he
moving towards north is a correct direction, while Car B driver says he moving toward south direction is
correct. But neither of one moves back to allow another car to move forward, this condition is called a
deadlock condition.
car-example
For better understanding let us consider another example where there two resources R1, R2, and two
processes P1 and P2, where R1 is assigned to P1 and R2 is assigned to P2. Now if P1 wants to access R2,
as we already know R2 is held by P2, and now P2 wants to access R1, which is P1 executes only when it
gets accessed to R2, also P2 executes only when it gets accessed to R1 this situation is a deadlock state.
processor-example
Dead-Lock Conditions
The following are the four important deadlock conditions to occur if all the conditions occur
simultaneously there are certain chances for the deadlock to occur.
Mutual Exclusion
It means whatever resource we are using it must be used in a mutually exclusive way. Where only one
processes use one resource at a time only. For example, the printing process is going on and all sudden
another process tries to interrupt the printing process. So here in mutual exclusion situation, only after
the printing task is completed then only the next task is processed. Mutual exclusion can be eliminated
by sharing resources simultaneously, which is not possible practically.
mutual-exclusion
No Pre-emption
According to pre-emptive based algorithms, if there is a priority task trying to interrupt the current task.
The pre-emptive algorithm it holds the current task and firstly executes priority task and get backs to its
first task. A situation explained as per the above example where a process holds the resource as long as
it gets executed, that is P1 can release R1 only after executing, similarly P2 release R2 only after
execution. If there is no pre-emption the deadlock may occur.
no-
preemption-example
A process is holding some resources and is waiting for additional resources but those resources are
acquired by some other process. From the above example, P1 is holding R1 and waiting for R2, where R2
is acquired by P2, and P2 is holding R2 and waiting for R1, where R1 is acquired by P1 is a hold and wait
situation deadlock may occur in the system.
hold-and-wait-example
Circular Wait
A set of processes are said to be in deadlock if one process is waiting for a resource that is allocated to
another process and that process is waiting for a resource, it is similar to the above-explained example
where it is in loop form. Where P1 is waiting for R2 and R2 is allocated for P2 and P2 is waiting for R1
and R1 allocated for P1 which is a circular wait form if this condition satisfies deadlock occurs.
circular-
wait-example
The cases where we allocate resources to processes, and operating system rechecks if a deadlock has
occurred in the system or no using 2 main deadlock detection algorithms, they are
Single instance
Single Instance
A single instance is a situation where a system is having single instances of all the resources. It is also
known as wait for graph algorithm or resource allocation graph. The resource allocation graph is
consisting of a set of processes and set of resources which are represented as two different vertices. The
resources in the resource allocation graph are modified and are represented as wait for graph form.
Where wait for graph form has only processes which are represented as vertices as shown below
wherein,
Resource allocation graph: Processes P1, P2, P3 and resources R1, R2, R3 are represented in the
resource-allocation graph.
Wait for Graph: Only Processes P1, P2, P3 are mentioned in wait for the graph.
If there is a cycle condition, that if there is a continuous flow of a process in one direction it
means cycle condition exits and wait for the graph is in a deadlock condition.
Example 1: The below example shows there is no deadlock state because there is no continuous flow
observed in wait for the graph.
single-instance-
example1
Example 2: Deadlock condition has occurred because there is a continuous flow of cycle from P1 to P4.
single-instance-
example2
If deadlock occurs very frequently in the system then the detection algorithm is used frequently. If there
is more usage of the detection algorithm then there will be more overhead and more computation time.
Hence to overcome this, we invoke the algorithm after, giving an equal amount of time, this is how the
weight for the graph is used to detect deadlock.
Let us consider the following example, assume there are 3 processes P0, P1, P2, and resource type A, B,
C where A can be CPU, B can be printer and C can be the keyboard. The digits “0” in the column
represent the availability of resources.
Case (i): Suppose if we take the condition request is “000” condition which is present in P0 and P2, we
should check which request is fulfilled, the processes P0 release the processes after getting allocated,
then next P2 processes releases after getting allocated. Like this, in a sequence, one by one process
releases P0, P2, P3, P1, P4 in a sequence. Finally, we get available resources as P7, P2, P6. The available
sequence is a condition where there is no deadlock.
bankers-algorithm-example1
Case(ii): Suppose if P2 is 001 instead of 000, now apply the banker’s algorithm to check for deadlock
condition, where the only P0 gets executed among all 5 processes. Hence P1, P2, P3, P4 are in deadlock
state except for P0.
bankers-example2
Applications of Deadlock
The applications of deadlock can be explained with a real-time example of examination online results,
where several students try to access their university website on time of release. One can observe that at
times the web page does not load at a time to multiple users, this is a deadlock condition. This can be
overcome using any one of the algorithms.
Advantages
Disadvantages
References:
1. Abraham Silberschatz, Greg Gagne, and Peter Baer Galvin, "Operating System Concepts, Ninth
Edition ", Chapter 7
For the purposes of deadlock discussion, a system can be modeled as a collection of limited
resources, which can be partitioned into different categories, to be allocated to a number of
processes, each having different needs.
Resource categories may include memory, printers, CPUs, open files, tape drives, CD-ROMS, etc.
By definition, all the resources within a category are equivalent, and a request of this category
can be equally satisfied by any one of the resources in that category. If this is not the case ( i.e. if
there is some difference between the resources within a category ), then that category needs to
be further divided into separate categories. For example, "printers" may need to be separated
into "laser printers" and "color inkjet printers".
In normal operation a process must request a resource before using it, and release it when it is
done, in the following sequence:
1. Request - If the request cannot be immediately granted, then the process must wait
until the resource(s) it needs become available. For example the system calls open( ),
malloc( ), new( ), and request( ).
2. Use - The process uses the resource, e.g. prints to the printer or reads from the file.
3. Release - The process relinquishes the resource. so that it becomes available for other
processes. For example, close( ), free( ), delete( ), and release( ).
For all kernel-managed resources, the kernel keeps track of what resources are free and which
are allocated, to which process they are allocated, and a queue of processes waiting for this
resource to become available. Application-managed resources can be controlled using mutexes
or wait( ) and signal( ) calls, ( i.e. binary or counting semaphores. )
A set of processes is deadlocked when every process in the set is waiting for a resource that is
currently allocated to another process in the set ( and which can only be released when that
other waiting process makes progress. )
2. Hold and Wait - A process must be simultaneously holding at least one resource and
waiting for at least one resource that is currently being held by some other process.
3. No preemption - Once a process is holding a resource ( i.e. once its request has been
granted ), then that resource cannot be taken away from that process until the process
voluntarily releases it.
4. Circular Wait - A set of processes { P0, P1, P2, . . ., PN } must exist such that every P[ i ] is
waiting for P[ ( i + 1 ) % ( N + 1 ) ]. ( Note that this condition implies the hold-and-wait
condition, but it is easier to deal with the conditions if the four are considered
separately. )
In some cases deadlocks can be understood more clearly through the use of Resource-
Allocation Graphs, having the following properties:
o A set of resource categories, { R1, R2, R3, . . ., RN }, which appear as square nodes on the
graph. Dots inside the resource nodes indicate specific instances of the resource. ( E.g.
two dots might represent two laser printers. )
o Request Edges - A set of directed arcs from Pi to Rj, indicating that process Pi has
requested Rj, and is currently waiting for that resource to become available.
o Assignment Edges - A set of directed arcs from Rj to Pi indicating that resource Rj has
been allocated to process Pi, and that Pi is currently holding resource Rj.
o Note that a request edge can be converted into an assignment edge by reversing the
direction of the arc when the request is granted. ( However note also that request edges
point to the category box, whereas assignment edges emanate from a particular
instance dot within the box. )
o For example:
Figure 7.1 - Resource allocation graph
If a resource-allocation graph contains no cycles, then the system is not deadlocked. ( When
looking for cycles, remember that these are directed graphs. ) See the example in Figure 7.2
above.
If a resource-allocation graph does contain cycles AND each resource category contains only a
single instance, then a deadlock exists.
If a resource category contains more than one instance, then the presence of a cycle in the
resource-allocation graph indicates the possibility of a deadlock, but does not guarantee one.
Consider, for example, Figures 7.3 and 7.4 below:
Figure 7.2 - Resource allocation graph with a deadlock
2. Deadlock detection and recovery - Abort a process or preempt some resources when
deadlocks are detected.
3. Ignore the problem all together - If deadlocks only occur once a year or so, it may be
better to simply let them happen and reboot as necessary than to incur the constant
overhead and system performance penalties associated with deadlock prevention or
detection. This is the approach that both Windows and UNIX take.
In order to avoid deadlocks, the system must have additional information about all processes. In
particular, the system must know what resources a process will or may request in the future.
( Ranging from a simple worst-case maximum to a complete resource request and release plan
for each process, depending on the particular algorithm. )
Deadlock detection is fairly straightforward, but deadlock recovery requires either aborting
processes or preempting resources, neither of which is an attractive alternative.
If deadlocks are neither prevented nor detected, then when a deadlock occurs the system will
gradually slow down, as more and more processes become stuck waiting for resources currently
held by the deadlock and by other waiting processes. Unfortunately this slowdown can be
indistinguishable from a general system slowdown when a real-time process has heavy
computing needs.
Deadlocks can be prevented by preventing at least one of the four required conditions:
Unfortunately some resources, such as printers and tape drives, require exclusive access by a
single process.
To prevent this condition processes must be prevented from holding one or more resources
while simultaneously waiting for one or more others. There are several possibilities for this:
o Require that all processes request all resources at one time. This can be wasteful of
system resources if a process needs one resource early in its execution and doesn't need
some other resource until much later.
o Require that processes holding resources must release them before requesting new
resources, and then re-acquire the released resources along with the new ones in a
single new request. This can be a problem if a process has partially completed an
operation using a resource and then fails to get it re-allocated after releasing it.
o Either of the methods described above can lead to starvation if a process requires one
or more popular resources.
7.4.3 No Preemption
Preemption of process resource allocations can prevent this condition of deadlocks, when it is
possible.
o One approach is that if a process is forced to wait when requesting a new resource, then
all other resources previously held by this process are implicitly released, ( preempted ),
forcing this process to re-acquire the old resources along with the new resources in a
single request, similar to the previous discussion.
o Another approach is that when a resource is requested and not available, then the
system looks to see what other processes currently have those resources and are
themselves blocked waiting for some other resource. If such a process is found, then
some of their resources may get preempted and added to the list of resources for which
the process is waiting.
o Either of these approaches may be applicable for resources whose states are easily
saved and restored, such as registers and memory, but are generally not applicable to
other devices such as printers and tape drives.
One way to avoid circular wait is to number all resources, and to require that processes request
resources only in strictly increasing ( or decreasing ) order.
In other words, in order to request resource Rj, a process must first release all Ri such that i >= j.
One big challenge in this scheme is determining the relative ordering of the different resources
The general idea behind deadlock avoidance is to prevent deadlocks from ever happening, by
preventing at least one of the aforementioned conditions.
This requires more information about each process, AND tends to lead to low device utilization.
( I.e. it is a conservative approach. )
In some algorithms the scheduler only needs to know the maximum number of each resource
that a process might potentially use. In more complex algorithms the scheduler can also take
advantage of the schedule of exactly what resources may be needed in what order.
When a scheduler sees that starting a process or granting resource requests may lead to future
deadlocks, then that process is just not started or the request is not granted.
A resource allocation state is defined by the number of available and allocated resources, and
the maximum requirements of all processes in the system.
More formally, a state is safe if there exists a safe sequence of processes { P0, P1, P2, ..., PN }
such that all of the resource requests for Pi can be granted using the resources currently
allocated to Pi and all processes Pj where j < i. ( I.e. if all the processes prior to Pi finish and free
up their resources, then Pi will be able to finish also, using the resources that they have freed
up. )
If a safe sequence does not exist, then the system is in an unsafe state, which MAY lead to
deadlock. ( All safe states are deadlock free, but not all unsafe states lead to deadlocks. )
For example, consider a system with 12 tape drives, allocated as follows. Is this a safe state?
What is the safe sequence?
P0 10 5
P1 4 2
P2 9 2
What happens to the above table if process P2 requests and is granted one more tape drive?
Key to the safe state approach is that when a request is made for resources, the request is
granted only if the resulting allocation state is a safe one.
In this case, unsafe states can be recognized and avoided by augmenting the resource-allocation
graph with claim edges, noted by dashed lines, which point from a process to a resource that it
may request in the future.
In order for this technique to work, all claim edges must be added to the graph for any particular
process before that process is allowed to request any resources. ( Alternatively, processes may
only make requests for resources for which they have already established claim edges, and claim
edges cannot be added to any process that is currently holding resources. )
When a process makes a request, the claim edge Pi->Rj is converted to a request edge. Similarly
when a resource is released, the assignment reverts back to a claim edge.
This approach works by denying requests that would produce cycles in the resource-allocation
graph, taking claim edges into effect.
Consider for example what happens when process P2 requests resource R2:
The resulting resource-allocation graph would have a cycle in it, and so the request cannot be
granted.
Figure 7.8 - An unsafe state in a resource allocation graph
For resource categories that contain more than one instance the resource-allocation graph
method does not work, and more complex ( and less efficient ) methods must be chosen.
The Banker's Algorithm gets its name because it is a method that bankers could use to assure
that when they lend out resources they will still be able to satisfy all their clients. ( A banker
won't loan out a little money to start building a house unless they are assured that they will later
be able to loan out the rest of the money to finish the house. )
When a process starts up, it must state in advance the maximum allocation of resources it may
request, up to the amount available on the system.
When a request is made, the scheduler determines whether granting the request would leave
the system in a safe state. If not, then the process must wait until the request can be granted
safely.
The banker's algorithm relies on several key data structures: ( where n is the number of
processes and m is the number of resource categories. )
o Available[ m ] indicates how many resources are currently available of each type.
o Need[ n ][ m ] indicates the remaining resources needed of each type for each process.
( Note that Need[ i ][ j ] = Max[ i ][ j ] - Allocation[ i ][ j ] for all i, j. )
o One row of the Need vector, Need[ i ], can be treated as a vector corresponding to the
needs of process i, and similarly for Allocation and Max.
o A vector X is considered to be <= a vector Y if X[ i ] <= Y[ i ] for all i.
In order to apply the Banker's algorithm, we first need an algorithm for determining whether or
not a particular state is safe.
This algorithm determines if the current state of a system is safe, according to the following
steps:
Work is a working copy of the available resources, which will be modified during
the analysis.
2. Find an i such that both (A) Finish[ i ] == false, and (B) Need[ i ] < Work. This process has
not finished, but could with the given available working set. If no such i exists, go to step
4.
3. Set Work = Work + Allocation[ i ], and set Finish[ i ] to true. This corresponds to process i
finishing up and releasing its resources back into the work pool. Then loop back to step
2.
4. If finish[ i ] == true for all i, then the state is a safe state, because a safe sequence has
been found.
( JTB's Modification:
4. For step 4, the test can be either Finish[ i ] > 0 for all i, or s >= n. The benefit of this
method is that if a safe state exists, then Finish[ ] indicates one safe sequence ( of
possibly many. ) )
Now that we have a tool for determining if a particular state is safe or not, we are now ready to
look at the Banker's algorithm itself.
This algorithm determines if a new request is safe, and grants it only if it is safe to do so.
When a request is made ( that does not exceed currently available resources ), pretend it has
been granted, and then see if the resulting state is a safe one. If so, grant the request, and if not,
deny the request, as follows:
1. Let Request[ n ][ m ] indicate the number of resources of each type currently requested
by processes. If Request[ i ] > Need[ i ] for any process i, raise an error condition.
2. If Request[ i ] > Available for any process i, then that process must wait for resources to
become available. Otherwise the process can continue to step 3.
3. Check to see if the request can be granted safely, by pretending it has been granted and
then seeing if the resulting state is safe. If so, grant the request, and if not, then the
process must wait until its request can be granted safely.The procedure for granting a
request ( or pretending to for testing purposes ) is:
And now consider what happens if process P1 requests 1 instance of A and 2 instances of C.
( Request[ 1 ] = ( 1, 0, 2 ) )
What about requests of ( 3, 3,0 ) by P4? or ( 0, 2, 0 ) by P0? Can these be safely granted? Why or
why not?
If deadlocks are not avoided, then another approach is to detect when they have occurred and
recover somehow.
In addition to the performance hit of constantly checking for deadlocks, a policy / algorithm
must be in place for recovering from deadlocks, and there is potential for lost work when
processes must be aborted or have their resources preempted.
If each resource category has a single instance, then we can use a variation of the resource-
allocation graph known as a wait-for graph.
An arc from Pi to Pj in a wait-for graph indicates that process Pi is waiting for a resource that
process Pj is currently holding.
Figure 7.9 - (a) Resource allocation graph. (b) Corresponding wait-for graph
This algorithm must maintain the wait-for graph, and periodically search it for cycles.
The detection algorithm outlined here is essentially the same as the Banker's algorithm, with
two subtle differences:
o In step 1, the Banker's Algorithm sets Finish[ i ] to false for all i. The algorithm presented
here sets Finish[ i ] to false only if Allocation[ i ] is not zero. If the currently allocated
resources for this process are zero, the algorithm sets Finish[ i ] to true. This is
essentially assuming that IF all of the other processes can finish, then this process can
finish also. Furthermore, this algorithm is specifically looking for which processes are
involved in a deadlock situation, and a process that does not have any resources
allocated cannot be involved in a deadlock, and so can be removed from any further
consideration.
o In step 4, the basic Banker's Algorithm says that if Finish[ i ] == true for all i, that there is
no deadlock. This algorithm is more specific, by stating that if Finish[ i ] == false for any
process Pi, then that process is specifically involved in the deadlock which has been
detected.
( Note: An alternative method was presented above, in which Finish held integers instead of
booleans. This vector would be initialized to all zeros, and then filled with increasing integers as
processes are detected which can finish. If any processes are left at zero when the algorithm
completes, then there is a deadlock, and if not, then the integers in finish describe a safe
sequence. To modify this algorithm to match this section of the text, processes with allocation =
zero could be filled in with N, N - 1, N - 2, etc. in step 1, and any processes left with Finish = 0 in
step 4 are the deadlocked processes. )
Consider, for example, the following state, and determine if it is currently deadlocked:
Now suppose that process P2 makes a request for an additional instance of type C, yielding the
state shown below. Is the system now deadlocked?
The answer may depend on how frequently deadlocks are expected to occur, as well as the
possible consequences of not catching them immediately. ( If deadlocks are not removed
immediately when they occur, then more and more processes can "back up" behind the
deadlock, making the eventual task of unblocking the system more difficult and possibly
damaging to more processes. )
2. Do deadlock detection only when there is some clue that a deadlock may have occurred,
such as when CPU utilization reduces to 40% or some other magic number. The
advantage is that deadlock detection is done much less frequently, but the down side is
that it becomes impossible to detect the processes involved in the original deadlock,
and so deadlock recovery can be more complicated and damaging to more processes.
3. ( As I write this, a third alternative comes to mind: Keep a historical log of resource
allocations, since that last known time of no deadlocks. Do deadlock checks periodically
( once an hour or when CPU usage is low?), and then use the historical log to trace
through and determine when the deadlock occurred and what processes caused the
initial deadlock. Unfortunately I'm not certain that breaking the original deadlock would
then free up the resulting log jam. )
1. Inform the system operator, and allow him/her to take manual intervention.
3. Preempt resources.
Two basic approaches, both of which recover resources allocated to terminated processes:
o Terminate all processes involved in the deadlock. This definitely solves the deadlock, but
at the expense of terminating more processes than would be absolutely necessary.
o Terminate processes one by one until the deadlock is broken. This is more conservative,
but requires doing deadlock detection after each step.
In the latter case there are many factors that can go into deciding which processes to terminate
next:
1. Process priorities.
2. How long the process has been running, and how close it is to finishing.
3. How many and what type of resources is the process holding. ( Are they easy to preempt and
restore? )
7. ( Whether or not the process has made non-restorable changes to any resource. )
When preempting resources to relieve deadlock, there are three important issues to be
addressed:
1. Selecting a victim - Deciding which resources to preempt from which processes involves
many of the same decision criteria outlined above.
2. Rollback - Ideally one would like to roll back a preempted process to a safe state prior to
the point at which that resource was originally allocated to the process. Unfortunately it
can be difficult or impossible to determine what such a safe state is, and so the only safe
rollback is to roll back all the way back to the beginning. ( I.e. abort the process and
make it start over. )
3. Starvation - How do you guarantee that a process won't starve because its resources are
constantly being preempted? One option would be to use a priority system, and
increase the priority of a process every time its resources get preempted. Eventually it
should get a high enough priority that it won't get preempted any more.
What is a Deadlock?
Deadlocks are a set of blocked processes each holding a resource and waiting to acquire a resource held
by another process.
How to avoid Deadlocks
Deadlocks can be avoided by avoiding at least one of the four conditions, because all this four conditions
are required simultaneously to cause deadlock.
1. Mutual Exclusion
Resources shared such as read-only files do not lead to deadlocks but resources, such as printers and
tape drives, requires exclusive access by a single process.
In this condition processes must be prevented from holding one or more resources while simultaneously
waiting for one or more others.
3. No Preemption
Preemption of process resource allocations can avoid the condition of deadlocks, where ever possible.
4. Circular Wait
Circular wait can be avoided if we number all resources, and require that processes request resources
only in strictly increasing(or decreasing) order.
Handling Deadlock
The above points focus on preventing deadlocks. But what to do once a deadlock has occured. Following
three strategies can be used to remove deadlock after its occurrence.
1. Preemption
We can take a resource from one process and give it to other. This will resolve the deadlock situation,
but sometimes it does causes problems.
2. Rollback
In situations where deadlock is a real possibility, the system can periodically make a record of the state
of each process and when deadlock occurs, roll everything back to the last checkpoint, and restart, but
allocating resources differently so that deadlock does not occur.
What is a Livelock?
There is a variant of deadlock called livelock. This is a situation in which two or more processes
continuously change their state in response to changes in the other process(es) without doing any useful
work. This is similar to deadlock in that no progress is made but differs in that neither process is blocked
or waiting for anything.
A human example of livelock would be two people who meet face-to-face in a corridor and each moves
aside to let the other pass, but they end up swaying from side to side without making any progress
because they always move the same way at the same time.
Deadlock refers to the condition when 2 or more processes are waiting for each other to release a
resource indefinitely. A process in nature requests a resource first and uses it and finally releases it.
But in deadlock situation, both the processes wait for the other process . Let’s look at one example to
understand it – Say, Process A has resource R1 , Process B has resource R2. If Process A request resource
R2 and Process B requests resource R1, at the same time , then deadlock occurs.
More Facts
In deadlock, processes get blocked because each process is holding some resource and they are
waiting for other resource , which is held by another process.
In Multiprocessing, many processes share a specific type of mutually exclusion resource known
as soft lock.
Time Sharing computers are equipped with a hardwired lock which gaurantees exclusive access
to processes , thus preventing deadlock.
Deadlock can occur if all the given 4 conditions ( Coffman Conditions ) are satisfied, that is if all are true :
Mutual Exclusion
No Preemption
Circular wait
Let’s look at each one in detail :
1. Mutual exclusion – If At least one or more resource are in non shareable mode , the resources
should be in mutual exclusion. Non Shareable means , resource can’t be used by more than 1
process at a time, unless the resource gets released.
2. Hold and Wait – Processes which are already holding at least 1 resource may request new
resource.
3. No Pre emptive condition – Resources can’t be released by force , only the process holding the
resource can release it, which does so after the process has finished its task.
4. Circular wait – 2 or more process form a circular chain where each process waits and want to
acquire the resource which the next process in the chain holds.
September 1, 2019
Deadlock Detection is an important task of OS. As the OS doesn’t take many precautionary means to
avoid it. The OS periodically checks if there is any existing deadlock in the system and take measures to
remove the deadlocks.
Detection
There are 2 different cases in case of Deadlock detection –
o We make a different algorithm. (We will discuss these algorithm in future posts)
A wait-for graph is made. A Wait-for graph vertex denotes process. The edge implies process waiting for
Process 2 to release resource. A deadlock is detected if one wait-for graph contains a cycle.
Wait-for graph is made by looking at resource allocation graph. An edge between P1 to P2 exists if P1
needs some resource which P2 has.
In the above wait-for graph P1 is waiting for resource currently in use by P2 and P2 is waiting for
resource currently in use by P3 and so on… P5 is waiting for resource currently in use by P1. Which
creates a cycle thus deadlock is confirmed.
To detect cycle, system maintains the wait state of graph and periodically invoke an algorithm to detect
cycle in graph.
By multiple instance we mean one resource may allow 2 or more accesses concurrently, in such cases –
In this, a new algorithm is used. It’s a bit similar to Banker’s Algorithm, but Bankers Algorithm is
different.
o Available
Vector of length m
o Allocation
o Request
Check this page here to learn how to solve Deadlock Detection Algorithm.
Deadlock Recovery
Kill the Process – One way is to kill all the process in deadlock or the second way kill the process
one by one, and check after each if still deadlock exists and do the same till the deadlock is
removed.
Preemption – The resources that are allocated to the processes involved in deadlock are taken
away(preempted) and are transferred to other processes. In this way, system may recover from
deadlock as we may change system state.
Rollback – The OS maintains a database of all different states of system, a state when the
system is not in deadlock is called safe state. A rollback to previous ‘n’ number of safe states in
iterations can help in the recover.
Deadlock Detection Algorithm in Operating System
Deadlock Detection Algorithm helps decide if in scenario of multi instance resources for various
processes are in deadlock or not.
In cases of single resource instance we can create wait-for graph to check deadlock state. But, this we
can’t do for multi instance resources system.
Algorithm Example
Do not confuse the deadlock detection algorithm with Banker’s algorithm which is completely different.
Learn Banker’s Algorithm here.
Available
o Vector of length m
Allocation
Request
Steps
Step 1
2. Finish(vector) length = n
1. For i=0, 1, …., n-1, if Allocationi = 0, then Finish[i] = true; otherwise, Finish[i]= false.
Step 2
1. Finish[i] == false
Step 3
2. Finish[i]= true
Go to Step 2.
Step 4
If Finish[i]== false for some i, 0<=i<n, then the system is in a deadlocked state. Moreover,
if Finish[i]==false the process Pi is deadlocked.
Deadlock Avoidance and Prevention in OS
August 21, 2019
Deadlock Prevention
Mutual exclusion
No Pre emption
Circular wait
Hence , the way to prevent deadlock is to ensure that at least one of these conditions do not hold :
Mutual exclusion – If 1 resource is non- shareable , then mutual exclusion condition must hold.
We can’t prevent deadlock by denying this mutual exclusion condition, as some resource are
inherently non shareable and this can’t be changed.
Hold And wait – For Hold and wait condition to never occur, it should be made sure that
whenever a process requests a resource, it does not hold any other resource.There can be many
protocols implemented –
o One protocol could be Allocating all resources to a process before its execution .
o Other Could be , A process can request resource only when it has no resource.
Starvation
No Preemption of resources- Allow preemption of resources from process when resources are
acquired by high priority process.
Circular wait – To prevent this, impose a total ordering of all resources types and every process
should request resource in increasing order.
Deadlock Avoidance
Deadlock prevention causes low device utilization and reduced system throughput.
To avoid deadlocks, one requires additional information about how resources all to be requested.
For one instance of each resource type, Resource Allocation Graph Algorithm is used.
For avoidance of multiple instances of each resource type, Banker’s Algorithm is used.
Banker’s Algorithm is a resource allocation and deadlock avoidance algorithm. This name has been given
since it is one of most problem in Banking Systems these days.
In this, as a new process P1 enters, it declares the maximum number of resource it needs.
o The system looks at those and checks if allocating those resources to P1 will leave
system in safe state or not.
o If after allocation, it will be in safe state , the resources are allocated to process P1.
A state is safe if the system can allocate all resources requested by all processes ( up to their stated
maximums ) without entering a deadlock state.(MCQ Question in CoCubes)
Now, Let’s have a look at how these are implemented . They are implemented using 4 data structure.
Available
o 1 D Array of Size m.
o Each element Available[i] indicates the number of resources of i type available. Denoted
by Ri
Maximum
o Maximum[i,j] tells the maximum demand an ith process will request of jth type
resource.
Allocation
Need
o Tells about remaining resources type which are required by each process.
o Need[i,j] means i th process needs k instance of j th resource type.
The above data Structures Size and value vary over time. There are 2 Algorithm on which banker’s
Algorithm is build upon
Safety Algorithm –
Deadlock Characteristics
As discussed in the previous post, deadlock has following characteristics.
1. Mutual Exclusion
3. No preemption
4. Circular wait
Deadlock Prevention
1. Allocate all required resources to the process before the start of its execution, this way hold and
wait condition is eliminated but it will lead to low device utilization. for example, if a process
requires printer at a later time and we have allocated printer before the start of its execution
printer will remain blocked till it has completed its execution.
2. The process will make a new request for resources after releasing the current set of resources.
This solution may lead to starvation.
Eliminate No Preemption
Preempt resources from the process when resources required by other high priority processes.
Deadlock Avoidance
Banker’s Algorithm
Bankers’s Algorithm is resource allocation and deadlock avoidance algorithm which test all the request
made by processes for resources, it checks for the safe state, if after granting request system remains in
the safe state it allows the request and if there is no safe state it doesn’t allow the request made by the
process.
1. If the request made by the process is less than equal to max need to that process.
2. If the request made by the process is less than equal to the freely available resource in the
system.
Example:
ABCD
6576
ABCD
3112
ABCD
P1 1 2 2 1
P2 1 0 3 3
P3 1 2 1 0
ABCD
P1 3 3 2 2
P2 1 2 3 4
P3 1 3 5 0
ABCD
P1 2 1 0 1
P2 0 2 0 1
P3 0 1 4 0
Deadlock Detection And Recovery
In the previous post, we have discussed Deadlock Prevention and Avoidance. In this post, Deadlock
Detection and Recovery technique to handle deadlock is discussed.
Deadlock Detection
In the above diagram, resource 1 and resource 2 have single instances. There is a cycle R1 → P1 → R2 →
P2. So, Deadlock is Confirmed.
Deadlock Recovery
A traditional operating system such as Windows doesn’t deal with deadlock recovery as it is time and
space consuming process. Real-time operating systems use Deadlock recovery.
Recovery method
1. Killing the process: killing all the process involved in the deadlock. Killing process one by one.
After killing each process check for deadlock again keep repeating the process till system recover
from deadlock.
2. Resource Preemption: Resources are preempted from the processes involved in the deadlock,
preempted resources are allocated to other processes so that there is a possibility of recovering
the system from deadlock. In this case, the system goes into starvation.
Banker’s Algorithm in Operating System
Bankers algorithm is an algorithm which is used for deadlock avoidance and resource allocation. It was
established by Edsger Dijkstra. The reason behind the name ‘banker’s algorithm’ is that it is mostly used
in banking systems. Banker’s algorithm helps to identify whether a loan should be provided or not.
3. In the banker’s algorithm, various resources are maintained that fulfill the needs of at least one
client.
5. In the banker’s algorithm, if the process gets all the needed resources, then it is must to return
the resources in a restricted period.
1. During processing, it does not permit a process to change its maximum need.
2. All the processes should know in advance about the maximum resource needs.
3. Banker’s algorithm permits the requests to be provided in constrained time, but for one year
which is a fixed period.
There are four types of data structures used to implement Banker’s algorithm:
1. Available
2. Max
3. Allocation
4. Need
1. Available
Available is a one-dimensional array. The size of the array is ‘m’ which is used to determine the
number of available resources of each kind.
2. Max
Max is a two-dimensional array. The size of the array is ‘n*m’. The max data structure is used to
determine the maximum number of resources that each process requests.
Max[i, j] = k indicates that ‘Pi’ can demand or request maximum ‘k’ instances of ‘Rj’ resource
type.
3. Allocation
Allocation is a two-dimensional array, and the size of the array is ‘n*m’, which is used to define
the number of resources of each kind presently assigned to each process.
Allocation[i, j] = k indicates that currently process ‘Pi’ is assigned ‘k’ instances of ‘Rj’ resource
type.
4. Need
Need is a two-dimensional array. The size of the array is ‘n*m’. Need is used to define the
remaining resources which are required for each process.
Need [i, j] = k indicates that for the execution of ‘Pi’ process, presently ‘k’ instances of resource
type ‘Rj’ are required.
1. Safety algorithm
1. Safety algorithm
The safety algorithm is used to check the system state means whether the system is in a safe state or
not.
Let Requesti is the request array for the process ‘Pi’. Requesti [j] = k means ‘Pi’ process needs k instances
of Rj resource type. At the time when a process Pi demands resources, then we follow the below steps.
P0 1 1 2 4 3 3 2 1 0
P1 2 1 2 3 2 2
P2 4 0 1 9 0 2
P3 0 2 0 7 5 3
P4 1 1 2 1 1 2
Solution:
1. Content of the need matrix can be calculated by using the below formula
⸫⸫
2. Now, we check for a safe state
Safe sequence:
Available = (2, 1, 0)
Available = (2, 1, 0)
Request of P1 is granted.
⸫ Available = Available +Allocation
= (2, 1, 0) + (2, 1, 2)
Available = (4, 2, 2)
Available = (4, 2, 2)
Available = (4, 2, 2)
Request of P4 is granted.
= (4, 2, 2) + (1, 1, 2)
Available = (5, 3, 4)
Request of P2 is granted.
= (5, 3, 4) + (4, 0, 1)
Available = (9, 3, 5)
Request of P3 is granted.
⸫ Available = Available +Allocation
= Available (9, 5, 5)
The system allocates all the needed resources to each process. So, we can say that system is in a safe
state.
= [8 5 7] + [2 1 0] = [10 6 7]
The banker’s algorithm is a resource allocation and deadlock avoidance algorithm that tests for safety by
simulating the allocation for predetermined maximum possible amounts of all resources, then makes an
“s-state” check to test for possible activities, before deciding whether allocation should be allowed to
continue.
In other words, the bank would never allocate its money in such a way that it can no longer satisfy the
needs of all its customers. The bank would try to be in safe state always.
Let ‘n’ be the number of processes in the system and ‘m’ be the number of resources types.
Available :
It is a 1-d array of size ‘m’ indicating the number of available resources of each type.
Max :
It is a 2-d array of size ‘n*m’ that defines the maximum demand of each process in a system.
Max[ i, j ] = k means process Pi may request at most ‘k’ instances of resource type Rj.
Allocation :
It is a 2-d array of size ‘n*m’ that defines the number of resources of each type currently
allocated to each process.
Need :
It is a 2-d array of size ‘n*m’ that indicates the remaining resource need of each process.
Allocationi specifies the resources currently allocated to process Pi and Needi specifies the additional
resources that process Pi may still request to complete its task.
Safety Algorithm
The algorithm for finding out whether or not a system is in a safe state can be described as follows:
1) Let Work and Finish be vectors of length ‘m’ and ‘n’ respectively.
Initialize: Work = Available
Finish[i] = false; for i=1, 2, 3, 4….n
2) Find an i such that both
a) Finish[i] = false
b) Needi <= Work
if no such i exists goto step (4)
Resource-Request Algorithm
Let Requesti be the request array for process Pi. Requesti [j] = k means process Pi wants k instances of
resource type Rj. When a request for resources is made by process Pi, the following actions are taken:
3) Have the system pretend to have allocated the requested resources to process Pi by modifying the
state as
follows:
Available = Available – Requesti
Allocationi = Allocationi + Requesti
Needi = Needi– Requesti
Example:
Considering a system with five processes P0 through P4 and three resources of type A, B, C. Resource
type A has 10 instances, B has 5 instances and type C has 7 instances. Suppose at time t 0 following
snapshot of the system has been taken:
Question1. What will be the content of the Need matrix?
Question2. Is the system in a safe state? If Yes, then what is the safe sequence?
C++
C
Java
Python3
C#
filter_none
edit
play_arrow
brightness_4
// Banker's Algorithm
#include <iostream>
using namespace std;
int main()
int n, m, i, j, k;
n = 5; // Number of processes
m = 3; // Number of resources
{ 2, 0, 0 }, // P1
{ 3, 0, 2 }, // P2
{ 2, 1, 1 }, // P3
{ 0, 0, 2 } }; // P4
{ 3, 2, 2 }, // P1
{ 9, 0, 2 }, // P2
{ 2, 2, 2 }, // P3
{ 4, 3, 3 } }; // P4
f[k] = 0;
int need[n][m];
for (i = 0; i < n; i++) {
int y = 0;
if (f[i] == 0) {
int flag = 0;
flag = 1;
break;
if (flag == 0) {
ans[ind++] = i;
avail[y] += alloc[i][y];
f[i] = 1;
return (0);
Output:
Banker's Algorithm is used majorly in the banking system to avoid deadlock. It helps you to identify
whether a loan will be given or not.
This algorithm is used to test for safely simulating the allocation for determining the maximum amount
available for all resources. It also checks for all the possible activities before determining whether
allocation should be continued or not.
For example, there are X number of account holders of a specific bank, and the total amount of money
of their accounts is G.
When the bank processes a car loan, the software system subtracts the amount of loan granted for
purchasing a car from the total money ( G+ Fixed deposit + Monthly Income Scheme + Gold, etc.) that
the bank has.
It also checks that the difference is more than or not G. It only processes the car loan when the bank has
sufficient money even if all account holders withdraw the money G simultaneously.
Available
Max
Allocation
Need
5 Pen drives
2 Printers
4 Scanners
3 Hard disks
Here, we have created a vector representing total resources: Available = (5, 2, 4, 3).
Assume there are four processes. The available resources are already allocated as per the matrix table
below.
P 2 0 1
Q 0 1 0
R 1 0 1
S 1 1 0
Process Name Pen Drives Printer Scann
Total 4 2 2
We also create a Matrix to display the number of each resource required for all the processes. This
matrix is called Need=(3,0,2,2)
P 1 1 0
Q 0 1 1
R 2 1 0
S 0 0 1
Available=Available- Allocated
= (5, 2, 4, 3) -(4, 2, 2, 3)
=(1, 0, 2, 0)
Resource request algorithm enables you to represent the system behavior when a specific process
makes a resource request.
Step 1) When a total requested instance of all resources is lesser than the process, move to step 2.
Step 2) When a requested instance of each and every resource type is lesser compared to the available
resources of each type, it will be processed to the next step. Otherwise, the process requires to wait
because of the unavailability of sufficient resources.
This final step is performed because the system needs to assume that resources have been allocated. So
that there should be less resources available after allocation.
Characteristics of Banker's Algorithm
Keep many resources that satisfy the requirement of at least one client
Whenever a process gets all its resources, it needs to return them in a restricted period.
Does not allow the process to change its Maximum need while processing
It allows all requests to be granted in restricted time, but one year is a fixed period for that.
All processes must know and state their maximum resource needs in advance.
Summary:
Banker's algorithm is used majorly in the banking system to avoid deadlock. It helps you to
identify whether a loan will be given or not.
Resource request algorithm enables you to represent the system behavior when a specific
process makes a resource request.
Banker's algorithm keeps many resources that satisfy the requirement of at least one client
The biggest drawback of banker's algorithm this that it does not allow the process to change its
Maximum need while processing.
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.
Let us assume that there are n processes and m resource types. Some data structures that are used to
implement the banker's algorithm are:
1. 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 R(j).
2. 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 P(i) can request atmost k instances of resource type R(j).
3. 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 P(i) is currently allocated k instances of resource type R(j).
4. Need
It is an n x m matrix which indicates the remaining resource needs of each process. If Need[i][j] = k, then
process P(i) may need k more instances of resource type R(j) to complete its task.
This describes the behavior of the system when a process makes a resource request in the form of a
request matrix. The steps are:
1. If number of requested instances of each resource is less than the need (which was declared
previously by the process), go to step 2.
2. If number of requested instances of each resource type is less than the available resources of
each type, go to step 3. If not, the process has to wait because sufficient resources are not
available yet.
3. Now, assume that the resources have been allocated. Accordingly do,
This step is done because the system needs to assume that resources have been allocated. So there will
be less resources available after allocation. The number of allocated instances will increase. The need of
the resources by the process will reduce. That's what is represented by the above three operations.
After completing the above three steps, check if the system is in safe state by applying the safety
algorithm. If it is in safe state, proceed to allocate the requested resources. Else, the process has to wait
longer.
Safety Algorithm
2. Work = Available
This means, initially, no process has finished and the number of available resources is represented by
the Available array.
5. Finish[i] ==false
It means, we need to find an unfinished process whose need can be satisfied by the available resources.
If no such process exists, just go to step 4.
9. Finish[i] = true;
Go to step 2.
When an unfinished process is found, then the resources are allocated and the process is marked
finished. And then, the loop is repeated to check the same for all other processes.
10. If Finish[i] == true for all i, then the system is in a safe state.
That means if all processes are finished, then the system is in safe state.