Multi-programming in THE Landon Cox February 3, 2016 THE (First SOSP ’67) SIGOPS Hall of Fame summary “The first paper to suggest that an operating system be built in a structured way. That structure was a series of layers, each a virtual machine that introduced abstractions built using the functionality of lower layers. The paper stimulated a great deal of subsequent work in building operating systems as structured systems.” Across systems categories • Many common, important problems • • • • • • Fault tolerance Coordination of concurrent activities Geo. separated but linked data Large-scale data sets Protection from mistakes and attacks Interactions with many people • All of these problems lead to complexity Complexity • How do we control complexity? • Build abstractions that hide unimportant details • Establish conventional interfaces • Enable composition of simple, well-defined components • None of these ideas a specific to computer systems • These are just principles of good engineering • Civil engineering, urban planning, mechanical engineering, aviation and space flight, electrical engineering, ecology and political science Dealing with complexity • How do you impose order on complex programs? • • • • Break functionality into modules Goal is to “decouple” unrelated functions Narrow the set of interactions between modules Hope to make whole system easier to reason about • How do we specify interactions between code modules? • Procedure calls • int foo(char *buf) • Why do procedure calls reduce complexity? • They make interface between modules explicit • They hide implementation details Dealing with complexity P(a){…} C(){… y=P(x); …} • Via what data structure do C and P communicate? • They communicate via a shared, in-memory stack • What is on the stack? • Stack: regs, args, RA, P's vars, maybe args ... • C: push regs, push args, push PC+1, jmp P, pop args, pop regs, ... R0 • P: push vars, ..., pop vars, mov xx -> R0, pop PC • The call contract • • • • • P returns P returns to where C said P restores stack pointer P doesn't modify stack (or C's other memory) P doesn't wedge machine (e.g., use up all heap memory) Dealing with complexity P(a){…} C(){… y=P(x); …} • Is the call contract enforced? • At a low level none of it is enforced • P can violate all terms of the contract • Violations due to bugs and attacks • What about P’s functionality (what it does)? Enforced? • No. E.g., no guarantee that sqrt() returns the right answer • Would like a specification (spec) that P is correct • Can we enforce either the spec or contract? • We cannot enforce a procedure’s spec (i.e., that it is correct) • Can only apply testing to convince ourselves that code adheres to spec • We can enforce the call contract (= enforced modularity) Dealing with complexity P(a){…} C(){… y=P(x); …} • Why can we enforce the call contract? • Purely mechanical interaction • Programmer’s intention is clear • No semantic gap to cross • Ways that call contracts are enforced? • Put P and C in different processes (RPC) • Trusted compiler/runtime transitions between C, P (Java) • Hardware regulates transitions (kernel, user process) Dealing with complexity P(a){…} C(){… y=P(x); …} • Functions are programming language constructs • Can you think of a PL construct that shatters modularity? • goto • Inspired famous rant by Dijkstra in CACM in 1968 • “Go To Statement Considered Harmful” "The unbridled use of the goto statement has as an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress. ... The goto statement as it stands is just too primitive, it is too much an invitation to make a mess of one's program.” • Several months earlier … “Structure of THE multi-programming system” THE-multiprogramming system • An operating system is a very complex program • • • • Has to manage other processes Has to handle interrupts Has to handle I/O devices Has to handle synchronization primitives • How THE handles all of this complexity • Breaks functionality into stacked layers • Higher layers depend on lower layers THE-multiprogramming system • What is a layer? • A layer is a set of related procedure calls or functionality • Represents a level in a hierarchy • How do layers differ from procedure calls w/in a program? • Code can only call within layer and into layers below • Can build a layer without worrying about what happens above • Why is this additional structure appealing? • • • • Further limits how modules interact Reduces dependencies between modules Hopefully reduces complexity and bugs Do not have to worry about code in Layer 1 calling into Layer 4 THE-multiprogramming system • Batch system • Users submit programs • OS decides what to run • 5 user processes at a time • Multi-tasking • Manage multiple programs concurrently • Need to switch between programs Multi-programming • Want more than 1 process in phys memory • Processes can have same virtual addrs • Cannot share physical addrs • Addresses must be translated • Programs cannot manipulate phys addrs anymore • Statically: occurs before execution • Dynamically: occurs during execution • Protection becomes harder Static address translation • Want two processes in memory • With address independence • Without translating on-the-fly • Using static translation • How to do this? Static address translation • Adjust loads/stores when put in memory • This is called a linker-loader • • • fffff . Programming 80000 • Assume memory starts at 0 . 3ffff load $1, $base + offset . . Linker-loader . 20000 • Adds 0x20000 to offsets for P2 1ffff Similar to linking phase of compile . . . 00000 (high memory) OS kernel User process 2 User process 1 (low memory) Layer 4 User programs Layer 3 I/O devices Layer 2 Console Layer 1 Pager Layer 0 Scheduler Layer 0 • Primary responsibilities of Layer 0 • Ensure efficient, fair allocation of processor (government) • Hide number of processors (abstraction layer) • How did the scheduler ensure fair allocation? • Handled all transitions between processes • Some were voluntary transitions (synchronization calls) • Some were involuntary transitions (timer interrupts) • Could Layer 0 code be swapped out? • No, timer-interrupt handler always has to be in memory Layer 1 • Primary responsibilities of Layer 1 • Ensure efficient, fair allocation of memory (government) • Hide drum addresses (abstraction layer) • What were a segment, core page, and drum page? • Segment = coarse-grained unit of data, visible to program • Core page = in-memory container for a segment • Drum page = on-disk container for a segment Layer 1 • How were processes isolated? • • • • No hardware support for virtual memory Relied on language and compiler Programs written in Algol used private segment identifiers Like the static address translation discussed last time • Could Layer 1 code be swapped out? • No, memory manager always has to be in memory • Otherwise have a serious bootstrapping problem Layer 1 • How did paging work without hardware support? • Similar to way compiler handles procedure calls • Compiler needed to know which segments to be accessed • Compiler could insert calls into layer 1 to page-in segments • Was any of this enforced? • No, just like procedure-call contract is not enforced • At the lowest-level, could easily corrupt other processes • Bugs and attacks could crash the whole system Coordination of processes • “A society of harmonious sequential processes” • Each layer implemented as a process • To move between layers, need way to transition safely • What primitives provided safe switching? • Semaphores: Up (V) and Down (P) • Down (may) block the calling process • Up (may) allow another process to be scheduled Semaphores • First defined by Dijkstra for THE • Two operations: up and down // aka “V” (“verhogen”) up () { // begin atomic value++ // end atomic } What is going on here? Can value ever be < 0? // aka “P” (“proberen”) down () { do { // begin atomic if (value > 0) { value-break } // end atomic } while (1) } More semaphores • Key state of a semaphore is its value • Initial value determines semaphore’s behavior • Value cannot be accessed outside semaphore • (i.e., there is no semaphore.getValue() call) • Semaphores can be both synchronization types • Mutual exclusion (like locks) • Ordering constraints (like monitors) Semaphore mutual exclusion • Ensure that 1 (or < N) thread is in critical section s.down (); // critical section s.up (); • How do we make a semaphore act like a lock? • • • • Set initial value to 1 (or N) Like lock/unlock, but more general (could allow 2 threads in critical section if initial value = 2) Lock is equivalent to a binary semaphore Semaphore ordering constraints • Thread A waits for B before proceeding // Thread A s.down (); // continue // Thread B // do task s.up (); • How to make a semaphore behave like a monitor? • Set initial value of semaphore to 0 • A is guaranteed to wait for B to finish • Doesn’t matter if A or B run first • Like a CV in which condition is “sem.value==0” • Can think of as a “prefab” condition variable Prod.-cons. with semaphores • Same before-after constraints • If buffer empty, consumer waits for producer • If buffer full, producer waits for consumer • Semaphore assignments • mutex (binary semaphore) • fullBuffers (counts number of full slots) • emptyBuffers (counts number of empty slots) Prod.-cons. with semaphores • Initial semaphore values? • Mutual exclusion • sem mutex (?) • Machine is initially empty • sem fullBuffers (?) • sem emptyBuffers (?) Prod.-cons. with semaphores • Initial semaphore values? • Mutual exclusion • sem mutex (1) • Machine is initially empty • sem fullBuffers (0) • sem emptyBuffers (MaxSodas) Prod.-cons. with semaphores Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas) consumer () { down (fullBuffers) } producer () { down (emptyBuffers) down (mutex) down (mutex) take soda out put soda in up (mutex) up (mutex) up (emptyBuffers) up (fullBuffers) } Use one semaphore for fullBuffers and emptyBuffers? Remember semaphores are like prefab CVs. Prod.-cons. with semaphores Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas) consumer () { down (mutex) } 1 producer () { down (mutex) 2 down (fullBuffers) down (emptyBuffers) take soda out put soda in up (emptyBuffers) up (fullBuffers) up (mutex) up (mutex) } Does the order of the down calls matter? Yes. Can cause “deadlock.” Prod.-cons. with semaphores Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas) consumer () { down (fullBuffers) } producer () { down (emptyBuffers) down (mutex) down (mutex) take soda out put soda in up (emptyBuffers) up (fullBuffers) up (mutex) up (mutex) } Does the order of the up calls matter? Not for correctness (possible efficiency issues). Prod.-cons. with semaphores Semaphore mutex(1),fullBuffers(0),emptyBuffers(MaxSodas) consumer () { down (fullBuffers) } producer () { down (emptyBuffers) down (mutex) down (mutex) take soda out put soda in up (mutex) up (mutex) up (emptyBuffers) up (fullBuffers) } What about multiple consumers and/or producers? Doesn’t matter; solution stands. Prod.-cons. with semaphores Semaphore mtx(1),fullBuffers(1),emptyBuffers(MaxSodas-1) consumer () { down (fullBuffers) } producer () { down (emptyBuffers) down (mutex) down (mutex) take soda out put soda in up (mutex) up (mutex) up (emptyBuffers) up (fullBuffers) } What if 1 full buffer and multiple consumers call down? Only one will see semaphore at 1, rest see at 0. Monitors vs. semaphores • Monitors • Separate mutual exclusion and ordering • Semaphores • Provide both with same mechanism • Semaphores are more “elegant” • Single mechanism • Can be harder to program Monitors vs. semaphores // Monitors lock (mutex) // Semaphores down (semaphore) while (condition) { wait (CV, mutex) } unlock (mutex) • Where are the conditions in both? • Which is more flexible? • Why do monitors need a lock, but not semaphores? Monitors vs. semaphores // Monitors lock (mutex) // Semaphores down (semaphore) while (condition) { wait (CV, mutex) } unlock (mutex) • When are semaphores appropriate? • When shared integer maps naturally to problem at hand • (i.e., when the condition involves a count of one thing) Classic synchronization problem Each barber/stylist is a thread Each customer is a thread Sofa capacity = 4 Standing room Customer room capacity =9 Only 9 customers in room at a time. Stylist(int id) { Customer () { } Break up into groups of 2 or 3 Try to come up with a solution We’ll talk about it in 10 minutes… } Salon with semaphores Semaphore room = 9, sofa = 4, chair = 3, customer = 0, cut = 0; Stylist () { while (1) { customer.down() // cut hair cut.up () } } Is anything weird here? Customer () { room.down () // enter room sofa.down () // sit on sofa chair.down () // sit on chair sofa.up () customer.up () cut.down () // leave chair chair.up () // leave room room.up () } lock; roomCV; numInRoom=0; sofaCV; numOnSofa=0; chairCV; freeChairs[3]; customerCVs[3]; cutCVs[3]; Enter room Customer () { lock.acquire (); while (numInRoom == 9) roomCV.wait (lock); numInRoom++; while (numOnSofa == 4) sofaCV.wait (lock); numOnSofa++; Sit on sofa while (findFreeChair () == NONE) chairCV.wait (lock); stylist = findFreeChair (); freeChairs[stylist] = 1; numOnSofa--; sofaCV.signal (lock); Sit in chair Wake up stylist customerCVs[stylist].signal (lock); Wait for cut to finish cutCVs[stylist].wait (lock); freeChairs[stylist] = 0; chairCV.signal (lock); numInRoom--; roomCV.signal (lock); lock.release (); Leave the shop } Stylist(int id) { lock.acquire (); while (1) { while (!freeChairs[id]) customerCVs[id].wait (lock); cutHair (); cutCVs[id].signal (); } lock.release (); } Customer () { lock.acquire (); while (numInRoom == 9) roomCV.wait (lock); numInRoom++; while (numOnSofa == 4) sofaCV.wait (lock); numOnSofa++; while (findFreeChair () == NONE) chairCV.wait (lock); stylist = findFreeChair (); freeChairs[stylist] = 1; numOnSofa--; sofaCV.signal (lock); customerCVs[stylist].signal (lock); cutCVs[stylist].wait (lock); freeChairs[stylist] = 0; chairCV.signal (lock); numInRoom--; roomCV.signal (lock); lock.release (); } Next time • More history • Multics • “Almost every important idea in OS can be found somewhere in Multics.” • So complicated and hard to use, it inspired UNIX • Questions?