To avoid deadlock, there are two techniques. We will discuss both these techniques to see how we can avoid deadlock. Note that the deadlock prevention methods need a lot of processing power and can be complicated. So, try balancing between the requirement to allocate resources and the avoidance of deadlocks.
Resource Allocation Graph (RAG)
The very first technique to avoid deadlock is the resource allocation graph (RAG). It is a type of directed graph that is used to show how resources are allocated in a system. RAG is also a representation of the system’s state. Every process, every resource that has been assigned to it, and every resource that each process has requested are all included in the graph.
In RAG, the resources and processes are represented by nodes and resource allocation and requests are represented by edges. The resources in RAG can be allocated using two methods: Dynamic and static allocation.
In the dynamic allocation method, the processes are allocated dynamically in real-time as required by the OS to reduce the chances of deadlock.
In the static allocation method, the processes are allocated using the scheduled time but not in real time. This method is not recommended as it reduces resource utilisation and makes inflexibility.
An example of understanding the resource allocation graph (RAG) is given below.
Banker’s Algorithm
To understand the banker’s algorithm, we need to understand two concepts called safe state and unsafe state.
Safe and Unsafe State
The idea of safe and unsafe states is essential to avoiding deadlocks. If there is a sequence of resource allocations that permits all processes to complete without creating a stalemate, then that state is safe. In a safe state, the OS allocates the maximum resources of all processes.
A state where there is no such sequence and deadlock is possible is considered an unsafe state. In an unsafe state, the OS is not able to prevent the processes from requesting CPU resources.
What is a Banker’s Algorithm?
A banker’s algorithm uses the concept of a safe state where it checks for deadlock avoidance. This algorithm determines whether a system is in a safe state or unsafe state by simulating the allocation of resources (maximum available) using techniques like RAG.
The banker’s algorithm evaluates every resource request made by processes, looks for the safe state, and grants requests only if the system is still in the safe state. If the system is not in a safe state, it denies requests made by processes. Let’s see how a banker’s algorithm works with the help of an example.
Example:
Consider three resources (R1, R2, R3) with the processes P1, P2, and P3. The available resources are 3, 3, and 2. Check whether the system is in a safe state or unsafe state.
Given,
Max = [[7, 5, 3], [3, 2, 2], [9, 0, 2]]
Allocation = [[0, 1, 0], [2, 0, 0], [3, 0, 2]]
We are given the max, allocation, and available resources. Now to check for a safe or unsafe state, we have to first check how much resources each process needs. Therefore, we calculate the Need using the formula: Need = Max – Allocation.
Need = [[7, 4, 3], [1, 2, 2], [6, 0, 0]]
So, now we have the need for each process (i.e., [[7, 4, 3], [1, 2, 2], [6, 0, 0]]) and the available resources (3, 3, 2). So, we will now allocate the resources in order of the available resources.
As per the algorithm,
- Initialize Work and Finish:
Work = Available = [3, 3, 2]
Finish = [False, False, False]
- Find a Process that Can Be Satisfied:
Check if there is a process whose Need <= Work and is not finished.
Iteration 1:
Check which process Need <= work.
P1: Need = [7, 4, 3], which is not <= Work = [3, 3, 2]
P2: Need = [1, 2, 2], which is <= Work = [3, 3, 2]
Here, in the first iteration, P2 satisfies the condition; therefore, we allocate resources to P2 and mark it as finished. Now, our new work becomes: Work + Allocation[P2] = [3, 3, 2] + [2, 0, 0] = [5, 3, 2]
Iteration 2:
P1: Need = [7, 4, 3], which is not <= Work = [5, 3, 2]
P3: Need = [6, 0, 0], which is <= Work = [5, 3, 2]
Here, as no other process satisfies the condition, therefore we will check if we can satisfy any process with the updated Work:
Process P3: Need = [6, 0, 0], which is not <= Work = [5, 3, 2]
Since no other process satisfies the condition, we determine that the system is in an unsafe state. As the system is in an unsafe state, we also can’t create its Gantt Chart.
Let’s see a code example in C language for the implementation of Banker’s algorithm:
Code:
// import statements
#include <stdio.h>
#include <stdbool.h>
// no of processes and resources
#define P 4
#define R 3
// Check if the system is in a safe state
bool isSafe(int processes[], int avail[], int max[][R], int allot[][R]) {
int resNeed[P][R];
int completionStatus[P] = {0}; // To track finished processes
int safetySequence[P]; // Safe sequence
// Calculate the need matrix
for (int i = 0; i < P; i++)
for (int j = 0; j < R; j++)
resNeed[i][j] = max[i][j] – allot[i][j];
// Copy available resources to work
int work[R];
for (int i = 0; i < R; i++)
work[i] = avail[i];
int cnt = 0;
while (cnt < P) {
bool found = false;
for (int p = 0; p < P; p++) {
if (completionStatus[p] == 0) {
int j;
for (j = 0; j < R; j++)
if (resNeed[p][j] > work[j])
break;
if (j == R) {
for (int k = 0; k < R; k++)
work[k] += allot[p][k];
safetySequence[cnt++] = p;
completionStatus[p] = 1;
found = true;
}
}
}
if (found == false) {
printf(“System is in unsafe staten”);
return false;
}
}
printf(“System is in safe state.nSafe sequence is: “);
for (int i = 0; i < P; i++)
printf(“%d “, safetySequence[i]);
printf(“n”);
return true;
}
// Request resources for a process
bool reqRes(int process, int request[], int avail[], int max[][R], int allocatedRes[][R]) {
int need[P][R];
for (int i = 0; i < P; i++)
for (int j = 0; j < R; j++)
need[i][j] = max[i][j] – allocatedRes[i][j];
for (int i = 0; i < R; i++) {
if (request[i] > need[process][i]) {
printf(“Sorry, process has exceeded its max need.n”);
return false;
}
if (request[i] > avail[i]) {
printf(“resources not available.n”);
return false;
}
}
for (int i = 0; i < R; i++) {
avail[i] -= request[i];
allocatedRes[process][i] += request[i];
need[process][i] -= request[i];
}
if (isSafe((int[]) {0, 1, 2, 3, 4}, avail, max, allocatedRes)) {
printf(“Your request is granted.n”);
return true;
} else {
for (int i = 0; i < R; i++) {
avail[i] += request[i];
allocatedRes[process][i] -= request[i];
need[process][i] += request[i];
}
printf(“Request ungranted as it is unsafe state.n”);
return false;
}
}
// Main function
int main() {
int processes[] = {0, 1, 2, 3};
int avail[] = {4, 3, 2};
int max[P][R] = {
{7, 5, 3},
{9, 0, 2},
{3, 2, 2},
{2, 2, 2}
};
int allot[P][R] = {
{0, 1, 0},
{3, 0, 2},
{2, 0, 0},
{2, 1, 1}
};
isSafe(processes, avail, max, allot);
int req1[] = {1, 0, 2};
reqRes(1, req1, avail, max, allot);
return 0;
}
Output:
System is in safe state.
Safe sequence is: 2 3 0 1
Sorry, process has exceeded its max need.