CEUC301 · Unit 4 of 9
Deadlock Modeling &
System-Level Handling
Chapter 3 gave you real tools — semaphores, mutexes, monitors — for making threads wait safely for each other. This chapter is about what happens when "wait safely" goes wrong in a completely different way: not corruption, not a race, but a thread that will wait correctly, forever, for something that will never come.
The spine. WriteWell now protects two separate things with two separate locks: a
document lock around the in-memory document buffer, and a log lock
around the autosave log file. T_save needs both to finish an autosave: the document lock
to read the current text, the log lock to record that it saved. T_flush, rotating the log
file to keep it from growing forever, also needs both — log lock first, to do the rotation, then the
document lock, to double-check the document matches what the log says was last saved. Same two locks. Opposite
order. That's the whole chapter.
Deadlock Modeling, Prevention & Avoidance
Two threads, two locks, opposite order — and neither one did anything wrong.
T_save locks the document, then asks for the log. T_flush
locks the log, then asks for the document. If both get their first lock before either asks for the second,
neither request can ever be granted. This isn't a bug in either thread's logic — each one, read alone,
is completely correct. The problem only exists in how the two combine.
Two cooks, one knife, one cutting board
Imagine two cooks sharing a kitchen with exactly one knife and one cutting board. Cook A grabs the knife, Cook B grabs the board. Cook A now needs the board; Cook B now needs the knife. Both are perfectly willing to share — they'll hand over what they're holding the instant they're done with it — but neither will ever be done, because neither can start without the thing the other is holding. This is a deadlock: a set of threads, each waiting for a resource held by another thread in the same set, with no possible sequence of events that lets any of them proceed.
Modeling: the four conditions and the graph
Coffman, Elphick, and Shoshani proved in 1971 that all four of the following must hold simultaneously for a deadlock to exist. Break any one, and deadlock becomes structurally impossible — not just unlikely.
| Condition | What it means | In the spine |
|---|---|---|
| Mutual exclusion | At least one resource is held in a non-shareable way. | Both locks are exclusive — that's the whole point of a lock. |
| Hold and wait | A thread holds one resource while waiting for another. | T_save holds the document lock while waiting for the log lock. |
| No preemption | A held resource can't be forcibly taken away; it's only released voluntarily. | Neither thread's lock can be yanked away by the OS mid-hold. |
| Circular wait | A cycle exists: each thread in the cycle waits for a resource held by the next. | T_save → log lock → T_flush
→ document lock → T_save. |
A resource-allocation graph (RAG) makes circular wait visible directly: processes and resources are nodes, an edge from a resource to a process means "held by," and an edge from a process to a resource means "wants." A cycle in this graph is the circular-wait condition, drawn.
Figure 1 · the cycle, drawn
T_save and T_flush, deadlocked
resource-allocation graphDepth — with multiple instances per resource, a cycle isn't a guarantee
If a resource type has more than one interchangeable instance (say, three identical log locks instead of one), a cycle in the RAG becomes necessary but not sufficient for deadlock — there might be a free instance of that resource type sitting elsewhere in the cycle that lets someone proceed after all. The guarantee above depends specifically on each resource in the cycle having exactly one instance, which is the common case for a lock but not for resources like "printers" or "database connections" in general.
Prevention: make one condition structurally impossible
Prevention attacks one of the four conditions so deadlock literally cannot arise, at some cost in flexibility or efficiency.
| Attack | How | Cost |
|---|---|---|
| Eliminate mutual exclusion | Make the resource shareable (spooling a printer, for instance). | Doesn't work for anything that genuinely can't be shared — a write lock on a document being one. |
| Eliminate hold-and-wait | Require a thread to request everything it will ever need up front, atomically, before starting. | Poor resource utilisation — T_save
would hold the log lock the whole time it's editing the document, even during the part that never
touches the log. |
| Allow preemption | Let the OS forcibly reclaim a held resource from a waiting thread. | Complex, and not meaningful for every resource type — you can preempt a CPU (Chapter 2); you can't safely preempt "half-written to a log file." |
| Eliminate circular wait | Impose a global order on resources; every thread must request them in that order. | Cheap and the one actually used almost everywhere. |
Applied to the spine: declare "document lock always before log lock," system-wide. T_save
already follows this. T_flush is rewritten to acquire the document lock first too, even
though it uses the log lock first logically — the acquisition order is what matters, not the order of
use. With both threads obeying the same order, the cycle in Figure 1 can never form: whichever thread gets the
document lock first is guaranteed to be able to get the log lock too, because the other thread can't have
skipped ahead to grab it out of order.
Avoidance: allow the conditions, refuse unsafe requests
Prevention forecloses a condition permanently, everywhere. Avoidance is more surgical: allow all four conditions to exist in general, but before granting any single request, check whether granting it could still lead somewhere safe. Dijkstra's Banker's algorithm does exactly this, provided every thread declares its maximum possible need for each resource type up front.
A state is safe if there's some order to finish every thread one at a time, where
each thread's remaining Need can be met from Available plus
whatever earlier-finishing threads have released. A request is granted only if the resulting state is still
safe — otherwise the requester waits, even though the resources it's asking for happen to be free right
now.
Worked example: three resource pools, four residents
WriteWell's process now tracks three shared pools: network connections (9 total), disk I/O buffers (6 total), and worker-thread slots (7 total).
| Thread | Alloc R1 | R2 | R3 | Max R1 | R2 | R3 | Need R1 | R2 | R3 |
|---|---|---|---|---|---|---|---|---|---|
| T_save | 1 | 2 | 1 | 5 | 3 | 2 | 4 | 1 | 1 |
| T_flush | 2 | 0 | 1 | 4 | 2 | 3 | 2 | 2 | 2 |
| B_sync | 2 | 1 | 2 | 6 | 2 | 4 | 4 | 1 | 2 |
| P_backup | 1 | 1 | 0 | 3 | 3 | 1 | 2 | 2 | 1 |
Available right now (Total − sum of Allocation): R1=3, R2=2, R3=3.
Running the safety algorithm: T_flush's need (2,2,2) fits in Available (3,2,3), so it
can finish first, releasing (2,0,1) back — Available grows to (5,2,4). From there,
B_sync (4,1,2) fits, then P_backup (2,2,1), then
T_save (4,1,1) last. Safe sequence: T_flush → B_sync → P_backup →
T_save. The state is safe.
| Request | Check | Verdict |
|---|---|---|
T_flush asks for 1 more R1, 1 more R2 |
New Available (2,1,3) — re-running safety finds the same order still finishes everyone. | GRANTED |
T_save asks for just 1 more R2 |
New Available (3,1,3) — now no thread's Need fits: every Need vector needs more R2 than 1. | DENIED |
The second request looks tiny — one more disk buffer — and would still be well within
T_save's own declared Need. It's refused anyway, because granting it leaves not one
single thread able to finish with what remains. Nothing has deadlocked yet at the moment of the request; the
Banker's algorithm's whole job is refusing to walk into a state where deadlock becomes inevitable, before it
happens.
Figure 2 · try your own request
Same state as above — test any request
live safety checkPitfall
Banker's algorithm requires knowing every thread's maximum possible need in advance — a real, awkward requirement most real programs can't satisfy exactly. It's taught because the underlying safe/unsafe-state idea is foundational, not because operating systems commonly run it as-is; most real systems lean on prevention (lock ordering) or detection (Section 4.2) instead.
Practice 4.1
- Which single Coffman condition does "always acquire locks in the same global order" attack, and why does
breaking just that one condition prevent deadlock even though the other three still hold?
Show solution
Circular wait. Mutual exclusion, hold-and-wait, and no-preemption can all still be true, but a global order means it's never possible for thread A to hold what thread B wants while thread B holds what thread A wants — whichever resource comes first in the order, some thread will always be able to get it uncontested, breaking any potential cycle before it can close.
- Variation. If
T_save's Need were (4,1,1) but Available were only (3,1,3) with nothing else pending, couldT_savefinish right now?Show solution
No — Need (4,1,1) is not ≤ Available (3,1,3) in the first component (4>3).
T_savemust wait, regardless of what any other thread is doing, until enough R1 becomes available. - Interpretation. A system uses the Banker's algorithm and never deadlocks. Does that mean
deadlock was impossible for this workload regardless of allocation policy?
Show solution
No — it means the avoidance policy successfully steered around every unsafe state it was offered. The same threads, with a naive first-come allocator instead, could easily deadlock. Avoidance doesn't mean deadlock was never possible; it means every request that would have led somewhere unsafe was refused.
- Synthesis. Connect prevention's "eliminate hold-and-wait" strategy back to Chapter 3's
bounded buffer: would requiring
T_saveandT_flushto acquire all three semaphores at once (instead of one at a time) actually help here?Show solution
It would eliminate hold-and-wait for this specific interaction (no thread would ever hold one semaphore while waiting for another), but at the same cost noted in the prevention table:
T_savewould holdmutexfor its entire operation instead of just the few lines that touch the buffer, blocking unrelated access far longer than necessary. It solves the deadlock at the cost of a large chunk of Chapter 3's own concurrency benefit.
Detection & Recovery Trade-offs
Don't prevent it. Don't avoid it. Just catch it fast and clean up.
Section 4.1's approaches both cost something before a deadlock ever threatens: prevention gives up flexibility permanently, avoidance needs advance knowledge of every thread's maximum need. A third philosophy pays nothing upfront: let the four conditions hold freely, let deadlocks actually happen, and run a smoke detector.
Detection: collapse the graph, look for a cycle
Take Figure 1's resource-allocation graph and collapse each resource node away, replacing "process wants a
resource held by another process" with a direct edge between the two processes. This
wait-for graph makes the check almost trivial: T_save waits for
T_flush; T_flush waits for T_save. A
2-node cycle, found by the same kind of cycle-detection a compiler uses to catch circular imports. For
single-instance resources (true of both locks here), a cycle in the wait-for graph is exactly a deadlock
— found after the fact, rather than refused in advance the way Section 4.1's Banker's algorithm does.
Recovery: now that you've found it, what do you do?
Process termination
- Kill everyone in the cycle at once. Simple, certain to break the deadlock, and wastes every bit of work all of them had done.
- Kill one at a time, re-checking after each. Less wasteful, but needs a way to pick who — by priority, by how much CPU time they've already invested, by how many resources they hold, or by how far from finished they are.
Resource preemption
- Instead of killing a thread outright, forcibly take one of its resources back and roll it to an earlier checkpoint — needs the ability to checkpoint and restart cleanly, which not every resource or piece of state supports.
- The same victim-selection problem as termination, plus a new one: if the same thread keeps getting picked as the victim every time, it may never actually finish — a direct route into Section 4.4's starvation.
The trade-off: how often do you even look?
Running the detection algorithm isn't free, and the choice of when to run it is its own balancing act, structurally identical to trade-offs already seen twice in this book: Chapter 1's context-switch-overhead curve and Chapter 2's scheduling quantum.
| Policy | Accuracy | Cost |
|---|---|---|
| On every resource request | Catches a deadlock the instant it forms. | Highest possible overhead — a full graph-cycle check on every single request, most of which were never going to deadlock at all. |
| On a fixed timer | Deadlocked threads may sit stuck for up to one full interval before being noticed. | Bounded, predictable overhead — the usual real-world choice. |
| When CPU utilisation drops unexpectedly | Cheap heuristic trigger, but idle CPU has other innocent causes too (Chapter 1's I/O-bound threads, for one). | Lowest overhead, least reliable trigger. |
Pitfall
Detection-and-recovery does not mean "no cost." The cost simply moves from before the deadlock (Section 4.1's restricted flexibility or upfront bookkeeping) to after it (lost work from termination, detection-interval delay, or the engineering effort of implementing safe rollback). Choosing this philosophy is a bet that deadlocks will be rare enough that paying the recovery cost occasionally beats paying prevention's cost on every single request.
Practice 4.2
- In the wait-for graph for the spine deadlock, how many nodes and how many edges does the cycle have?
Show solution
Two nodes (
T_save,T_flush) and two edges (each waiting for the other) — the smallest possible cycle. Collapsing the two resource nodes out of Figure 1's four-node RAG leaves exactly this. - Variation. If recovery kills only
T_flush, doesT_saveimmediately proceed?Show solution
Yes. Killing
T_flushreleases the log lock it was holding;T_save's pending request for that lock can now be granted, breaking the cycle with a single termination rather than requiring both threads to be killed. - Interpretation. A system runs deadlock detection once every 30 seconds. A deadlock
forms 2 seconds after a check. How long do the affected threads sit stuck before recovery even begins?
Show solution
Up to 28 seconds — the deadlock isn't found until the next scheduled check, roughly 30 seconds after the previous one, minus the 2 seconds that had already elapsed. This is exactly the fixed-timer policy's accuracy cost from the table above, made concrete.
- Synthesis. Explain why resource preemption's victim-selection problem is a direct
preview of Section 4.4, before you've even read it.
Show solution
If victim selection always favours the same criteria (say, "always preempt whichever thread holds the fewest resources"), one unlucky thread matching that profile repeatedly could be rolled back every single time a deadlock involving it is detected, never actually completing — the system keeps making progress overall, just never for that one thread. That is precisely Section 4.3's definition of starvation, arrived at from the recovery side rather than the scheduling side.
Deadlock vs. Starvation: The Resource Trade-off
Gridlock, where nobody moves — versus one car that never gets let into the lane.
Both look like "a thread that never gets what it's waiting for." They are not the same failure, they don't have the same fix, and conflating them is a reliable way to lose marks on exactly this kind of question.
| Deadlock | Starvation | |
|---|---|---|
| Who's stuck | Every thread in the deadlocked set. | Possibly just one thread; everyone else is fine. |
| System progress | None — the deadlocked set can never advance. | The system as a whole keeps working; one thread just never gets its turn. |
| Is it guaranteed? | Yes, structurally — given the Coffman conditions, it's a certainty, provable from the resource-allocation graph. | Usually a probabilistic consequence of a policy plus bad luck, not a structural guarantee. |
| Standard fix | Prevention, avoidance, or detection + recovery (this chapter). | Aging, or a fairness bound built into the policy itself (Chapter 2). |
Where the two meet: allocation policy is the shared cause
Chapter 2 already showed this trade-off once, from the scheduling side: plain Priority scheduling can starve a low-priority thread indefinitely if higher-priority threads keep arriving, and the fix was aging — gradually raising priority the longer a thread waits. Resource allocation has the identical tension. A resource-granting policy based on priority, or on whichever request looks "safest" to grant (Section 4.1's Banker's algorithm doesn't rule this out), can systematically favour some threads over others. Nothing about that is a deadlock — the system keeps running, resources keep getting granted — but a consistently-deprioritised thread can wait indefinitely all the same.
Strict FCFS resource queuing is the opposite choice: grant requests in arrival order, full stop. This makes starvation structurally impossible (Chapter 2's Round Robin argument applies unchanged: bounded position in a queue is a bounded wait) at the cost of the flexibility priority-based or safety-based allocation offered — an urgent request can still be stuck behind a pile of older, less urgent ones.
Pitfall
"The system is deadlocked" and "this one thread is starving" call for different diagnosis and different fixes. If other threads are visibly still making progress, it is not a deadlock, no matter how long the stuck thread has been waiting — reach for aging or a fairness bound, not the resource-allocation graph.
Practice 4.3
- A monitoring dashboard shows one thread that hasn't run in ten minutes, while every other thread on the
system is actively making progress. Deadlock or starvation?
Show solution
Starvation. Deadlock requires that no thread in the affected set can progress; here, everything else is fine, which rules out deadlock by definition regardless of how long the one thread has waited.
- Variation. Would switching that system's resource-granting policy from priority-based to
strict FCFS fix the problem, and what would it cost?
Show solution
Yes — FCFS guarantees a bounded wait, eliminating this kind of starvation structurally. The cost is the same one Chapter 2 identified for Round Robin: every request is now served in arrival order regardless of urgency, so a genuinely time-critical request can be stuck behind a long queue of routine ones that arrived first.
- Interpretation. Does using the Banker's algorithm (Section 4.1) guarantee no thread will
starve?
Show solution
No. Banker's algorithm guarantees the system never enters an unsafe state, which prevents deadlock — it says nothing about whether one particular thread's requests happen to keep landing in the "would be unsafe" category while others' don't. Avoidance and starvation-freedom are separate guarantees; a system can have one without the other.
- Synthesis. Explain, in terms of the Coffman conditions, why starvation doesn't need any
of the four to hold.
Show solution
The Coffman conditions describe a structural configuration that makes progress impossible. Starvation doesn't require impossibility — it only requires that a policy keeps choosing not to grant a particular thread's request, even though granting it would be entirely possible and would not violate mutual exclusion, hold-and-wait, no-preemption, or circular wait in any way. It's a fairness failure, not a structural one.
Starvation & Livelock
Blocked forever is one failure. Busy forever, going nowhere, is a different one.
Starvation, briefly revisited
Section 4.3 defined it: a thread indefinitely denied a resource while the rest of the system keeps moving. The fix is the same one Chapter 2 already used for Priority scheduling: aging — gradually raise a waiting thread's priority (or its position in a resource queue) the longer it waits, so it eventually outranks everything else and is guaranteed to get through.
Livelock: busy, active, and still stuck
Deadlock's threads are passive — blocked, using no CPU, waiting for something that will never come. Livelock is the active twin: threads keep changing state in direct response to each other, consuming real CPU time, and still make no forward progress. Two people in a hallway who both step left to let the other pass, then both step right for the same reason, forever, are livelocked — neither is standing still, and neither is getting anywhere.
Worked example: a deadlock "fix" that trades one bug for another
Suppose T_save and T_flush try to avoid Section 4.1's
deadlock without imposing a lock order: each grabs its first lock, tries the second with a short timeout, and
if that fails, releases what it's holding and retries after a fixed delay.
| t | T_save | T_flush |
|---|---|---|
| 0 | locks document | locks log |
| 1 | tries log — blocked | tries document — blocked |
| 2 | both release their lock, both wait 3 time units | |
| 5 | locks document | locks log |
| 6 | tries log — blocked | tries document — blocked |
| 7 | both release, both wait 3 time units — identical to t=2 | |
No thread is ever blocked for more than an instant, no cycle ever sits still long enough for Section 4.2's detector to even catch it — and neither thread ever finishes an autosave. The timeout-and-backoff fix genuinely solved deadlock (nothing waits forever) while introducing livelock (nothing finishes, either) in its place.
The fix: break the symmetry
The failure above depends entirely on both threads retrying in lockstep. Give each thread a randomised backoff delay instead of the same fixed one, and their retries stop colliding within a few rounds almost certainly — whichever one happens to wait slightly longer lets the other grab both locks and finish. This is exactly the idea behind exponential backoff with jitter, used everywhere from network protocols to database retry logic.
All three, side by side
| Deadlock | Starvation | Livelock | |
|---|---|---|---|
| Thread state | Blocked, passive | Blocked/waiting, passive | Actively running, busy |
| CPU usage | None, for the stuck threads | None, for the stuck thread | Real CPU time spent, continuously |
| System-wide progress | None, for the affected set | Yes, elsewhere | None, for the livelocked threads |
| Standard fix | Prevention, avoidance, detection+recovery | Aging, fairness bound | Randomised backoff, breaking symmetry |
Pitfall
A system under livelock can look healthy on the crudest monitoring dashboards — CPU usage is high, threads are clearly "doing something." The tell isn't resource usage, it's the absence of actual completions over time. Always check throughput (Chapter 2: jobs completed per unit time), not just whether the CPU looks busy.
Practice 4.4
- Two threads retry a failed lock acquisition using the exact same fixed 100ms delay, forever. Deadlock,
starvation, or livelock?
Show solution
Livelock — both threads are actively running (acquiring, failing, releasing, waiting, retrying) rather than blocked, and neither ever completes despite the constant activity.
- Variation. If only
T_saveretries with a fixed delay whileT_flushretries with a randomised one, is livelock still likely?Show solution
Much less likely. Livelock in this example depends on both threads' retries landing at the same instant, round after round; if even one side's timing varies randomly, their attempts will drift apart within a few rounds and one will eventually succeed while the other is still waiting.
- Interpretation. A support ticket says "the server's CPU has been at 95% for an hour, but
no requests are completing." Which of this chapter's three failures does that best match, and why not the
other two?
Show solution
Livelock. High, sustained CPU usage rules out deadlock (whose stuck threads are blocked, not running) and points away from simple starvation (which wouldn't typically drive CPU usage up across the board, only deny progress to specific threads while others complete normally) — active CPU use combined with zero completions is livelock's signature.
- Synthesis. Explain why aging (Section 4.3's starvation fix) would do nothing to solve
the livelock in the worked example above.
Show solution
Aging works by eventually giving a waiting thread top priority so a scheduler or allocator is forced to favour it. In the livelock example, both threads already have equal standing and neither is being systematically deprioritised — the problem is purely timing symmetry between two equally-privileged threads, which raising one's priority over time doesn't address at all. Aging fixes an unfairness problem; livelock here is a synchronisation problem.
Cheat Sheet & Self-Test
Everything above, compressed to what you'd want on the way into an exam.
4.1 Modeling, prevention, avoidance
4 Coffman conditions (all needed): mutual exclusion, hold-and-wait, no preemption, circular wait.
RAG cycle + single-instance resources = guaranteed deadlock.
Prevention: break one condition, usually via lock ordering. Avoidance: Banker's algorithm — grant only if the resulting state is safe.
4.2 Detection & recovery
Wait-for graph = RAG with resources collapsed out. Cycle = deadlock, found after the fact.
Recovery: kill (all at once or one at a time) or preempt-and-rollback. Victim selection matters.
When to check: every request (accurate, costly) vs. timer (cheaper, delayed).
4.3 Deadlock vs. starvation
Deadlock: all stuck, no system progress, structurally guaranteed.
Starvation: one stuck, system progresses elsewhere, policy-driven not guaranteed.
Priority/safety-based allocation risks starvation; strict FCFS avoids it, at a flexibility cost.
4.4 Starvation & livelock
Starvation fix: aging.
Livelock: threads active, retrying, CPU busy — zero completions. Symmetric backoff can cause it; randomised backoff fixes it.
Tell: high CPU + zero throughput, not "CPU idle" (that's deadlock).
Mixed self-test
Deliberately not grouped by section — your exam won't be either.
- Name the four Coffman conditions in one line each.
Show solution
Mutual exclusion (a resource can't be shared), hold-and-wait (holding one resource while waiting for another), no preemption (a held resource can't be forcibly taken), circular wait (a cycle of threads each waiting on the next). (4.1)
- A resource-allocation graph has a cycle, but each resource type in the cycle has 3 interchangeable
instances. Is deadlock guaranteed?
Show solution
No — with multiple instances per resource, a cycle is necessary but not sufficient. A free instance elsewhere in the cycle could still let a thread proceed, breaking the deadlock that a single-instance cycle would have guaranteed. (4.1)
- Under the Banker's algorithm, a request is within both the requesting thread's declared Need and the
system's current Available. Is it automatically granted?
Show solution
No — those two checks are necessary but not sufficient. The request is only granted if the safety algorithm confirms the resulting state is still safe; this chapter's own worked example showed a request that passed both checks and was still denied. (4.1)
- What's the fundamental trade-off between detecting deadlocks on every resource request versus on a fixed
timer?
Show solution
Checking every request catches a deadlock the instant it forms but pays the overhead of a full check on every single request, most of which were never going to deadlock. A fixed timer bounds that overhead but lets a deadlock sit unnoticed, for up to one full interval, before recovery even begins. (4.2)
- Every other thread on a system is completing work normally except one, which hasn't run in an hour.
Deadlock or starvation?
Show solution
Starvation — deadlock requires that no thread in the affected set can progress; here, everything except one thread is fine, which rules deadlock out by definition. (4.3)
- Does switching a resource-granting policy to strict FCFS eliminate starvation for free?
Show solution
It eliminates starvation (a bounded queue position is a bounded wait, the same argument Chapter 2 made for Round Robin) but not for free: it costs the flexibility a priority-based or safety-based policy had, since an urgent request now has to wait behind every older request regardless of urgency. (4.3)
- A server shows 95% CPU utilisation but zero completed requests over the last hour. Which failure mode
does this match, and what rules out the other two?
Show solution
Livelock. High CPU usage rules out deadlock, whose stuck threads are blocked and consume no CPU; zero completions system-wide (not just for one thread) rules out simple starvation, where everything else keeps completing normally. (4.4)
- Two threads livelocked on a symmetric fixed-delay retry loop are given independently randomised delays
instead. Does this guarantee they'll never collide again?
Show solution
No guarantee, but it makes repeated collision extremely unlikely rather than certain. Randomised backoff breaks the systematic symmetry that made every retry collide; two random delays could still coincidentally match on any given round, just not indefinitely, round after round, the way two identical fixed delays do. (4.4)
Further reading
- Stallings, Operating Systems: Internals and Design Principles, 9th ed., Ch. 6. This course's primary text; closest match to this chapter's structure across all four sections.
- Silberschatz, Galvin & Gagne, Operating System Concepts, 10th ed., Ch. 8. The standard reference treatment of the resource-allocation graph and Banker's algorithm.
- Coffman, E.G., Elphick, M., & Shoshani, A., "System Deadlocks," ACM Computing Surveys, 1971. The original paper naming the four necessary conditions used throughout Section 4.1.
- Dijkstra, E.W., "EWD108: Een algorithme ter voorkoming van de dodelijke omarming" ("An algorithm to prevent the fatal embrace"), 1965. The original description of what became the Banker's algorithm.
Before this chapter — Chapter 2 (priority scheduling's starvation risk and its aging fix, reused directly in Section 4.3) and Chapter 3 (the mutexes and locks this entire chapter's deadlocks are built from — every scenario here is Chapter 3's tools, used correctly in isolation, colliding in combination).
Where this goes next — Unit 5, Memory Management System Design: memory fragmentation, paging, segmentation, and the kernel allocators (buddy, slab) that hand out memory itself. Every lock this chapter protected guards some region of memory — Unit 5 asks how the OS decides where that memory physically lives in the first place.