Operating System Concepts chapter 7 CS 355 Operating Systems Dr. Matthew Wright Deadlock Problem • Deadlock: A set of processes, each holding a resource, and each waiting to acquire a resource held by another process in the set. • Example: –Suppose a system has two disk drives. –Processes P1 and P2 each hold one disk drive and are waiting for the other drive. • Multithreaded processes are good candidates for deadlock because multiple threads can be competing for shared resources. Deadlock Characterization Deadlock requires that four conditions hold simultaneously: 1. Mutual exclusion: only one process can use a resource at a time 2. Hold and wait: a process holding a resource is waiting to acquire a resource held by other processes 3. No preemption: a resource can only be released voluntarily by the process holding it, after it has completed its task 4. Circular wait: there exists a set {P0, P1, ..., Pn} of waiting processes such that P0 is waiting for a resource held by P1, P1 is waiting for a resource held by P2, ..., Pn-1 is waiting for a resource held by Pn, and Pn is waiting for a resource held by P0. Resource-Allocation Graph • Round nodes indicate processes. • Rectangular nodes indicate resources, which might have multiple instances. • An arrow from a process to a resource indicates that the process has requested the resource. • An arrow from a resource to a process indicates that the resource has been allocated to the process. • If the graph contains no cycles, then there is no deadlock. • If each resource has only one instance, then a cycle indicates deadlock. R1 P1 R3 P2 P3 no deadlock deadlock R2 R4 Resource-Allocation Graph • Note: A cycle indicates the possibility of deadlock. It does not guarantee that a deadlock exists. • Example: The following resource-allocation graph contains a cycle, but not a deadlock: P2 R1 P1 P3 R2 P4 Java Deadlock Example The following program might or might not cause deadlock when it is run. class A implements Runnable { private Lock one, two; public A(Lock one, Lock two) { this.one = one; this.two = two; } class B implements Runnable { private Lock one, two; public A(Lock one, Lock two) { this.one = one; this.two = two; } public void run() { public void run() { public class DeadlockExample try { try { { one.lock(); two.lock(); public static void main arg[]) { // do something // do(String something Lock lockX = new ReentrantLock(); two.lock(); one.lock(); Lock lockY = new ReentrantLock(); // do something else // do something else } } Thread threadA = new Thread(new A(lockX, lockY)); finally { finally { Thread threadB = new Thread(new B(lockX, lockY)); one.unlock(); two.unlock(); two.unlock(); one.unlock(); threadA.start(); } } threadB.start(); } } } } } } Handling Deadlocks Three strategies for handling deadlocks: 1. Ensure that the system will never enter a deadlocked state. 2. Allow the system to enter a deadlocked state, detect it, and recover. 3. Ignore the problem and pretend that deadlocks never occur. Strategy 3 is employed by most operating systems, including UNIX, Windows, and the JVM. Deadlock Prevention Deadlock prevention: ensure that at least one of the four necessary conditions for deadlock cannot hold 1. Mutual exclusion: Can we eliminate mutual exclusion? • Sharable resources (e.g. read-only files) cannot be involved in deadlock. • Since some resources are intrinsically nonsharable, we generally cannot remove the mutual exclusion condition. 2. Hold and wait: How could we guarantee that a process never holds a resource while it waits for another? • We could require that a process holding any resource may not request another resource (e.g. a process must request all resources when it is created). • We could require that a process releases all resources before making a request for another resource. • These protocols are not efficient. Deadlock Prevention 3. No preemption: We could preempt resources from processes. • If a process requests resources that are not available, we could preempt any resources that it currently holds. • If a process requests resources held by another waiting process, we could preempt them from the other process. • Preemption is difficult if the state of a resource cannot easily be saved and restored. 4. Circular wait: Can we eliminate circular waits? • We could require that processes request resources in a particular order (e.g. tape drives, then disk drives, and finally printers). • An ordering could be implemented in Java by using System.identityHashCode(). • Often, requiring resource requests in a particular order is not convenient. Deadlock Avoidance • OS requires additional information from each process about the resources it will need, and the OS makes processes wait if they make a request that would produce deadlock. • Simple strategy: – Require each profess to declare in advance the maximum number of resources of each type that it will need. – The system then allocates resources in such a way that a circular wait condition never exists. • Safe state: The system is safe if it can allocate resources to each process and avoid deadlock. • Safe sequence: A sequence of processes (P1, P2, ..., Pn) is safe if the resources required by Pi can be satisfied by the currently available resources plus those held by all Pj, with j < i. Deadlock Avoidance • Safe state: The system is safe if it can allocate resources to each process and avoid deadlock. • Unsafe state: The system might not be able to allocate resources to each processes and avoid deadlock. • An unsafe state is not necessarily deadlocked! Deadlock Avoidance Example Suppose a system has 10 tape drives and 3 processes: Maximum Needs Allocation at t0 P0 8 3 P1 6 4 P2 3 2 At t0, the system is safe. Processes can run in the order P2, P1, P0. However, suppose that we let process P1 run and it requests and is allocated another tape drive at time t1. The system state is then: Maximum Needs Allocation at t1 P0 8 3 P1 6 5 P2 3 2 This state is unsafe, because any process that runs next might request another tape drive, which we would be unable to allocate. Deadlock Avoidance Strategy • When a process requests resources, grant the resources only if the system will still be in a safe state. • Resource utilization may be lower than it would otherwise be. • If there is only one instance of each resource, we can implement this strategy using a variant of the resourceallocation graph. • If there are multiple instances of each resource, we can use the “Banker’s Algorithm.” Resource-Allocation-Graph Algorithm • This avoids deadlock if there is only one instance of each resource type. • Initially, each process must specify which resources it might request in the future. • In the resource allocation graph, a dotted arrow from Pi to Rj is a claim edge, indicating that Pi might request Rj in the future. • If the request occurs, the claim edge is converted to a request edge. • A request can be granted if and only if converting the request edge to an assignment edge does not result in a cycle in the graph. • Cycle-detection algorithms are O(n2), where n is the number of vertices. R1 P1 R1 P2 P1 P2 R2 R2 Safe: no cycle Unsafe: a cycle Banker’s Algorithm • This avoids deadlock if there are many instances of each resource type. • We must maintain the following data structures: (n is number of processes and m is number of resource types) – Available: vector of length m, indicating the number of available resources of each type Available[j] is the number of instances of resource Rj. – Max: n x m matrix, indicating the maximum demand of each process Max[i][j] is the maximum number of instances of resource Rj that process Pi may request. – Allocation: n x m matrix, indicating the resources of each type currently allocated to each process Allocation[i][j] is the number of instances of resource Rj currently allocated to process Pi. – Need: n x m matrix, indicating the remaining resource need of each process Need[i][j] = Max[i][j] – Allocation[i][j] Banker’s Algorithm Safety Algorithm: determines whether or not the system is in a safe state; the algorithm is O(mn2) 1. Let Work be a vector of length m, and set Work = Available. Let Finish be a vector of length n, initialized so that each entry is false. 2. Find an index i such that both a. Finish[i] == false b. Need[i] ≤ Work If no such i exists, go to step 4. 3. Work = Work + Allocation[i] Finish[i] = true Go to step 2. 4. If Finish[i] == true for all i, then the system is safe. Otherwise, the system is unsafe. Example 3 resources: A (has 6 instances), B (has 3 instances), and C (has 4 instances) 4 processes, P0, P1, P2, P3, with maximums and allocations: P0 P1 P2 P3 A 2 4 5 3 Max B 2 0 2 2 C 1 2 2 0 Allocation A B C 1 1 1 1 0 2 0 0 0 1 2 0 Is the system in a safe state? Banker’s Algorithm Resource-Request Algorithm: determines whether resources can be safely granted Let Request[i] be the request vector for Pi. 1. If Request[i] ≤ Need[i], go to step 2. Otherwise, the request exceeds the process’ maximum. 2. If Request[i] ≤ Available[i], go to step 3. Otherwise, Pi must wait. 3. Pretend to grant the request, as follows: Available = Available – Request[i] Allocation[i] = Allocation[i] + Request[i] Need[i] = Need[i] – Request[i] Check to see if the system would still be safe, then grant the request. Otherwise, roll back the changes and make Pi wait. Example 3 resources: A (has 6 instances), B (has 3 instances), and C (has 4 instances) 4 processes, as before: P0 P1 P2 P3 A 2 4 5 3 Max B 2 0 2 2 C 1 2 2 0 Allocation A B C 1 1 1 1 0 2 0 0 0 1 2 0 What happens if the following requests are made (starting from the above state each time)? a. P0 requests [1, 0, 0] b. P2 requests [4, 0, 1] c. P2 requests [2, 0, 1] Deadlock Detection • If a system does not prevent deadlocks, it may provide: – An algorithm that examines the state of the system to determine whether a deadlock has occurred – An algorithm to recover from deadlock • If all resources have only a single instance, detecting deadlock involves looking for a cycle in the resource-allocation graph. • In fact, we can collapse the resource-allocation graph to a wait-for graph, which indicates which processes are waiting for which other processes to release resources. P5 R1 resourceallocation graph R3 P5 R4 P1 P1 R2 P2 P4 P2 P3 P4 corresponding wait-for graph P3 R5 Deadlock Detection Algorithm • If some resources have multiple instances, we must use the following data structures, similar to those in the Banker’s Algorithm: – Available: vector of length m, indicating the number of available resources of each type Available[j] is the number of instances of resource Rj. – Allocation: n x m matrix, indicating the resources of each type currently allocated to each process Allocation[i][j] is the number of instances of resource Rj currently allocated to process Pi. – Request: n x m matrix, indicating the current request of each process Request[i][j] is the number of instances of resource Rj requested by Pi. • The deadlock detection algorithm is O(mn2). Deadlock Detection Algorithm 1. Let Work be a vector of length m, and set Work = Available. Let Finish be a vector of length n. If Allocation[i] = 0, set Finish[i] = true; otherwise, set Finish[i] = false. 2. Find an index i such that both a. Finish[i] == false b. Request[i] ≤ Work If no such i exists, go to step 4. 3. Work = Work + Allocation[i] Finish[i] = true Go to step 2. 4. If Finish[i] == false for some i, then the system is in a deadlocked state. Example 3 resources: A (has 7 instances), B (has 4 instances), and C (has 2 instances) 4 processes, as before: P0 P1 P2 P3 Allocation A B C 2 1 1 1 0 0 0 0 0 1 1 0 Request A B C 1 2 1 6 0 2 5 4 1 3 2 2 Is the system deadlocked? What if P1 instead requests [4, 2, 1]? Deadlock Algorithm Usage • How often should we run the deadlock detection algorithm? • Factors to consider: – How often is deadlock likely to occur? – How many processes will be affected by deadlock if it happens? • Running the deadlock detection algorithm whenever a process requests a resource would be computationally expensive. • We could run the algorithm at periodic intervals (e.g. every hour). • We could run the algorithm when CPU utilization drops below some threshold (e.g. 40%). Deadlock Recovery • One way to recover from deadlock is to terminate processes. • Two strategies: 1. Abort all deadlocked processes: will surely work, but very expensive 2. Abort processes individually until deadlock is eliminated: still expensive, since we have to run the deadlock detection algorithm after each process terminated • Aborting processes is tricky, because the system could be left in an inconsistent state (e.g. if the process was in the midst of updating a file). • How do we choose which processes to terminate? Consider: – What is the priority of the process? – Are the resources held by the process are easy to preempt? – What is the least number or processes whose termination would resolve the deadlock? – How much computation would be repeated if the processes is restarted? Deadlock Recovery • Another way to recover from deadlock is to preempt resources. • Three issues: 1. Selecting a victim: Which resources should be preempted from which processes? How expensive will this be? 2. Rollback: If we preempt a resource from a process, what happens to that process? Can we roll the process back to a previous state? Perhaps we will need to abort the process. 3. Starvation: How do we ensure that starvation will not occur? (i.e. we shouldn’t always preempt resources from the same process)