Project Overview
The Banker's Algorithm is one of the most important deadlock avoidance algorithms in operating systems. In this project, you will build a complete simulation that takes a system state (allocation, maximum need, available resources) and determines whether the state is safe. You will also handle dynamic resource requests and decide whether granting them keeps the system safe.
This project helps you understand not just the algorithm mechanics, but WHY an operating system needs to look ahead before allocating resources.
Background Concepts
Think of the operating system as a bank. Processes are customers who have declared the maximum loan (resources) they might need. The bank has limited cash (available resources). The banker must ensure that even in the worst case — where every customer demands their maximum — there exists an order in which all customers can be satisfied. If no such order exists, the system is in an unsafe state and could deadlock.
Key Data Structures:
Available[m] — resources currently available (m resource types)Max[n][m] — maximum demand of each processAllocation[n][m] — resources currently held by each processNeed[n][m] — remaining need (Max - Allocation)
Implementation in C
Step 1: Define the System State
#include <stdio.h>
#include <stdbool.h>
#define MAX_PROCESSES 10
#define MAX_RESOURCES 10
int n, m; // number of processes and resource types
int available[MAX_RESOURCES];
int max_need[MAX_PROCESSES][MAX_RESOURCES];
int allocation[MAX_PROCESSES][MAX_RESOURCES];
int need[MAX_PROCESSES][MAX_RESOURCES];
Step 2: Implement the Safety Algorithm
The safety algorithm checks if a safe sequence exists from the current state:
bool is_safe(int safe_sequence[]) {
int work[MAX_RESOURCES];
bool finish[MAX_PROCESSES];
int count = 0;
// Initialize work = available, finish = false
for (int j = 0; j < m; j++)
work[j] = available[j];
for (int i = 0; i < n; i++)
finish[i] = false;
// Find a process whose need can be satisfied
while (count < n) {
bool found = false;
for (int i = 0; i < n; i++) {
if (!finish[i]) {
bool can_allocate = true;
for (int j = 0; j < m; j++) {
if (need[i][j] > work[j]) {
can_allocate = false;
break;
}
}
if (can_allocate) {
// Process i can finish — release its resources
for (int j = 0; j < m; j++)
work[j] += allocation[i][j];
finish[i] = true;
safe_sequence[count++] = i;
found = true;
}
}
}
if (!found) return false; // No safe sequence exists
}
return true;
}
Step 3: Resource Request Algorithm
When process Pi requests resources Request[m]:
bool request_resources(int process_id, int request[]) {
// Step 1: Check if request <= need
for (int j = 0; j < m; j++) {
if (request[j] > need[process_id][j]) {
printf("Error: Process requested more than its declared maximum.\n");
return false;
}
}
// Step 2: Check if request <= available
for (int j = 0; j < m; j++) {
if (request[j] > available[j]) {
printf("Process must wait — resources not available.\n");
return false;
}
}
// Step 3: Pretend to allocate and check safety
for (int j = 0; j < m; j++) {
available[j] -= request[j];
allocation[process_id][j] += request[j];
need[process_id][j] -= request[j];
}
int safe_sequence[MAX_PROCESSES];
if (is_safe(safe_sequence)) {
printf("Request granted. Safe sequence: ");
for (int i = 0; i < n; i++)
printf("P%d ", safe_sequence[i]);
printf("\n");
return true;
} else {
// Rollback — restore previous state
for (int j = 0; j < m; j++) {
available[j] += request[j];
allocation[process_id][j] -= request[j];
need[process_id][j] += request[j];
}
printf("Request denied — would lead to unsafe state.\n");
return false;
}
}
Step 4: Main Program with Input
int main() {
printf("Enter number of processes and resource types: ");
scanf("%d %d", &n, &m);
printf("Enter Available resources: ");
for (int j = 0; j < m; j++)
scanf("%d", &available[j]);
printf("Enter Max matrix:\n");
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
scanf("%d", &max_need[i][j]);
printf("Enter Allocation matrix:\n");
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) {
scanf("%d", &allocation[i][j]);
need[i][j] = max_need[i][j] - allocation[i][j];
}
// Check initial safety
int safe_sequence[MAX_PROCESSES];
if (is_safe(safe_sequence)) {
printf("System is in a SAFE state.\nSafe sequence: ");
for (int i = 0; i < n; i++)
printf("P%d ", safe_sequence[i]);
printf("\n");
} else {
printf("System is in an UNSAFE state!\n");
}
// Handle resource requests
int pid, request[MAX_RESOURCES];
printf("\nEnter process ID for resource request (-1 to exit): ");
scanf("%d", &pid);
while (pid != -1) {
printf("Enter request vector: ");
for (int j = 0; j < m; j++)
scanf("%d", &request[j]);
request_resources(pid, request);
printf("Enter process ID for resource request (-1 to exit): ");
scanf("%d", &pid);
}
return 0;
}
Sample Test Case
Input:
- 5 processes, 3 resource types
- Available: [3, 3, 2]
- Allocation: [[0,1,0], [2,0,0], [3,0,2], [2,1,1], [0,0,2]]
- Max: [[7,5,3], [3,2,2], [9,0,2], [2,2,2], [4,3,3]]
Expected Output: Safe sequence exists: P1 → P3 → P4 → P0 → P2
Project Extensions
- Visualization: Print the state matrices after each request (color-code safe/unsafe)
- Multiple resource requests: Allow batch requests and show state transitions
- GUI version: Use ncurses or a web frontend to visualize the algorithm
- Performance analysis: Measure how computation time grows with process/resource count
- Comparison: Implement detection algorithm alongside avoidance and compare overhead
Common Mistakes to Avoid
- Forgetting to rollback state when a request makes the system unsafe
- Confusing Need with Max (Need = Max - Allocation, not Max itself)
- Not resetting the
finish array for each safety check - Off-by-one errors in loop bounds
Learning Outcomes
After completing this project, you will understand:
- Why deadlock avoidance requires knowledge of maximum resource needs
- The computational cost of safety checking (O(n² × m) per request)
- Why Banker's Algorithm is impractical for large systems (overhead too high)
- The trade-off between safety (no deadlock) and resource utilization (conservative allocation)