The operating system manages resources that are (generally) non-preemptable.
However, managed resources are serially reusable. They can be reused by another process after the current process finishes (e.g., tape drives).
Resources are discrete and bounded, meaning that they are allocated in integer units (e.g., 2 tape drives, not 1.2 drives).
Identical resources belong to resource classes. For instance, most processes don't care which tape drive they get out of the pool of identical drives.
The resource manager fields requests from user processes to allocate and release resources. The manager has two options:
Get Resource; /* may block */ Use Resource; Release Resource;
If the process terminates before it has released its resources, the resource manager can:
Canonical example: Two processes A & B need two tape drives sometime during their execution. The system has only two drives. The resource manager could allocate resources as follows:
A deadlock has occurred, because neither process can proceed.
Of course, the resource manager could grant requests differently. For instance:
Deadlock can occur only if the following conditions hold:
Designing algorithms that prevent deadlock involves preventing one or more of the above conditions.
When considering deadlock, it is useful to draw a resource graph that shows processes and resources. In a resource graph:
A deadlock has occurred if the resource graph contains a cycle.
Note: The resource manager designer is faced with a classic problem: we want to maximize concurrency, yet minimize the occurrence of deadlock.
Conservative policies deny resources even when they are available. However, they reduce the likelyhood of deadlock at the expense of reduced concurrency
Liberal policies allocate resources when they are available at the risk of entering a deadlock state later.
Classic problem to demonstrate both deadlock and starvation.
Five philosophers seated around a table with ten chopsticks and five rice bowls. Need two chopsticks to eat rice. Philosophers alternate between thinking and eating. Want to grant chopsticks while avoiding both deadlock and starvation.
What happens with the following straightforward solution:
Philospher(which) int which; /* index of philospher */ Resource LeftChopstick, RightChopstick; { while (1) { Think; /* for some amount of time */ GetResource(LeftChopstick); GetResource(RightChopstick); Eat; ReleaseResource(LeftChopstick); ReleaseResource(RightChopstick); } }
The most conservative approach is called serialization. No two processes are allowed to allocate any resources concurrently.
Serialization works because all processes must wait for the one process holding resources to complete.
Example: Suppose two processes both need two tape drives. Once one drive has been allocated to the first process, the second process would not be granted any request until the first process releases all the resources it holds.
Disadvantage:
To avoid cycles in the resource graph, require processes to acquire all resources at one time. That way, the resource manager has sufficient information to determine if granting the request can lead to a deadlock.
Consider the Dining Philosophers problem. Philosophers would ask for both chopsticks at the same time, and return them both at the same time:
Disadvantage:
Observation: we can eliminate cycles in a resource graph by assigning increasing numbers to resources, and disallowing arcs from higher numbered resources to lower numbered ones.
Requirement: a process may only request resources at a higher level than any resources it currently holds.
Example: A system with tape drives, printers, and plotters might assign each resource at level 1, 2, and 3 respectively.
A process that has one tape drive may request either printers or plotters (but not another tape drive).
A process that has one plotter may not request any additional resources.
Hierarchical allocation solves the deadlock problem because deadlock can only occur if a cycle appears in the resource graph. However, a cycle can occur only if an arc goes from a higher numbered resource to a lower numbered one.
Disadvantage:
Processes make an advance claim before acquiring resources.
The resource manager records the current allocation state for each resource class.
An allocation state is called realizable if:
The resource manager wants to insure that unrealizable states are never entered.
A realizable state is called safe if there is a sequence of processes, called a safe sequence that satisfy:
As long as we are in a safe state, we can always run the processes in the order given by the sequence, thereby preventing deadlock. (Hopefully, we can do even better!)
Example:
Process | Holding | Claims |
A | 4 | 6 |
B | 2 | 7 |
C | 4 | 11 |
Unallocated | 2 |
A can finish (although it can request 2 more drives, they are unallocated)
B can finish. If it asks for 5 more, it must wait until A completes.
C can finish. If it asks for 7 more, it must wait until A & B complete.
Example:
Process | Holding | Claims |
A | 4 | 6 |
B | 2 | 9 |
C | 4 | 11 |
Unallocated | 2 |
A can finish, but B will not, if it requests 7 drives.
Advance-Claim Algorithm: never allocate a request if it causes the current allocation state to become unsafe (also called the banker's algorithm).
Observation: if the state is safe, and if some process A can finish given currently available resources, there is a safe sequence starting with A.
Algorithm:
S = { set of all processes }; while (S != EMPTY_SET) { Find A, an element in S that can finish if (no A exists) state is unsafe remove A from S; add A's resources to the unallocated pool } state is safe
Note: most liberal policy we have seen that prevents deadlock.
Disadvantages:
So far, we have considered deadlock avoidance algorithms. The strategy of discovering deadlocks after they have occurred is called deadlock detection.
If we suspect a deadlock has occurred, build a resource graph and look for cycles. However, our definition of a resource graph must be modified slightly:
A knot is a set of vertices (processes and resources) such that when starting at any vertex in the knot, paths lead to all vertices in the knot, but to no vertices outside the knot.
Detecting knots:
When would be a good time to check for deadlock?
Once deadlock has been detected, deadlock recovery choses a victim process that will break the deadlock when terminated.
Issues:
A process bigjob needing many resources may starve:
Possible solution: