Assignment 05: Deadlock Management
I. Objective & Theoretical Framework
In a multiprogramming environment, several processes may compete for a finite number of resources. If a process requests resources that are currently unavailable, it enters a waiting state. A deadlock occurs when a waiting process can never again change state because the resources it has requested are held by other waiting processes.
This laboratory focuses on Deadlock Avoidance using the Banker’s Algorithm. This approach requires the operating system to be given advanced information concerning which resources a process will request and use during its lifetime. The system uses this knowledge to evaluate whether granting a request will leave the system in a “Safe State.”
The Banker’s Algorithm is specifically applicable to a system with multiple instances of each resource type.
II.A. Motivation: Experiencing Deadlock in Action
Before implementing an avoidance algorithm, it is crucial to observe how easily deadlocks manifest in concurrent programming. You will compile and run the attached dining_philosophers_deadlock.c resource file.
This program simulates five philosophers sitting at a table, where each must pick up a left fork and a right fork to eat. Because every thread is programmed to pick up the left fork first, pause slightly, and then request the right fork, a circular wait is guaranteed.
Instructions to test the deadlock:
- Download the attached
dining_philosophers_deadlock.cfile. - Compile it using the POSIX threads library:
gcc dining_philosophers_deadlock.c -o deadlock_sim -pthread - Execute the binary:
./deadlock_sim - Observe the terminal output. The program will freeze indefinitely once every philosopher is holding their left fork and waiting for the right. You must manually terminate the process using
Ctrl+C.
This catastrophic system freeze is exactly what the Banker’s Algorithm is designed to prevent.
💻 Attached codes
II.B. Prerequisite Knowledge & Resources
- Vector and Matrix Arithmetic: The Banker’s Algorithm relies heavily on 1D arrays (Available resources) and 2D arrays/structures (Maximum, Allocation, and Need matrices).
- System State Definitions:
- Safe State: An execution sequence exists where all processes can finish executing without deadlocking.
- Unsafe State: A state that may lead to a deadlock.
- Formulas: You must dynamically calculate the Need matrix using the relation: $Need_{i,j} = Max_{i,j} - Allocation_{i,j}$
III. Starter Code & Partial Implementations
To manage the matrices cleanly, it is highly recommended to use an array of structures. The following starter code provides the data structure and the initial logic to compute the need matrix.
#include <stdio.h>
// Structure to hold resource data for a single process
struct process_node {
int all[10]; // Allocation
int max[10]; // Maximum demand
int need[10]; // Remaining need
int flag; // Visited/Finished status
};
int main() {
struct process_node p[10];
int avail[10], seq[10];
int n, r, i, j;
printf("Enter number of processes: ");
scanf("%d", &n);
printf("Enter number of resource types: ");
scanf("%d", &r);
// [Omitted: Code to scan Allocation and Max matrices from the user]
// Calculate the Need Matrix
for(i = 0; i < n; i++) {
for(j = 0; j < r; j++) {
p[i].need[j] = p[i].max[j] - p[i].all[j];
// Safety catch for invalid inputs
if(p[i].need[j] < 0) {
p[i].need[j] = 0;
}
}
p[i].flag = 0; // Initialize as unvisited
}
// [Insert Safety Algorithm Logic Here]
return 0;
}
IV. Step-by-Step Task List
Matrix Initialization: Complete the starter code by writing the
forloops to accept user input for the Allocation and Maximum matrices, as well as the initial Available resources array.The Safety Algorithm: Implement the core Banker’s logic. Iterate through all unvisited processes (
flag == 0). For each process, check if itsneedfor every resource type is $\le$ theavailresources.Simulate Execution: If a process can be executed, mark it as visited (
flag = 1), add it to your Safe Sequence array (seq), and release its allocated resources back into theavailpool ($Available = Available + Allocation$).State Output: Loop this process until all processes are visited (System is in a Safe State) or until you complete a full loop without being able to execute any process (System is in an Unsafe State). Print the Safe Sequence if one exists.
V. Common Pitfalls & Debugging Strategies
Infinite Loops: When searching for a safe process, if the system is in an unsafe state, your
whileloop might run forever. You must include a counter or a boolean trigger (e.g.,int g = 0;that turns to1if a process executes). If a full pass finishes and $g == 0$, you mustbreakthe loop and declare a deadlock.Element-wise Comparison: Do not try to compare arrays directly in C. You must iterate through every resource type $j$ for process $i$ to verify that $Need_{i,j} \le Available_j$ for all $j$. A single requested resource exceeding availability means the process must wait.
VI. Real-World Case Study
The principles of deadlock avoidance are foundational to distributed systems. In a cluster computing environment or a distributed database, multiple nodes often request locks on shared data tables concurrently. If these locks are not mathematically vetted for safety before being granted, a circular wait occurs, freezing the database. While modern operating systems rarely use the Banker’s algorithm for general processes due to the overhead of knowing “Max Demand” in advance, specialized distributed transaction managers and embedded avionics systems still rely heavily on graph-based avoidance algorithms derived directly from these concepts.
VII. Advanced Variant Tasks
Dynamic Resource Request Algorithm: Extend your program to handle dynamic requests. Prompt the user: “Enter New Request Details” (including PID and the requested resource vector).
First, check if the request exceeds the process’s declared Maximum.
Second, check if the request exceeds current Availability.
If both checks pass, pretend to allocate the resources, run the Safety Algorithm, and if the resulting state is safe, commit the allocation. If unsafe, rollback the allocation and deny the request.
Deadlock Recovery via Victim Preemption: Instead of avoiding deadlock, allow the system to reach an unsafe state. Write a routine that identifies the deadlock and forcefully preempts resources from a “victim” process to break the circular wait. Base your victim selection on a heuristic (e.g., terminate the process holding the fewest resources, or the one with the highest remaining need).
Timestamp-Based Prevention (Wait-Die/Wound-Wait): Simulate a distributed database deadlock prevention scheme. Assign a synthetic “timestamp” (integer) to each process. Implement the Wait-Die or Wound-Wait logic to dictate whether an older process preempts a younger one or waits for it when a resource conflict occurs.
Distributed Deadlock Detection Engine: Extend your Banker’s Algorithm simulation to act as a cluster monitor. Instead of static input, configure your program to accept a continuous stream of lock requests and releases from simulated remote nodes. Build a Resource Allocation Graph (RAG) in the background and write a cycle-detection algorithm to output a real-time alert:
DEADLOCK DETECTED: Nodes [X, Y, Z] involved in circular wait.Automated Graphviz RAG Generation
The Concept: Instead of just printing “Safe” or “Unsafe” to the terminal, require your Banker’s Algorithm (or your simulated deadlock code) to dynamically generate a Resource Allocation Graph (RAG).
The Task:
- Add a function to your C code that writes the current state of processes and resources to a
.dottext file using Graphviz syntax.- For example, your C code prints
P1 -> R1;(Request edge) andR2 -> P1;(Assignment edge) to the file based on the Need and Allocation matrices.- The Observation: You run your C program, pipe the
.dotfile into the open-source Graphviz tool (dot -Tpng rag.dot -o rag.png), and instantly generate a visual map of the system state. If a deadlock occurs, you will literally be able to see the circular wait rendered as a cycle on your screen.
VIII. Resources & Further Reading
- OSTEP - Concurrency Bugs: Read Chapter 32: Common Concurrency Problems (PDF) for a breakdown of the conditions required for a deadlock (Circular Wait, Hold-and-Wait, No Preemption, Mutual Exclusion).
- Edsger Dijkstra’s Original Paper: For a historical deep dive, search for Dijkstra’s original 1965 manuscript (EWD108) introducing the Banker’s Algorithm. It remains a masterclass in algorithmic design for distributed environments.