Module 2 · Unit 7 · Laboratory 6
Deadlock Analysis
This laboratory turns the deadlock you produced in Lab 5 into an object of systematic study. You will construct deadlocks deliberately, characterise them with resource allocation graphs, apply the four classical prevention techniques, implement the Banker's algorithm for avoidance, and build a detector and recovery mechanism. The laboratory closes by measuring how each technique constrains the workload and by reasoning about why real operating systems typically choose prevention through resource ordering over avoidance through Banker's algorithm.
01 Lab overview
| Course | COMP 3150 / CMIS 3150 — Operating Systems |
|---|---|
| Unit | Unit 7 — Deadlocks |
| Weight | 1.25% of the final grade (1/12 of the 15% laboratory component) |
| Estimated duration | 4–5 hours of hands-on work |
| Submission | One archive containing the C source files, the Makefile, the output logs, and a short lab report (see § 06) |
| Environment | Linux VM, gcc, make, valgrind, strace, optional graphviz for resource allocation graphs |
This lab contributes to U7.1 through U7.5, and to course learning outcomes CLO3 and CLO8.
02 Learning objectives
By the end of this laboratory you will be able to
- Explain the four necessary conditions for deadlock and demonstrate that each is independently necessary.
- Construct and interpret a resource allocation graph, and identify cycles and knots.
- Apply the four prevention techniques (mutual exclusion, hold-and-wait elimination, preemption, circular-wait breaking) and evaluate their cost.
- Implement the Banker's algorithm for deadlock avoidance and demonstrate its safe-state test.
- Implement a deadlock detector for a single-instance and a multiple-instance resource model.
- Compare prevention, avoidance, detection, and recovery, and select the appropriate strategy for a given workload.
03 Background
3.1 The four necessary conditions
Deadlock can occur if and only if all four of the following conditions hold simultaneously:
- Mutual exclusion. At least one resource is non-shareable — only one process may hold it at a time.
- Hold and wait. A process holding at least one resource is waiting to acquire additional resources held by other processes.
- No preemption. A resource can be released only voluntarily by the process holding it.
- Circular wait. There is a closed chain of processes, each waiting for a resource held by the next.
The four conditions are independently necessary: if any one is removed, deadlock becomes impossible. This is why every prevention technique targets exactly one of them.
3.2 Resource allocation graphs
A resource allocation graph has two node sets (processes P and resources R) and two edge sets: a request edge from a process to a resource, and an assignment edge from a resource to a process. If every resource has a single instance, a cycle in the graph is a necessary and sufficient condition for deadlock. If resources have multiple instances, a cycle is necessary but not sufficient — the deadlock depends on which instance a process is waiting for.
3.3 Banker's algorithm
The Banker's algorithm is a deadlock
avoidance technique. It maintains the
matrices Allocation, Max,
and the vector Available, and grants a
request only if the resulting state is
safe. A state is safe if there exists at
least one sequence in which every process can finish
using the currently available resources plus those
held by processes that have already finished. If no
such sequence exists, the request is denied (the
requesting process is blocked and retries later).
# Safe-state test (n processes, m resource types)
Work = Available; Finish[i] = false for all i
while there is an i with Finish[i]==false and Need[i] <= Work:
Work += Allocation[i]
Finish[i] = true
return true if Finish[i] == true for all i
3.4 Detection and recovery
Detection runs a cycle-detection algorithm (for single-instance resources) or the Banker's-style reduction (for multiple instances) periodically or when the system suspects deadlock. Recovery may involve process termination (kill all deadlocked processes, or kill them one at a time until the cycle is broken) or resource preemption (take a resource from one holder and give it to a waiter, rolling the holder back to a safe point if the system supports checkpoints).
04 Guided activities
Activity 4.1 — Constructing a deadlock deliberately
- Create the lab directory and the file
deadlock_basic.c:mkdir -p ~/comp3150/lab6 && cd ~/comp3150/lab6 - Write a program with two mutexes
AandBand two threads. Thread 1 locksA, sleeps briefly, then locksB. Thread 2 locksB, sleeps briefly, then locksA. Thesleepensures the interleaving that produces deadlock. - Run the program. Confirm that it hangs. Attach
gdbto the running process (gdb -p <pid>), and useinfo threadsandthread apply all btto show that each thread is blocked on apthread_mutex_lockcall for the mutex held by the other. - Take a screenshot or save the backtrace output as
deadlock_basic.txt. - Verify with
valgrind --tool=helgrindthat the tool reports a lock-order inversion. What does the report say, and how does it identify the two locks and the two threads?
Activity 4.2 — Breaking the circular wait
- Modify the program to impose a global lock ordering: both threads must acquire
AbeforeB. The deadlock disappears. - Now modify the program to release the first lock before acquiring the second (breaks hold-and-wait). The deadlock also disappears, but the program's semantics change — the operation is no longer atomic across the two locks.
- Use
pthread_mutex_trylockto implement the "no preemption" break: if the second lock cannot be acquired, release the first and retry. Add randomised back-off and verify the program terminates. - For each of the three fixes, write two sentences stating which of the four necessary conditions it removes.
Activity 4.3 — Resource allocation graph
- For the program in Activity 4.1, draw the resource allocation graph. Each thread is a process node; each mutex is a resource node with one instance.
- Render the graph with Graphviz (
dot -Tpdf) and include it in your report. - Extend the program to a third thread and a third mutex in a configuration that produces a longer cycle. Draw the new graph and confirm that the cycle is a single closed chain.
- Construct a program with no deadlock but a cycle in the graph. This is impossible for single-instance resources; explain why, and then construct the analogous case for multiple-instance resources: two processes each waiting for an instance of a resource, but the resource has two instances and the other process holds the one that satisfies its request.
Activity 4.4 — Banker's algorithm
- Implement the Banker's algorithm in
banker.c:- The program reads an
Allocationmatrix, aMaxmatrix, and anAvailablevector from standard input (or a file). - It implements
safe_state()that returns the safe sequence if one exists, or reports that the state is unsafe. - It implements
request(pid, request_vector)that checks the request againstNeed, the request againstAvailable, and then tests the resulting state for safety before granting the request.
- The program reads an
- Test the implementation with the following state and verify that a safe sequence exists:
One valid safe sequence is ⟨P1, P3, P4, P0, P2⟩. Write the state transitions step by step.Allocation Max Available P0 0 1 0 7 5 3 3 3 2 P1 2 0 0 3 2 2 P2 3 0 2 9 0 2 P3 2 1 1 2 2 2 P4 0 0 2 4 3 3 - Now issue the request
P1: (1, 0, 2). The request exceeds the need for resource type 1; the algorithm must deny it. Then issueP4: (3, 3, 0)— this request cannot be granted because it exceedsAvailable. Finally issueP0: (0, 2, 0)and show that granting it leads to an unsafe state. - Discuss the practical limitations of Banker's algorithm: the need to know Max in advance, the cost of the safety check on every request, and the fact that the algorithm cannot handle dynamic process arrival or resources that appear and disappear.
Activity 4.5 — Deadlock detection
- Write
detect.cthat implements the detection algorithm for multiple-instance resources:Work = Available Finish[i] = (Allocation[i] == 0) for all i while exists i with Finish[i] == false and Request[i] <= Work: Work += Allocation[i] Finish[i] = true Deadlocked = {i : Finish[i] == false} - Feed the algorithm the states from Activity 4.4 and verify the expected results: after the initial state, no deadlock; after the erroneous grant of
P0: (0, 2, 0)forced through, the deadlock set should be non-empty. - Add periodic detection to a running program: spawn four threads that acquire and release mutexes, and a fifth thread that every second runs the detector on a shared snapshot of the lock state. Log every detection event.
- Simulate recovery by killing one of the detected deadlocked threads with
pthread_canceland verifying that the other threads proceed. Discuss why killing the wrong thread may leave shared data inconsistent, and how a real system decides which thread to victimise.
05 Exercises
- Dining philosophers, revisited. Implement the dining philosophers with each of the four prevention strategies — resource ordering, global semaphore, asymmetric acquisition, and trylock with back-off — and measure the total throughput (meals per second) for 100,000 meals per philosopher. Which strategy is fastest? Which is fairest?
- Ordered mutexes with a runtime checker. Modify the program from Activity 4.1 so that every mutex has a numeric rank, and each acquisition verifies that the rank is strictly greater than the rank of every mutex the thread already holds. On violation, print a diagnostic and abort. This is a runtime detector for lock-order inversion; explain why a static analyser (like Helgrind) is usually a better tool for the same purpose.
- Banker's algorithm, extended. Extend your implementation to support resource types that are added after some processes have started. The state must be re-tested for safety whenever a resource appears; describe when such a state transition is safe and when it is not.
- Deadlock in a database transaction. Simulate a small database with row-level locks. Two transactions T1 and T2 access rows R1 and R2 in opposite orders. Implement a wait-for graph and detect the cycle. Then implement deadlock recovery by aborting the transaction with the least work done (measured by number of rows modified). Compare this heuristic with aborting the newest transaction.
- Comparison of strategies. For a workload in which resources are requested in random order, measure the throughput of (a) no deadlock handling, (b) prevention by global ordering, (c) avoidance by Banker's, and (d) detection and recovery. Present the results and explain why real operating systems rarely use Banker's.
-
Deadlock in a pipe-based shell pipeline.
Construct a shell pipeline in which process A
writes to pipe p1 and reads
from pipe p2, while process
B does the reverse. Use
mkfifoand shell redirection to build the pipeline. The two processes deadlock on the first blocking write to an unread pipe. Explain why the standard shell's own pipeline construction (in which each process reads from the previous and writes to the next) does not deadlock.
06 Deliverables
Submit a single archive named
lab6-<lastname>-<studentnumber>.tar.gz
containing:
deadlock_basic.c— Activity 4.1.deadlock_fixed.c— Activity 4.2 with three prevention strategies selectable by a command-line flag.banker.c— Activity 4.4.detect.c— Activity 4.5.philosophers.c— Exercise 1 (four prevention strategies selectable by a flag).waitfor.c— Exercise 4 (wait-for graph with recovery).- The
Makefilewith one target per program and acleantarget. deadlock_basic.txt— thegdbbacktrace and Helgrind report from Activity 4.1.graphs/— the Graphviz source and rendered PDFs for Activity 4.3.outputs.txt— the output logs of all programs with the commands that produced them.report.md(orreport.pdf) — the lab report described below.
Lab report
The report should be 4–6 pages (excluding code) and contain:
- Deadlock characterisation. For each deadlock you produce, identify which of the four necessary conditions hold and, for the fixes, which condition is removed. Use the resource allocation graph to justify each claim.
-
Banker's algorithm.
The safe sequence for the test state, with the
intermediate
Workvectors, and the three denied requests with the reason for each denial. - Detection results. The deadlock sets produced by the detector for each test state, and the recovery decisions with their justification.
- Throughput comparison. The measurements from Exercise 5, with a discussion of the trade-offs between the four strategies.
- Reflection. A discussion of why Linux, Windows, and most general-purpose operating systems do not implement Banker's algorithm, and what they do instead (lock ordering discipline, timeout and roll-back in databases, and detection with killing in a few kernel subsystems).
07 Grading rubric
| Criterion | Points | CLOs | Exemplary (90–100%) | Proficient (80–89%) | Developing (70–79%) |
|---|---|---|---|---|---|
| Deadlock construction and characterisation | 25 | CLO3 | Deadlocks are produced reliably, each necessary condition is identified, and the RAG correctly justifies the analysis. | Deadlocks produced; characterisation mostly correct. | Deadlocks produced but conditions or RAG analysis is incomplete. |
| Prevention strategies | 20 | CLO3, CLO8 | All four prevention techniques implemented and correctly mapped to the conditions they remove. | Most techniques implemented; mapping partially complete. | Techniques implemented but with incorrect or missing mapping. |
| Banker's algorithm | 25 | CLO3, CLO8 | Safety test and request handling are correct; all test cases produce the expected results with hand-verified transitions. | Implementation correct on the main cases; edge cases incomplete. | Implementation has bugs or fails on unsafe-state cases. |
| Detection and recovery | 20 | CLO3, CLO8 | Detector identifies deadlock sets correctly; recovery is demonstrated and its trade-offs discussed. | Detector works; recovery demonstrated but analysis is brief. | Detector or recovery incomplete or incorrect. |
| Report quality | 10 | CLO3 | Report is evidence-based, well-structured, and the reflection is insightful. | Report is clear; some claims lack evidence. | Report is disorganised or contains inaccuracies. |
Late submission penalty: 10% per day, up to a maximum of three days, after which the lab is accepted only with documented extenuating circumstances.
08 Knowledge check
Check your understanding
Question 1
Why is a cycle in the resource allocation graph a sufficient condition for deadlock when resources have a single instance, but only a necessary condition when they have multiple instances?
Show answer
Question 2
A system has four processes P0–P3 and three resource types A, B, C with 10, 5, and 7 total instances. Current state:
Allocation Max
P0 0 1 0 7 5 3
P1 2 0 0 3 2 2
P2 3 0 2 9 0 2
P3 2 1 1 2 2 2
Is the state safe? If so, give a safe sequence.
Show answer
Question 3
Explain why preventing deadlock by breaking hold-and-wait usually causes lower resource utilisation than breaking circular wait.
Show answer
Question 4
Describe the two main recovery strategies from deadlock, and give one advantage and one disadvantage of each.
Show answer
Question 5
In practice, why do most operating systems not implement the Banker's algorithm, and what do they do instead?
Show answer
09 References
- Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, 10th ed., Chapter 8.
- Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. Operating Systems: Three Easy Pieces, Chapter 32.
- Tanenbaum, A. S., & Bos, H. Modern Operating Systems, 4th ed., Chapter 6.
- Coffman, E. G., Elphick, M. J., & Shoshani, A. System Deadlocks. ACM Computing Surveys, 1971.
- Havender, J. W. Avoiding Deadlock in Multitasking Systems. IBM Systems Journal, 1968.
- Dijkstra, E. W. Cooperating Sequential Processes. Academic Press, 1968.
- Linux man-pages:
man 3 pthread_mutex_lock,man 3 pthread_mutex_trylock,man 1 valgrind(Helgrind section).