Friday, September 24, 1993 ------------------------------------------------------------------------------- Today: mutual exclusion using semaphores ------------------------------------------------------------------------------- Mutual exclusion in multiprogramming system achieved using atomic hardware operations. Example: test and set function Test-and-set(var target: boolean): boolean; begin Test-and-set = target; target = true; end --- lock = false; Processes... while(true) { while(Test-and-set(lock)) do ; critical section lock = false; } --- Also swap, etc... ------------------------------------------------------------------------------- Problems with above solutions: hardware dependent. Different hardware, different implementations don't lend self easily to more complex problems (e.g., 2 processes can enter; solutions involving scheduling for reasons such as fairness, etc.) example given is inefficient because of busy wait (uniprocessor can burn up much time with busy wait) Again, would like to support more complex algorithm General principle: Only implement once Generalization: semaphores ------------------------------------------------------------------------------- Semaphores: Dijkstra (1965) two atomic operations, P(s) and V(s) where s is a *semaphore* s is a non-negative integer P: from Dutch proberen (to test): wait P(s)---decrement s by 1 if possible (i.e., without going negative). If s==0, then wait until it is possible to decrement s without going negative. V: from Dutch verhogen (to increment): signal V(s)---increment s by 1 in a single atomic action. Discussion of implementation of P and V will come later ------------------------------------------------------------------------------- Mutual exclusion now looks like this: mutex = 1; (the semaphore) Processes... while(true) { P(mutex); critical section V(mutex); } ------------------------------------------------------------------------------- Bounded buffer example (again) n, the number of buffers; Semaphores: e: number of empty buffers, f: number of full buffers; b: mutex e = n; f = 0; b = 1; Producer Consumer while(true) { while(true) { produce next record P(e); P(b); P(f); P(b); add to buffer remove from buffer V(b); V(f) V(b); V(e); process record } } ------------------------------------------------------------------------------- Disadvantage of semaphores: can still have deadlock Process one Process two P(S); P(Q); P(Q); P(S); critical section critical section V(S); V(Q); V(Q) V(S); Implementation may or may not prevent starvation. So, later will consider even higher-level mechanisms ------------------------------------------------------------------------------- Semaphore implementation Test-and-set First implement *binary* semaphores (either 0 or 1) and then implement general semaphores. Binary semaphores Pb(sb) and Vb(sb) sb == false means can pass sb == true means must wait Pb(sb): while(Test-and-set(sb)) do ; Vb(sb): sb := false; semaphores: mutex and delay P(s) Pb(mutex); s = s - 1; if(s < 0) then {Vb(mutex); Pb(delay)}; Vb(mutex); V(s) Pb(mutex); s = s + 1; if(s <= 0) then Vb(delay) else Vb(mutex) Disadvantage of test and set is busy wait But P and V can be implemented in more complex ways---for example blocking process on P if the resource is busy with the subsequent V operation unblocking next process to run (see text) ------------------------------------------------------------------------------- Next: higher level constructs classical synchronization problems