Lecture: Wednesday, February 9, 2005 February 9, 2005 Deadlocks_______________________________________________________________________ P2 ^ | ^ | x-----------x | | | | D | | | | R1 ^ | | x------x | | | | C | | | | | | v R2| x-----x | | | |E | | | B | | v | x------------x | | | A |_______________________________________________\ <----R1----> / P1 <-----R2-----> Figure of 2 processes running concurrently and requesting resources, R1, R2 at overlapping times. Resulting in blocks and deadlocks. P1 = process 1 P2 = process 2 R1 = resource 1 R2 = resource 2 region A - normal computation region B - concurrent processes. guaranteed deadlock (unsafe region) region C - is impossible, need use same unsharable resource region D - is impossible, to reach here would have to "decompute" point E - deadlock Deadlock Avoidance______________________________________________________________ Only make resource allocations that are "safe". safe state - one in which deadlock is not inevitable. I.e. one in which there exists a sequence of tasks, such that there are sufficient resources to complete one task, and after the resources released by that process are reclaimed, the state is still safe. (I.e. there exists a sequence of task completions which leads to all tasks being completed.) Any tasks left after trying this are deadlocked. After a task completion a safe state enters another safe state. safe allocation - one that leads to safe state safe sequence - task execution sequence that lead from a safe state to a safe state Not safe (i.e. unsafe) states lead to deadlocks. (Exception - a task may release some resources temporarily. Resource must be pre-emptible) Banker's Algorithm - used to compute safe sequence Can the bank make loans such that all borrowers are able to repay their debts? proc. has max need A 90 100 B 50 110 C 30 160 and bank has 20 available For every process J (j=1...n) we need to know Max(j,k) - max num of k that will be requested by j Allocation(j,k) - number currently allocated Need(j,k) - the number still needed - Max(j,k) - Allocation(j,k) For every resource k, we need to know available(k), k=1 ... m (different resources) Algorithm works as follows: (a) Given a request, let Alloc*(j,k) and Avail*(k) be the state after the request is granted. (b) If all processes can finish with no additional resources, then no deadlock; i.e. state safe. (c) Find a process x, for which for all k, Need*(x,k)<=Avail*(k). If there is no such process, then deadlock; i.e. state unsafe. Otherwise, "mark" process finished. (d) If all processes are marked finished, or can finish with no additional resources, then no deadlock; i.e. state safe. (e) Let for all k, Avail*(k) = Avail*(k) + Alloc*(x,k). Goto (c). Note that if there is only one resource (money), then the banker's algorithm applies to loans. Algorithm tests state by 'running' the 'fake' allocations with 'fake' jobs counting resources allocated and released. When 'fake' jobs finish, all resources held by job is released (like borrower paying back). Returns yes/no. Asked whenever about to allocate a resource. Q: Is it easy to compute max(j,k)? A: Often don't know how much "k" needed. Feasibility? Only works if have knowledge of maximum resources needed in advance May have severe overhead if lots of processes and resources present Recovery from Deadlock__________________________________________________________ Since we don't know enough to always prevent deadlocks, we can periodically run deadlock detection algorithms. Or we could check if: -system seems slow -there is a bunch of processes that seem to have waited a long time -there is lots of CPU idle time + lots of processes What to do when there is a deadlock? -kill all deadlocked processes -kill one at a time until deadlock goes away -calculate which minimum cost set of processes to kill Rollback - Return a process to an earlier state. Restart - Rollback to the beginning. Total rollback. Checkpoint - a copy of the state of the process at some past time. If checkpoints were used, may be able to do partial rollbacks to break a deadlock. Generally, deadlock prevention is hard. That is, it may be expensive and/or' inefficient. Detection is also expensive and recovery is seldom possible. < Professor reviews what was said in the first lecture and goes > < on a long digression. > < He talks about (among other things): > < - The business paradigm in software - compatability > < - Origins of the OS360 > IBM 0S/360 solutions: Data set names are enqueued all at once on a job basis. Devices, volumes, and memory are allocated in order on a per-job-step basis. Temporary file space can cause deadlocks. (or requests for additional file extents). Operator intervenes and kills a job. Multics Solution: In the main path of process, must request resources in specific order. In secondary paths (which can be preempted), must release resources held out of order (i.e. need "wait permit"). MTS (University of Michigan Timesharing System) - They put in only mild constraints on resource allocation. Moreover, they run deadlock detection algorithm periodically, and kill jobs that are found to be deadlocked. Seldom found to be the case. Unix - synchronization: Synchronized use of a resource done with two kinds of flags: a locked flag and a wanted flag. When a kernel process wants a resource, it checks the locked flag. If it isn't in use, it sets the locked flag, and uses the resource. (Must obviously set mutex to do this atomically.) On the other hand, if locked flag is set, kernel sets wanted flag, and calls sleep(). Process will in a channel associated with that resource (typically the data structure used to describe the resource.) When a process is done, clears locked flag, and if wanted flag is set, calls wakeup() to awaken all of the processes that called sleep() to await access to the resource. Those processes then compete to get lock. Note that this scheme is equivalent to the wakeup waiting flag scheme described in Watson's book. It fails if resource is unlocked between time lock is checked and flag is set. Some processes cannot be made to sleep (in "bottom half" of Unix kernel). Those processes are prevented from trying to use a locked resource (any resource that is shared between top and bottom of kernel) by raising the priority of the portion of the kernel that is running to prevent the other portion of the kernel from running. Unix - Deadlock Normally, process which is holding resources and finds that it wants a locked resource must release all of the resources it is holding before it goes to sleep. In special cases (e.g. going down a directory tree), resources are accessed in order. In that case, there can be no circuit in the resource alloc. graph, and so no sleep occurs. Introduction to Storage Allocation______________________________________ Linkers < Professor mentions something about zeroing out memory after finals > < A student says out loud: "I call it garbage collection. > object code in system is divided into 3 parts Code Data Stack Why distinguish between segments? We will sometimes want to have two or more processes running at the same time run the same code- i.e. share the code, which is possible since code isn't modified. However, data and stack segments must apply to each process, since those are modified. Code is generated by compiler from source code (or from user assembly langugage). Code contains addresses. Some of the addresses have values not known at compilation or assembly time. For example, address of base of text segment not known at compile time. Same with address of base of code and address of other, separately compiled pieces of code not known. Why are the addresses not known? We want the ability to combine code that was compiled at different times. If we compile everything at the same time, we don't have a problem. Division of responsibility between various portions of system: Compiler: generates one object file for each source code file containing ormation for that file. ormation is incomplete, since each source file generally references some things defined in other source files. Compiler provides the symbol table and relocation table, explained below. Linker or Linkage-Editor: combines all of the object files for one or more programs into a single object file, which is complete and self-sufficient. (I.e. linker can go from many object files to just one.) Loader - Takes single object file and adjusts all addresses to reflect correct load address for object file. Note - terms "linker" and "loader" are often used interchangably. We shall generally do so here. Linker often includes function of loader. Operating system: - Places object files into memory - allows several different processes to share memory at once - provides facilities for processes to get more memory after they've started running. Run-time library: works together with OS to provide dynamic allocation routines, such as calloc and free in C. What addresses are there in code? There are absolute addresses within a code segment. (E.g. JMP Label). The values of these addresses need to be determined. When code is compiled or assembled, these addresses are usually set as if segment is loaded at zero. There are relative addresses (e.g. JMP *+28), which don't need to be changed as they are relative to program counter. There are external addresses - e.g. CALL SOG. We also need those addresses. There are addresses of data in the data segment as well which can only be filled in when we know where data segment starts. When Compiling (we will ignore assemblers, which are similar), we create: Segment Table - for each segment, we need the segment name, segment size, and the base address at which it was assumed to be loaded: [Segment Name, Segment Size, Nominal base] Symbol table - contains global definitions and has table of labels that are needed in other segments. Usually, these have been declared in some way; internal labels are not externally visible. (Internal labels were known by the compiler anyway.) [symbol, segment_name, offset from base of segment] Relocation Table - table of addresses within this segment that need to be fixed, i.e. relocated. The table contains internal references to locations within this segment. It also contains external references (references not found locally) [address location, symbol name, offset to symbol (i.e. offset to address), length of address field] Compiler provides these tables along with the object code for each segment. Effectively, there are 3 steps in a Linker/Loader: 1) Determine location of each segment. 2) Calculate values of symbols and update symbol table. 3) Scan relocation table and relocate addresses.