Wednesday, September 15, 1993 Announcement No instructor office hours next MW since instructor out of town. TA will lead classes M: introduce laboratory assignment W: OS structure ------------------------------------------------------------------------------- Process coordination (Chapter 5) *Multiprocessor* considerations (multiple CPUs operating simultaneously) Shared state (e.g., shared memory). result of parallel computation on shared memory can be nondeterministic: - Suppose you execute A = 1; || B = 2; - What is result in A? 1, 2, 3? - Race condition (race to completion); cannot predict what will happen as it depends on which one goes faster [what happens if both go at exactly same speed?] Basic assumptions for building systems of cooperating processes - Order of some operations are irrelevant. Some operations are independent of each other (e.g., A = 1; || B = 2;). - Can identify certain segments where interaction is critical. - *Atomic operation(s)* exist in hardware (or can't solve this critical section problem). Atomic operation: either happens in its entirety without interruption or not at all. - assertion: if you don't have atomic operations you cannot make them - Example, difference if printf is and is not atomic with printf("ABC"); || printf("CBA"); - but printf is generally too big to be atomic. usually memory references and assignments on simple scalars (e.g., single bytes or words) are atomic. - use lower-level atomic operations to build higher-level ones - note also that in uniprocessor system, operations with interrupts disabled are atomic - More on the implementation of atomic operations after we see why we want them Note that in analysis, no assumptions on the relative speed of two processes (e.g., cannot count on process A always being faster and winning race), although can assume all processes executing at nonzero speed. ------------------------------------------------------------------------------- Producer/consumer applications Producer produces information; consumer consumes that information for example "piped" applications in Unix cat file.t | eqn | tbl | troff | lpr Bounded buffer --- filled by producer; emptied by consumer Given n, the size of buffer, need to define buffer [0..n-1] and two counters, in, out, to show where producer and consumer are. counter says how many entries in buffer (incremented by producer and decremented by consumer): initialize counter to 0 Producer Consumer while(true) { while(true) { produce an item in nextp; while counter == n do noop; while counter == 0 do noop; buffer[in] = nextp; nextc := buffer[out]; in = in + 1 mod n; out := out + 1 mod n; counter = counter + 1; counter := counter - 1; consume item in nextc; } } Concurrent execution can cause unexpected results, even if we assume that assignment and memory references are atomic If start with value n and execute two concurrently Interleavings can leave counter with value n (two ways), which is ok or with value n+1 or n-1 (incorrect). For example: load counter load counter subtract 1 store counter add 1 store counter results in value of n+1 Similar effects even when two processes are running *same* code over shared resource (e.g., shopping expedition example) ------------------------------------------------------------------------------- Problem is that we are running into that portion of the code for which interaction is critical. Need to manage interaction in those areas - critical section: section of code or collection of operations in which only one process may be executing at a given time. For example, updating "counter"; "shopping" entry: request permission to enter critical section exit: marks end of critical section - mutual exclusion: ensure that only one process is in critical section at any one time - locking: prevent others from entering critical section obtain lock; enter critical section; exit critical section; release lock Proper solution to critical-section problem must provide the following: Mutual exclusion Progress: if multiple processes are waiting entry to critical section and no process in critical section, eventually one of processes will gain entry (e.g., lockstep problem is counter example) and decision based only on waiting processes (non waiting can't cause delay) Bounded waiting: no indefinite postponment (e.g., a process that keeps zipping in and gaining access blocking out others) Avoiding deadlocks: e.g., 2 resources; Process A gains resource 1; process B gains resource 2; process A waits for resource 2; process B waits for resource 1 ------------------------------------------------------------------------------- Two process solution turn := 1; Process 1 Process 2 while(true) { while(true) { non critical stuff non critical stuff while(turn == 2) ; /* wait */ while(turn == 1) ; /*wait*/ critical section critical section turn = 2; turn = 1; non critical stuff non critical stuff } } Processes are alternating: 1, 2, 1, 2, ... (lockstep synchronization) if 1 not ready 2 has to wait if 1 dies 2 never gets to execute again... ------------------------------------------------------------------------------- p1busy = false; p2busy = false; Process 1 Process 2 while(true) { while(true) { while(p2busy) ; /*wait*/ while(p1busy) ; /*wait*/ p1busy = true; p2busy = true; critical section critical section p1busy = false; p2busy = false; } } Mutual exclusion not enforced! Both can enter critical regions simultaneously. ------------------------------------------------------------------------------- p1busy = false; p2busy = false; Process 1 Process 2 while(true) { while(true) { p1busy = true; p2busy = true; while(p2busy) ; /*wait*/ while(p1busy) ; /*wait*/ critical section critical section p1busy = false; p2busy = false; } } (flip order of test/setting busy flag) Mutual exclusion guaranteed but can result in deadlock (both in lockstep waiting) ------------------------------------------------------------------------------- Replace wait with while(p2busy) { while(p1busy) { p1busy = false; p2busy = false; sleep; sleep; p1busy = true; p2busy = true; } } No deadlock but indefinite postponement a possibility now (again lockstep) ------------------------------------------------------------------------------- Dekker's algorithm provides solution: turn = 1; p1busy = false; p2busy = false; Process one Process two is similar... while(true) { p1busy = true; while(p2busy} { if(turn == 2) { p1busy = false; while (turn == 2) ; /* wait */ p1busy = true; } } critical section turn = 2; p1busy = false other code } Avoids indefinite postponment. Note that if it isn't process one's turn it retracts its busy flag and waits for its turn. Otherwise it waits for p2 to retract its busy flag ------------------------------------------------------------------------------- Peterson's version turn = 1; p1busy = false; p2busy = false; Process one while(true) { p1busy = true; turn = 2; while(p2busy and turn == 2) ; /* wait */ critical section p1busy = false; other code... } -------------------------------------------------------------------------------