Lecture: Wednesday, February 9, 2005 Announcement: This is indeed CS 162 lecture _Figure_1_ |_ _____________ ^ | | | | | | | D ^ | |_ | |______ | R1 ^ | | | | | | | | C | | v | |_ |______e<-deadlock | | R2 | | | | | | B | | | v |_ |_____________| | | A P2 | t1 t2 t3 t4 |______|______|______|______| <-----R1----> <-----R2----> P1--------------------> Explanation of Figure 1: The x-axis represents the computational progress of process 1 (P1) The y-axis represents the computational progress of process 2 (P2) R1 and R2 are resources which are not shareable The times during which P1 and P2 use R1 and R2 are shown by the ranges around R1 and R2. For example, P1 uses R1 from t1 to t3. The graph is divided into four regions A, B, C, and D: A - The region in which normal computation is occurring. This region includes everything which is not in B, C, or D. B - This region is interesting because P1 is using R1 and will eventually request R2. But, P2 is using R2 and will eventually request R1. The two processes will wait for the resource that the other process is using and it is inevitable that point e will be reached. The two processes are in deadlock at point e. Computational progress can only move forward so from anywhere in B it is guaranteed that point e and hence deadlock will be reached. C - Impossible! Both processes cannot be using both resources at the same time. This region is not legal. D - This region is also impossible. "You can't get there from here!" This is a legal region, but to get there would require decomputation because C is not a legal region. Decomputing is not considered in this course. Bottomline: If you want P1 and P2 to compute and do their thing, they need to stay in region A. Deadlock Avoidance: - We can avoid deadlock, if we only make resource allocations that are "safe." safe state - One in which deadlock is not inevitable. (Region A in _Figure_1_) 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. This doesn't mean that we can't get ourselves into trouble in a safe state. safe allocation - one that leads to a safe state. safe sequence - the sequence of task executions leading from a safe state to another safe state. A complete safe sequence leads to all tasks being completed. unsafe state - One which leads to a deadlock. (Region b in _Figure_1_) There is an exception in that a task may release some resources temporarily which may enable other tasks to complete. Banker's algorithm: The banker's algorithm is the algorithm used to compute a safe sequence. Reason for name - If the processes are borrowers, the resource is money, and customers come in with partial loan requests, then the algorithm can be used to compute whether the bank can make the necessary loans to allow the customers to repay. Consider the case of construction loans: process has max need A 90 100 B 50 110 C 30 160 and bank has $20 available This was real money in the old days! for every process J (j=1...n) we need to know: - Max(j,k), which is the maximum number of resource k that will ever be requested by process j - Allocation(j,k), which is the number currently allocated - Need(j,k) which is the number still needed (Need(j,k) = Max(j,k) - Allocation(j,k)) for every resource k, we need to know Available(k), k=1...m (available(k) is the number still available) Algorithm works as follows (* denotes that we're not actually performing the operation) (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, everyone has everything they need (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). (because if there is a state that can finish, we assume that it did finish, so its resources go back into the pool) When applying this algorithm to the example, we discover that it is a safe state. For example, A gets the extra $10 dollars it needs and finishes. The $100 are returned to the bank. The bank has $110 now. B can get the $60 it needs. B can now finish and return the $110. The bank now has $160. This is enough to give C the extra $130 it needs. C finishes and returns the $160 so the bank ends up with $190. All of the construction projects were able to finish. The algorithm checks to see if a safe order exists, but doesn't return that order. So the algorithm is useful for determining whether an allocation should be made when one is requested. The algorithm should be run every time there is an allocation request. All we care about is the binary answer safe or not safe. max(j,k) is often not known. For example, the bay bridge retrofit. At some point this was estimated to be a 1 billion dollar job. There was enough money for that. Now its a 5 billion dollar job. So even when max(j,k) is known its not known. Another example is the Canary Wharf project in London. A Canadian real estate company decided at some point that it was time to redevelop this part of London. They reasoned that if they built enough buildings there they could create an upscale area with business buildings and so on. The problem was that they ran out of money about the same time the economy went down the tube in the early 90's. They ended up selling the whole thing at a huge loss. Now the area is actually doing ok. note that if there is only one resource (mmoney) then the banker's algorith, applies to loans feasibility? - banker's algorithm is only possible if we have knowledge of maximum resource needs in advance. This is how a computer system is not the same as building the Bay Bridge or Canary Wharf. A simple program which stores 100 numbers has straightforward resource needs. But a complicated online program may have very unpredictable resources needs. One can guess, but if that guess is wrong you end up with deadlock. - Overhead of running banker's algorithm if there are lots of processes and resources may be significant - not efficient. So, Banker's algorithm works in theory. But, its like shortest remaining time scheduler, it needs to know certain things. Recovery from deadlock: We can either prevent deadlocks (for instance with banker's algorithm), or recover from them. Given that we often do not know what resources will be needed in the future, it will be impossible to prevent them. We can try to prevent processes from doing stupid things. But, occasionally we are going to need to recover from deadlock. General idea is to periodically run a deadlock detection algorithm. It looks for circuits in the resource allocation graph. If it finds a circuit must break it. - do this periodically - or when system seems slow - and/or when there are a bunch of processes that seem to have waited for a long time - and/or when there are lots of processes, and also lots of CPU idle. two general approaches: - kill all deadlocked procceses - kill one process at a time until the deadlock cycle is eliminated. (This preferable - less damage, and less lost computation.) - want to select minimum cost processes to kill, which depends on whether the process can be restarted at all, and if so, how much computation has been lost. Rollback: causing the process to return to an earlier state. Sort of decomputing. If the process goes back to the beginning (restart), it is a total rollback. In some cases (especially if checkpoints have been kept), may be able to do partial rollback to break a deadlock. Checkpoint: a copy of the state of the process at some past time. distinction: UNIX vs. data processing system UNIX written for hackers by hackers - nobody was supposed to run payroll programs on UNIX. It was literally written by a couple of guys at bell labs. They wanted a program development system that they could use. It started out as 6 or 9 thousand lines of code. These guys were hackers. Now Unix (including linux, system 5, etc) is used by many people. Thoughout the semester we will see how Unix was developed for convenience not reliability. Data processing systems are designed to not be flaky like UNIX can be. Even when they crash, everything is backed up and can be recovered. UNIX can deadlock and one can definitely lose files. Although this is reasonable because Unix was never designed not to be flaky. When a database system is working correctly it should work like a data processing system. A database system is basically an overgrown file system. In general, prevention of deadlock is expensive and/or inefficient. Detection is also expensive and recovery is seldom possible (what if process has things in a weird state?). IBM OS/360 solutions: - Data set(file) 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. The obituary articles in the reader are good for learning about computer history. The single most important paradigm in the computer industry is software compatability. Whatever your product is, you have to maintain an interface. So if you have a machine architecture, you can make it bigger, faster, etc, but all the old software still has to run. If it doesn't run, people won't buy the new hardware. This is the reason for the x86 issue with Intel. One can still run 8080 software on a Pentium 4. Some 40 year old IBM mainframes are still running payroll programs. Whenever a product does not implement this forward compatability it usual goes downhill. For example, the Itanium. In a couple years the Itanium may be yesterday's news. Multics Solution: in 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) - put in only mild constraints on resource allocation. 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 locked flag and a wanted flag - When a kernel process wants a resource, it checks the locked flag. If not in use, sets the locked flag, and uses resource. (Must obviously set mutex to do this atomically.) - If locked flag is set, sets wanted flag, and calls sleep(), with a wait channel associated with that resource (typicaally the data structure used to describe the resource) - When 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 that 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 fromn running. Unix - Deadlock - Normally, (kernel?) 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 no sleep occurs. Unix - File Locking - only one process may have advisory exclusive lock or many processes may have advisory shared lock. Lock requests block if the lock is not available. Because locks are advisory, process may ignore them if desired. - Conditional lock request is possible - i.e. get error return if lock is set, rather than blocking. - Deadlock checking in Unix for filelocks is limited. System checks to make sure it doesn't deadlck with itself (i.e. it already holds an exclusive lock). Any application complicated enough to requre deadlock detection is required to develop its own. Now we are finished with synchronization deadlock and all that stuff. We are done with processors and processes. We are about to move to memory. Introduction to storage allocation: Linkers: Object code in system such as Unix divided into three parts: code ("text" in Unix terminology) data stack Why distinguish between different segments of a program? - 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. - Data and stack segments must apply to each process since those are modified - Dynamic linking. - Separate compilation Code is generated by compiler from source code (or from user assembly language) - Code contains addresses - Some of the addresses have values not known at compilation or assembly time. Address of base of text segment not known at compile time Address of base of code not known at assembly time Addresses of other separately compiled pieces of code not known. Why 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 information for that file. Information is incomplete, since each source file generally references some things defined in other source files. Compiler provides the symbol table and relocation table - linker: 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 address 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. 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 - no problem. - There are external addresses We also need these addresses. - There are addresses of data in the data segment. when compiling 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 - symbol table: contains global definitions - has table of labels that are needed in other segments. ussually these have been declared in some way - relocation table: table of addresses within this segment that need to be fixed. Contains internal - references to locations within this segment and external references - references that are believed to be external address location, symbol name, offsett to symbol, 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: determine location of each segment calculate values of symbols and update symbol table scan relocation table and relocate addresses in more detail: operation of a linker: collect all the pieces of a program this may involve finding some segments from the file system and libraries assign each segment a final location. build the segment table resolve all of the addresses that can be fixed, result is a new object file. All addresses may be resolved or there amy be a new object file with some addresses still unresolved this is done by taking the symbol table for each segment and assigning to each symbol its new address