Deadlock detection is a critical aspect of operating system design and process management. This comprehensive guide explores the intricacies of deadlock detection algorithms, with a special focus on Banker’s Algorithm. We’ll examine both theoretical concepts and practical implementations, providing working code examples and detailed explanations.
A deadlock is a situation where a set of processes are blocked because each process is holding a resource and waiting for another resource acquired by some other process.
-- Transaction 1:
UPDATE Account_A SET balance = balance - 100;
UPDATE Account_B SET balance = balance + 100;
-- Transaction 2:
UPDATE Account_B SET balance = balance - 50;
UPDATE Account_A SET balance = balance + 50;
// Process 1:
lock(file1);
lock(file2);
// Process 2:
lock(file2);
lock(file1);
A directed graph that represents the relationship between processes and resources.
#include <stdio.h>
#include <stdlib.h>
#define MAX_PROCESSES 10
#define MAX_RESOURCES 10
typedef struct {
int allocation[MAX_PROCESSES][MAX_RESOURCES];
int request[MAX_PROCESSES][MAX_RESOURCES];
int available[MAX_RESOURCES];
int processes;
int resources;
} ResourceGraph;
// Initialize Resource Graph
void initializeRAG(ResourceGraph *graph, int p, int r) {
graph->processes = p;
graph->resources = r;
// Initialize matrices
for(int i = 0; i < p; i++) {
for(int j = 0; j < r; j++) {
graph->allocation[i][j] = 0;
graph->request[i][j] = 0;
}
}
// Initialize available resources
for(int i = 0; i < r; i++) {
graph->available[i] = 0;
}
}
// Check for cycle in the graph (deadlock detection)
int detectDeadlock(ResourceGraph *graph) {
int work[MAX_RESOURCES];
int finish[MAX_PROCESSES] = {0};
int deadlock = 0;
// Initialize work array
for(int i = 0; i < graph->resources; i++) {
work[i] = graph->available[i];
}
// Find an unfinished process that can be completed
int found;
do {
found = 0;
for(int i = 0; i < graph->processes; i++) {
if(!finish[i]) {
int canComplete = 1;
// Check if process can complete with available resources
for(int j = 0; j < graph->resources; j++) {
if(graph->request[i][j] > work[j]) {
canComplete = 0;
break;
}
}
if(canComplete) {
// Process can complete, release its resources
for(int j = 0; j < graph->resources; j++) {
work[j] += graph->allocation[i][j];
}
finish[i] = 1;
found = 1;
}
}
}
} while(found);
// Check if all processes finished
for(int i = 0; i < graph->processes; i++) {
if(!finish[i]) {
deadlock = 1;
break;
}
}
return deadlock;
}
int main() {
ResourceGraph graph;
int p = 5, r = 3;
initializeRAG(&graph, p, r);
// Example resource allocation
graph.allocation[0][0] = 1; graph.allocation[0][1] = 0; graph.allocation[0][2] = 0;
graph.allocation[1][0] = 2; graph.allocation[1][1] = 0; graph.allocation[1][2] = 2;
graph.allocation[2][0] = 3; graph.allocation[2][1] = 0; graph.allocation[2][2] = 2;
graph.allocation[3][0] = 0; graph.allocation[3][1] = 1; graph.allocation[3][2] = 0;
graph.allocation[4][0] = 0; graph.allocation[4][1] = 0; graph.allocation[4][2] = 2;
// Example resource requests
graph.request[0][0] = 0; graph.request[0][1] = 0; graph.request[0][2] = 0;
graph.request[1][0] = 2; graph.request[1][1] = 0; graph.request[1][2] = 2;
graph.request[2][0] = 0; graph.request[2][1] = 0; graph.request[2][2] = 0;
graph.request[3][0] = 1; graph.request[3][1] = 0; graph.request[3][2] = 0;
graph.request[4][0] = 0; graph.request[4][1] = 0; graph.request[4][2] = 2;
// Available resources
graph.available[0] = 3;
graph.available[1] = 3;
graph.available[2] = 2;
if(detectDeadlock(&graph)) {
printf("Deadlock detected!\n");
} else {
printf("No deadlock detected.\n");
}
return 0;
}
A simplified version of RAG showing only process dependencies.
#include <stdio.h>
#include <stdlib.h>
#define MAX_PROCESSES 10
typedef struct {
int matrix[MAX_PROCESSES][MAX_PROCESSES];
int processes;
} WaitForGraph;
// Initialize Wait-for Graph
void initializeWFG(WaitForGraph *graph, int p) {
graph->processes = p;
for(int i = 0; i < p; i++) {
for(int j = 0; j < p; j++) {
graph->matrix[i][j] = 0;
}
}
}
// Detect cycle using DFS
int detectCycleDFS(WaitForGraph *graph, int vertex, int visited[], int recStack[]) {
if(!visited[vertex]) {
visited[vertex] = 1;
recStack[vertex] = 1;
for(int i = 0; i < graph->processes; i++) {
if(graph->matrix[vertex][i]) {
if(!visited[i] && detectCycleDFS(graph, i, visited, recStack)) {
return 1;
} else if(recStack[i]) {
return 1;
}
}
}
}
recStack[vertex] = 0;
return 0;
}
// Detect deadlock in Wait-for Graph
int detectDeadlock(WaitForGraph *graph) {
int visited[MAX_PROCESSES] = {0};
int recStack[MAX_PROCESSES] = {0};
for(int i = 0; i < graph->processes; i++) {
if(detectCycleDFS(graph, i, visited, recStack)) {
return 1;
}
}
return 0;
}
int main() {
WaitForGraph graph;
int p = 4;
initializeWFG(&graph, p);
// Example: Process 0 waiting for Process 1
graph.matrix[0][1] = 1;
// Process 1 waiting for Process 2
graph.matrix[1][2] = 1;
// Process 2 waiting for Process 3
graph.matrix[2][3] = 1;
// Process 3 waiting for Process 0 (creates a cycle)
graph.matrix[3][0] = 1;
if(detectDeadlock(&graph)) {
printf("Deadlock detected!\n");
} else {
printf("No deadlock detected.\n");
}
return 0;
}
The safety algorithm determines if a system is in a safe state.
#include <stdio.h>
#include <stdlib.h>
#define MAX_PROCESSES 10
#define MAX_RESOURCES 10
typedef struct {
int allocation[MAX_PROCESSES][MAX_RESOURCES];
int max[MAX_PROCESSES][MAX_RESOURCES];
int available[MAX_RESOURCES];
int need[MAX_PROCESSES][MAX_RESOURCES];
int processes;
int resources;
} BankersState;
// Initialize Banker's Algorithm state
void initializeBankers(BankersState *state, int p, int r) {
state->processes = p;
state->resources = r;
for(int i = 0; i < p; i++) {
for(int j = 0; j < r; j++) {
state->allocation[i][j] = 0;
state->max[i][j] = 0;
state->need[i][j] = 0;
}
}
for(int i = 0; i < r; i++) {
state->available[i] = 0;
}
}
// Calculate need matrix
void calculateNeed(BankersState *state) {
for(int i = 0; i < state->processes; i++) {
for(int j = 0; j < state->resources; j++) {
state->need[i][j] = state->max[i][j] - state->allocation[i][j];
}
}
}
// Check if system is in safe state
int isSafe(BankersState *state) {
int work[MAX_RESOURCES];
int finish[MAX_PROCESSES] = {0};
int safeSequence[MAX_PROCESSES];
int count = 0;
// Initialize work array
for(int i = 0; i < state->resources; i++) {
work[i] = state->available[i];
}
// Find a process that can be completed
while(count < state->processes) {
int found = 0;
for(int i = 0; i < state->processes; i++) {
if(!finish[i]) {
int canAllocate = 1;
for(int j = 0; j < state->resources; j++) {
if(state->need[i][j] > work[j]) {
canAllocate = 0;
break;
}
}
if(canAllocate) {
for(int j = 0; j < state->resources; j++) {
work[j] += state->allocation[i][j];
}
safeSequence[count] = i;
finish[i] = 1;
count++;
found = 1;
}
}
}
if(!found) {
printf("System is not in safe state.\n");
return 0;
}
}
printf("System is in safe state.\nSafe sequence: ");
for(int i = 0; i < state->processes; i++) {
printf("%d ", safeSequence[i]);
}
printf("\n");
return 1;
}
int main() {
BankersState state;
int p = 5, r = 3;
initializeBankers(&state, p, r);
// Example allocation matrix
int allocation[5][3] = {
{0, 1, 0},
{2, 0, 0},
{3, 0, 2},
{2, 1, 1},
{0, 0, 2}
};
// Example max matrix
int max[5][3] = {
{7, 5, 3},
{3, 2, 2},
{9, 0, 2},
{2, 2, 2},
{4, 3, 3}
};
// Available resources
state.available[0] = 3;
state.available[1] = 3;
state.available[2] = 2;
// Copy matrices
for(int i = 0; i < p; i++) {
for(int j = 0; j < r; j++) {
state.allocation[i][j] = allocation[i][j];
state.max[i][j] = max[i][j];
}
}
calculateNeed(&state);
isSafe(&state);
return 0;
}
Banker’s Algorithm with both Safety and Resource Request algorithm:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_PROCESSES 10
#define MAX_RESOURCES 10
typedef struct {
int processes;
int resources;
// Main matrices
int allocation[MAX_PROCESSES][MAX_RESOURCES];
int max[MAX_PROCESSES][MAX_RESOURCES];
int need[MAX_PROCESSES][MAX_RESOURCES];
int available[MAX_RESOURCES];
// For tracking safe sequence
int safe_sequence[MAX_PROCESSES];
bool finished[MAX_PROCESSES];
} BankersSystem;
// Initialize the Banker's Algorithm system
void initializeBankers(BankersSystem *bs, int p, int r) {
bs->processes = p;
bs->resources = r;
// Initialize all matrices to 0
for(int i = 0; i < p; i++) {
for(int j = 0; j < r; j++) {
bs->allocation[i][j] = 0;
bs->max[i][j] = 0;
bs->need[i][j] = 0;
}
bs->finished[i] = false;
}
for(int i = 0; i < r; i++) {
bs->available[i] = 0;
}
}
// Calculate Need Matrix
void calculateNeed(BankersSystem *bs) {
for(int i = 0; i < bs->processes; i++) {
for(int j = 0; j < bs->resources; j++) {
bs->need[i][j] = bs->max[i][j] - bs->allocation[i][j];
}
}
}
// Check if resources can be allocated to a process
bool canAllocateResources(BankersSystem *bs, int process_id, int work[]) {
for(int i = 0; i < bs->resources; i++) {
if(bs->need[process_id][i] > work[i])
return false;
}
return true;
}
// Safety Algorithm
bool isSafeState(BankersSystem *bs) {
int work[MAX_RESOURCES];
int completed = 0;
// Initialize work = available
for(int i = 0; i < bs->resources; i++) {
work[i] = bs->available[i];
}
// Reset finished array
for(int i = 0; i < bs->processes; i++) {
bs->finished[i] = false;
}
// Find a safe sequence
while(completed < bs->processes) {
bool found = false;
for(int i = 0; i < bs->processes; i++) {
if(!bs->finished[i] && canAllocateResources(bs, i, work)) {
// Add process to safe sequence
bs->safe_sequence[completed] = i;
// Add allocated resources back to work
for(int j = 0; j < bs->resources; j++) {
work[j] += bs->allocation[i][j];
}
bs->finished[i] = true;
completed++;
found = true;
}
}
if(!found) {
printf("System is not in safe state!\n");
return false;
}
}
printf("System is in safe state.\nSafe sequence: ");
for(int i = 0; i < bs->processes; i++) {
printf("P%d ", bs->safe_sequence[i]);
}
printf("\n");
return true;
}
// Resource Request Algorithm
bool requestResources(BankersSystem *bs, int process_id, int request[]) {
// Check if request is valid
for(int i = 0; i < bs->resources; i++) {
if(request[i] > bs->need[process_id][i]) {
printf("Error: Process has exceeded its maximum claim!\n");
return false;
}
if(request[i] > bs->available[i]) {
printf("Error: Resources not available!\n");
return false;
}
}
// Try to allocate resources
for(int i = 0; i < bs->resources; i++) {
bs->available[i] -= request[i];
bs->allocation[process_id][i] += request[i];
bs->need[process_id][i] -= request[i];
}
// Check if resulting state is safe
if(isSafeState(bs)) {
printf("Resources allocated successfully to Process %d\n", process_id);
return true;
}
// If not safe, rollback changes
for(int i = 0; i < bs->resources; i++) {
bs->available[i] += request[i];
bs->allocation[process_id][i] -= request[i];
bs->need[process_id][i] += request[i];
}
printf("Request denied: Would lead to unsafe state!\n");
return false;
}
// Print current system state
void printSystemState(BankersSystem *bs) {
printf("\nCurrent System State:\n");
printf("\nAllocation Matrix:\n");
for(int i = 0; i < bs->processes; i++) {
for(int j = 0; j < bs->resources; j++) {
printf("%d ", bs->allocation[i][j]);
}
printf("\n");
}
printf("\nMax Matrix:\n");
for(int i = 0; i < bs->processes; i++) {
for(int j = 0; j < bs->resources; j++) {
printf("%d ", bs->max[i][j]);
}
printf("\n");
}
printf("\nNeed Matrix:\n");
for(int i = 0; i < bs->processes; i++) {
for(int j = 0; j < bs->resources; j++) {
printf("%d ", bs->need[i][j]);
}
printf("\n");
}
printf("\nAvailable Resources:\n");
for(int i = 0; i < bs->resources; i++) {
printf("%d ", bs->available[i]);
}
printf("\n");
}
int main() {
BankersSystem bs;
int processes = 5;
int resources = 3;
initializeBankers(&bs, processes, resources);
// Initialize Available Resources
bs.available[0] = 3;
bs.available[1] = 3;
bs.available[2] = 2;
// Initialize Allocation Matrix
int allocation[5][3] = {
{0, 1, 0},
{2, 0, 0},
{3, 0, 2},
{2, 1, 1},
{0, 0, 2}
};
// Initialize Maximum Matrix
int max[5][3] = {
{7, 5, 3},
{3, 2, 2},
{9, 0, 2},
{2, 2, 2},
{4, 3, 3}
};
// Set up initial state
for(int i = 0; i < processes; i++) {
for(int j = 0; j < resources; j++) {
bs.allocation[i][j] = allocation[i][j];
bs.max[i][j] = max[i][j];
}
}
calculateNeed(&bs);
printSystemState(&bs);
// Check if system is in safe state
printf("\nChecking initial state safety...\n");
isSafeState(&bs);
// Try to request resources
printf("\nTesting resource request...\n");
int request[3] = {1, 0, 2};
requestResources(&bs, 1, request);
return 0;
}
Performance metrics for different deadlock detection algorithms:
| Algorithm | Time Complexity | Space Complexity | |—|—|—| | Resource Allocation Graph | O(P + R) | O(P * R) | | Wait-for Graph | O(P²) | O(P²) | | Banker’s Algorithm | O(P² * R) | O(P * R) | (P = processes, R = resources)
#include <stdio.h>
#include <stdlib.h>
#define MAX_PROCESSES 10
#define MAX_RESOURCES 10
typedef struct {
int pid;
int priority;
int age;
int resources_held[MAX_RESOURCES];
} Process;
typedef struct {
Process processes[MAX_PROCESSES];
int num_processes;
int num_resources;
} DeadlockRecovery;
// Initialize recovery system
void initializeRecovery(DeadlockRecovery *dr, int num_proc, int num_res) {
dr->num_processes = num_proc;
dr->num_resources = num_res;
for(int i = 0; i < num_proc; i++) {
dr->processes[i].pid = i;
dr->processes[i].priority = rand() % 10; // 0-9 priority
dr->processes[i].age = 0;
for(int j = 0; j < num_res; j++) {
dr->processes[i].resources_held[j] = 0;
}
}
}
// Select victim process for termination
int selectVictimProcess(DeadlockRecovery *dr) {
int victim = -1;
int lowest_priority = 999;
for(int i = 0; i < dr->num_processes; i++) {
int current_priority = dr->processes[i].priority - (dr->processes[i].age / 10);
if(current_priority < lowest_priority) {
lowest_priority = current_priority;
victim = i;
}
}
return victim;
}
// Release resources held by terminated process
void releaseResources(DeadlockRecovery *dr, int victim) {
printf("Releasing resources held by process %d\n", victim);
for(int i = 0; i < dr->num_resources; i++) {
if(dr->processes[victim].resources_held[i] > 0) {
printf("Released resource %d\n", i);
dr->processes[victim].resources_held[i] = 0;
}
}
}
// Handle deadlock recovery
void recoverFromDeadlock(DeadlockRecovery *dr) {
int victim = selectVictimProcess(dr);
if(victim != -1) {
printf("Selected process %d for termination\n", victim);
releaseResources(dr, victim);
// Age remaining processes
for(int i = 0; i < dr->num_processes; i++) {
if(i != victim) {
dr->processes[i].age++;
}
}
}
}
int main() {
DeadlockRecovery dr;
initializeRecovery(&dr, 5, 3);
// Simulate resource allocation
dr.processes[0].resources_held[0] = 1;
dr.processes[1].resources_held[1] = 1;
dr.processes[2].resources_held[2] = 1;
// Simulate deadlock detection and recovery
printf("Simulating deadlock recovery...\n");
recoverFromDeadlock(&dr);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#define MAX_RESOURCES 10
typedef struct {
int resource_id;
int priority;
int available;
} Resource;
typedef struct {
Resource resources[MAX_RESOURCES];
int num_resources;
} ResourceOrderingSystem;
// Initialize resource ordering system
void initializeResourceOrdering(ResourceOrderingSystem *ros, int num_res) {
ros->num_resources = num_res;
for(int i = 0; i < num_res; i++) {
ros->resources[i].resource_id = i;
ros->resources[i].priority = i; // Higher number = higher priority
ros->resources[i].available = 1;
}
}
// Check if resource request follows ordering
int isValidResourceRequest(ResourceOrderingSystem *ros, int current_resource, int requested_resource) {
if(current_resource == -1) return 1; // First resource request
return ros->resources[current_resource].priority < ros->resources[requested_resource].priority;
}
// Request resource
int requestResource(ResourceOrderingSystem *ros, int process_id, int current_resource, int requested_resource) {
if(!isValidResourceRequest(ros, current_resource, requested_resource)) {
printf("Invalid resource request order! Must request resources in increasing priority.\n");
return 0;
}
if(!ros->resources[requested_resource].available) {
printf("Resource %d not available\n", requested_resource);
return 0;
}
ros->resources[requested_resource].available = 0;
printf("Resource %d allocated to process %d\n", requested_resource, process_id);
return 1;
}
int main() {
ResourceOrderingSystem ros;
initializeResourceOrdering(&ros, 5);
// Simulate resource requests
int process_id = 1;
int current_resource = -1;
// Valid resource ordering
if(requestResource(&ros, process_id, current_resource, 0)) {
current_resource = 0;
}
if(requestResource(&ros, process_id, current_resource, 2)) {
current_resource = 2;
}
// Invalid resource ordering (will fail)
requestResource(&ros, process_id, current_resource, 1);
return 0;
}
Deadlock detection and prevention remain crucial aspects of operating system design. The choice of algorithm depends on system requirements, scale, and performance constraints. Banker’s Algorithm, while conservative, provides a safe approach to resource allocation, while other detection methods offer different trade-offs between accuracy and performance.
Further Reading:
This concludes our Day 14. Day 15 will cover Memory Management Overview, focusing on memory hierarchy and related concepts.