CMSC 421 Operating Systems Lecture Notes (c) 1994 Howard E. Motteler Semaphores ========== Example: Bounded Buffer ------------------------ Suppose we have a producer, a consumer, and a bounded buffer, Suppose we have - a sleep() call, that puts the caller to sleep, i.e., moves it to the ``waiting'' state - a wake(P) call, that `wakes' process P, i.e., moves it back to the ``ready'' state We can use sleep() and wake() instead of busy waiting N is buffer size; count the number of things in the buffer; initially cound, in, and out are all 0 Producer: while (alive) { generate(&V); // generate some data if (count == N) sleep(); // buffer is full buffer[in] = V; // we're awake now, so assume in = (in+1) % N; // there's space in the buffer count++; if (count == 1) wake(consumer); // if buffer *was* empty, } // then wake the consumer Consumer: while (alive) { if (count == 0) sleep(); // nothing to consume V = buffer[out]; // we're awake not, so assume out = (out+1) % N; // there's something to consume count--; if (count == N-1) wake(producer) // if buffer *was* full, // then wake the producer use(V); // use the data } Consider the following possible sequence: (note: such sequences are called ``traces'') - Suppose buffer is empty and count is 0 - consumer sees count == 0 - producer adds V to buffer, increments count to 1, and tries to wake the consumer - consumer does sleep() - but the consumer has missed the wakeup message! - when the buffer fills, the producer will also sleep, leaving both asleep forever Problem: a wakeup call was sent to a process that was not yet sleeping! One solution is to keep a queue of wake-up calls, or simply a count of pending wake-ups (i.e., to use semaphores) Question: can we treat the test of count, or the test of count and the sleep() together, as a critical section? Semaphores ========== wait (or DOWN) generalization of sleep() signal (or UP) generalization of wake() typedef struct { int val; process_list L; } sem; wait(sem * S) { // DOWN(S) S->val = S->val - 1; if ( S->val < 0 ) { add this process id to S->L; move this process to "waiting" state; } } signal(sem * S) { // UP(S) S->val = S->val + 1; if (S->val <= 0) { remove a process p from S->L; move p to "ready" state; } } If the list L is a FIFO queue, we have bounded waiting We say a process P ``waits on semaphore S'' if P is on S->L Semaphores must execute atomically, except when ``asleep'' - on a single processor system we can disable interrupts - on a multi-processor system we may need software mutex (Given mutual exclusion we can implement semaphores, and given semaphores we can implement mutual exclusion) If S->val is negative, then abs(S->val) is the number of waiting processes A seperate counter could be used for the number of waiting processes, or we could just count the length of L Example: Semaphores for mutual exclusion ----------------------------------------- Initially, lock->val = 1 Note: we will often temporarily forget about the process list L, and say lock=1 rather than lock->val = 1 P0, P1: : wait(lock); CS(); signal(lock); one process: initially lock = 1 P0: wait(lock) lock = 0 P0: CS() lock = 0 P0: signal(lock) lock = 1 two processes: initially lock = 1 P0: wait(lock) lock = 0 P0: CS() lock = 0 P1: wait(lock) lock = -1 P1 waits P0: signal(lock) lock = 0 P1 is awakened P1: CS() lock = 0 P1: signal(lock) lock = 1 initial lock value two processes with reentry of P0: initially lock = 1 P0: wait(lock) lock = 0 P0: CS() lock = 0 P1: wait(lock) lock = -1 P1 waits P0: signal(lock) lock = 0 P1 is awakened P0: wait(lock) lock = -1 P0 waits P1: CS() lock = -1 P1: signal(lock) lock = 0 P0 is awakened P0: CS() lock = 0 P0: signal(lock) lock = 1 initial lock value three processes: initially lock = 1 P0: wait(lock) lock = 0 P0: CS() lock = 0 P1: wait(lock) lock = -1 P1 waits P2: wait(lock) lock = -2 P2 waits P0: signal(lock) lock = -1 P1 is awakened P1: CS() lock = -1 P1: signal(lock) lock = 0 P2 is awakened P2: CS() lock = 0 P2: signal(lock) lock = 1 initial lock value We see that abs(lock) counts waiting processes In many implementations the semaphore value never goes negative, and there is a separate counter for the number of waiting processes Semaphores With Busy Waiting ---------------------------- In this case, the semaphore is just an integer variable wait(s) { while (s <= 0); s--; } signal(s) { s++; } Both signal() and wait() are atomic, except that in wait(), mutual exclusion must be released between successive ``while'' tests, so that another process has a chance to modify the semaphore variable s If the ``while'' test is false, i.e., if s > 0, then s-- is guaranteed to follow without interruption For this form of semaphore, the semaphore value is never negative, and there is no count of waiting processes There is no fixed bound on waiting, if two or more processes are waiting on such a semaphore - the process that is awakened depends on how the scheduler runs the ``while'' tests - if the scheduler is ``fair'' (e.g., round robin), then eventually every waiting process will catch a signal, but we can't put a bound on how long this wait may be An example with both types of semaphore two processes: sem.val sem initially 1 1 P0: wait(lock) 0 0 P0: CS() 0 0 P1: wait(lock) -1 0 P1 wait starts P0: signal(lock) 0 1, 0 P1 wait completes P1: CS() 0 0 P1: signal(lock) 1 1 Semaphores for Synchronization ------------------------------ Suppose S1 in P1 must always happen before S2 in P2 (e.g., S1 is a write and S2 is a read) initially synch is 0 P1: P2: : : S1; wait(synch) signal(synch); S2; : : Deadlock -------- Deadlock: two or more processes waiting on each other Example: suppose s = q = 0; P0: P1: : : wait(s) wait(q) : : signal(q) signal(s) : : P0 waits for P1, and P1 waits for P0 This can easily happen if signals and waits get out of order (more examples in section on deadlocks)