13 February 2002 Operating Systems Read: Nutt, Chapter 8 ------------------------- Part 1 of next project is due on Wednesday. Multiprocessor Scheduling ------------------------- General research community consensus - processor affinity - keep where was running before good for small bus-based multiprocessors - processor partitioning - semi-permanently allocate some preocessors to each app - good for large, n/w based machines reality: small multiprocessors: round robin w/ proc. affinity large - do nonstandard, ad hoc homogeneous processors vs. heterogeneous load sharing - common ready queue concurrent access to queue separate ready queue may leave processor idle master-slave - master schedules slaves master may handle all I/O Synchronization --------------- Goal of Synch: eliminate race conditions be fair don't cause deadlock Key: what is atomic? load and store of whole word is minimal assumption (actually, theory-people do work when only bit-level is atomic) Condition Synchronization or Mutual Exclusion Condition: assume atomic reads of memory keep reading from a memory location until you see the value you want Ex. for only 2 processes: Peterson 1981 | type pid = 1..2 | turn : pid // initial value doesn't matter | c : array [pid] of Boolean // initialized to false | processor local i, otherguy : pid | | procedure acquire | c[i] := true | turn := otherguy | repeat until (not c[otherguy]) or (turn = i) | | procedure release | c[i] := false c[i] - I want access turn - you can go Sol'n is fair, they will alternate Not strict turn taking, in other words, if the other guy is not waiting, then i can get access Note the busy-wait! Can generalize to N processors by building hierarchy of these simple 2 processor versions. Always takes O(log(n)) time. ------ There is a solution by Lamport for N processors that, in the absence of contention, takes constant time. type pid = 1..N x, y : pid // initialized to 0 b : array [pid] of Boolean // initialized to false procedure acquire loop b[i] := true x := i // most recent process to begin protocol if y <> 0 b[i] := false repeat until y = 0 continue y := i if x <> i // competition: recover b[i] := false for j <> i in pid repeat until not b[j] if y <> i repeat until y = 0 continue return procedure release y := 0 b[i] := false Notice what happens in the absense of competition: b[i] := true x := i ( y = 0 ) y := i ( x = i ) y := 0 b[i] := false ------ Other options besides only assuming atomic reads and writes? 1. lock out interrupts! - don't do it for too long - can mess up the I/O subsystem - do you really want application programmers doing this? - will not work for multiprocessors 2. other atomic operations test-and-set compare-and-swap these return the old value, so you know The simple test_and_set lock: type lock = Boolean := false procedure acquire (L : ^lock) repeat until test_and_set (L) = false procedure release (L : ^lock) L^ := false Problems: not fair (possible starvation) LOTS of contention, for memory and interconnect bandwidth can use exponential backoff to spin less const base = // some small number const limit = // some large number procedure acquire (L : ^lock) backoff = base while test_and_set (L) = true // held by somebody else b := backoff := min (backoff*2, limit) repeat while --