CSE 451 04/30/03 prepared by: Mark Yamagishi and Andrew Parker What were the points of today's lecture? - The principles of monitors can be added into non-strongly typed languages using locks and condition variables. - An evolution occurred of memory virtualization from 1 memory segment to many fixed-size memory segments to many variable-size memory segments. Point of 04/28/03 lecture: Semaphore usage can be error prone while monitors guarantee mutual exclusions. Monitors fundamentally guards access to data, implicitly giving sequentiality and mutual exclusion. What are downsides to monitors? 1. Dependent on compilers, must use a modular language w/ lexical scoping (strongly typed languages). 2. Granularity of control up to the compiler, control mechanism not easily expressible. Types are what allow for the data hiding. Compilers enforce and deduce modularity by using types. When in doubt the safest thing for a compiler to do about concurrent access is to take the conservative approach and just not allow it. OK, but most programming going on in C/C++ which aren't strongly typed. So can't use them in their present form in what most people are using. "No cars on Mars work here." >From DEC SRC '88, they tried answer how to use monitors in non-typed environments. Major benefits of monitors: 1) automatic handling of synchronization of access to the data, 2) separation of mechanism (mutual exclusion is done with mutexes whereas scheduling is done with condition variables). Can't do 1) in a language that doesn't have strong typing, so concentrate on 2) and try to solve the granularity problem. solution: Add locks (mutexes, use Mesa semantics) and condition vars as separate entities that can be used in not strongly typed languages. Ended up with the general form: Lock_t l; Condition_t c; lock(&l); --------> start critical section ... // do stuff ... while(not rdy) wait(c, &l); // block on condition var c, and release lock l to allow // others in, until get a signal() unlock(&l); --------> end critical section Advantages of this solution is that we can now have as fine grained of locking as you want (just use more locks). For example if you have 100 elements in an array, then you can just have a lock for each element instead of for the entire array: int a[100]; Lock_t l[100]; Condition_t c; lock(&l[i]); ... ... while (not rdy) wait(c, &l[i]); unlock(&l[i]); So the easy way is to have one big lock (coarse granularity, eg. Linux bkl (big kernel lock)), which is fine to start as one may be unsure of necessary mutual exclusion. Then can refine granularity with more locks as needed. ***************************************** ***************************************** Virtualizing Memory What does virtualizing memory mean? Providing an interface to allow a programmer to use memory differently than how it's actually working. Ways this works: 1. memory -> make it bigger (have X amount of physical memory, make it act like more) 2. --------> make it smaller (have X amount of physical memory, make it act like less) 3. --------> make it more efficient (2 of same program running, use same memory) 4. --------> make it "not here" (thinks its in memory, but actually on disk or network) 5. --------> make it reliable (bring back from failure, after reboot, etc.) 6. --------> make it secure (store in memory encrypted, then decrypt as needed) Mechanisms of Virtualization 1. Translation virtual address --> physical address (this is HW's job) program address machine address Can be seen with: GDB o-scope 2. Exception (page fault)- this is what happens when translation fails this is SW + OS job The analogy of the page fault can be made to the interrupt. The ability to do multiprogramming on a single processor is fundamentally tied to the interrupt. The ability to do memory virtualization is fundamentally tied to the page fault. Back to the box, where does the TLB exist? ----------------------------- | | | ALU ~~~~ | | ~~~~ | | | | ------------| | |other stuff| | Reg |P/!P | | |ASID | | |TLB | -> translation look-aside buffer - make the | |... | translation quick ----------------------------- Let's take the historical approach. In 1950 HW was big boards no translation. Then memory got to be cheaper and HW got to be faster. The translation idea was conceived and could be done off-board from the processor. In the multiprogramming model this led to: Fixed Size Segments -------------------- | | 0 virtual address -----------> |------------------| | | | 1k +-------- |------------------| | |-------->| | 2k base address --------------> |------------------| | | 3k |------------------| so sum virtual address to a base address to get to physical address. One goal is to make that addition fast. Make the base address the low order bits and virtual address the high order bits, then the sum becomes a concatenation of bits. Some truisms emerge from this construct -Virtual address comes from processor from program from compiler from programmer so the size of virtual address block never changes, it is a spec of architecture. -always taking a number generated from OS + number generated from program concatenated together. Q: What is good about fixed size segments? A: It's easy. Q: What are some potential problems w/ Fixed size partitions? A: -Potential overwriting outside fixed size, but the base/offset concatenation prevents this collision. not a problem. -A program could be too large to fit in a segment. How could one get around this? Maybe use multiple segments for the program. But this is harder than if it did fit in one segment, more work, more time. Plus would tie implementation to the the particular fixed size architecture. -A program could be very small, wasting memory (internal fragmentation). Must allocate a whole segment that will not be fully utilized. The problems of the program being too large to fit in a segment or being too small and wasting memory can be solved by going from Fixed to Variable sized partitions. So the memory virtualization has gone from: 1 partition -> fixed partitions -> VARIABLE partitions -------------------- +---+ Yes | | 0 virtual address -| > |-----> |------------------| +---+ | | | 2k | |No | | | | | | | | limit-------------+ | | | | | | | | | +-------- |------------------| 5k | | |-------->|------------------| base address --------------> | | 6k | | | V |------------------| EXCEPTION note: the base address path crosses over the 'not' path to the exception. No interaction between the two paths. Now along w/ the concatenation of the virtual address w/ the base address, the virtual address is compared to a variable limit. If the limit > VA everything is fine. If the VA > limit, then an exception occurs. This compare can be done in parallel with the address space construction. The exception goes to the processor -> generates a segmentation violation (access to address beyond allowed range). With the variable size partitions, can have small and large partitions w/ limits Benefits: -Internal fragmentation mostly goes away. -Can support big programs. Fri: What's good/bad about variable partitions