Synchronization 2: semaphores and more… 1 Operating Systems, 2011, Danny Hendler & Amnon Meisels 1 What’s wrong with busy waiting? The mutual exclusion algorithms we saw used busy-waiting. What’s wrong with that? Doesn‘t make sense for Uni-processor Wastes CPU time May cause priority inversion and deadlock Operating Systems, 2011, Danny Hendler & Amnon Meisels 2 What’s wrong with busy waiting? Busy waiting may cause priority-inversion and deadlock Process A's priority is higher than process B's Process B enters the CS Process A needs to enter the CS, busy-waits for B to exit the CS Process B cannot execute as long as the higher-priority process A is executing/ready Priority inversion and deadlock result Operating Systems, 2011, Danny Hendler & Amnon Meisels 3 Outline Semaphores and the producer/consumer problem Counting semaphores from binary semaphores Event counters and message passing synchronization Operating Systems, 2011, Danny Hendler & Amnon Meisels 4 Synchronization Constructs – Semaphores (Dijkstra ‘68) Two operations define a semaphore S: DOWN(S) p(s) while(s <= 0); s = s - 1; UP(S) s = s + 1; v(s) Operations on the counter are performed indivisibly S is non-negative but this description busy-waits… Operating Systems, 2011, Danny Hendler & Amnon Meisels 5 Semaphores (Dijkstra ‘68) Two atomic operations are supported by a semaphore S: down(S) [the ‘p’ operation] If S≤0 the process is blocked. It will resume execution only when awakened by an up(S) operation S-- up(S) [the `v’ operation] S++ If there are blocked processes, wake-up one of them S is non-negative What exactly does “atomic” mean ?? Operating Systems, 2011, Danny Hendler & Amnon Meisels 6 Wrong non negative Semaphore Lines of code are executed asynchronously S=0 and process A performs down(S) – A is blocked Process B performs up(S) – S=1 A is ready Process C performs down(S) – S=0 & C proceeds Process A gets a time-slice and proceeds – S=0 A single up() operation for 2 down()s Operating Systems, 2011, Danny Hendler & Amnon Meisels 7 Semaphores (Dijkstra ‘68) Two atomic operations are supported by a semaphore S: down(S) [the ‘p’ operation] If S≤0 the process is blocked. It will resume execution only when awakened by an up(S) operation else S-- up(S) [the `v’ operation] If there are blocked processes, wake-up one of them else S++ S is non-negative Supported by Windows, Unix, … Operating Systems, 2011, Danny Hendler & Amnon Meisels 8 Implementing mutex with semaphores Shared data: semaphore lock; /* initially lock = 1 down(lock) Critical section up(lock) Yes Does it satisfy deadlock-freedom? Yes Does it satisfy starvation-freedom? Depends… Does the algorithm satisfy mutex? Operating Systems, 2011, Danny Hendler & Amnon Meisels 9 Semaphore as a General Synchronization Tool Execute B in Pjj only after A executed in Pii Use semaphore flag initialized to 0 Code: Pii … time A up(flag) 0 Pjj … down(flag) B Operating Systems, 2011, Danny Hendler & Amnon Meisels 10 Synchronization with semaphores Three processes p1; p2; p3 semaphores p1 down(s1); A up(s2); s1 = 1, s2 = 0; p2 down(s2); B up(s2); p3 down(s2); C up(s1); Which execution orderings of A, B, C, are possible? (A B* C)* Operating Systems, 2011, Danny Hendler & Amnon Meisels 11 No guarantee for correct synchronization P0 P1 down(S); down(Q); move1 up(S); up(Q) down(Q); down(S); move2 up(Q); up(S); 1 1 Example: move money between two accounts which are protected by semaphores S and Q Does this work? Deadlock! Operating Systems, 2011, Danny Hendler & Amnon Meisels 12 Negative-valued semaphores Two atomic operations are supported by a semaphore S: up(S) down(S) S-If S<0 the process is blocked. It will resume execution only when S is non-negative S++ If there are blocked processes (i.e. S<0), wake-up one of them -3 If S is negative, then there are –S blocked processes Operating Systems, 2011, Danny Hendler & Amnon Meisels 13 Negative semaphore Implementation type semaphore = record value: integer; L: list of process; end; -3 down(S): S.value--; if (S.value < 0) { add this process to S.L; sleep; } up(S): S.value++; if (S.value <= 0) { remove a process P from S.L; wakeup(P); } Operating Systems, 2011, Danny Hendler & Amnon Meisels 14 Producer-Consumer Problem buffer in out Paradigm for cooperating processes, • producer process produces information that is consumed by a consumer process Two versions • unbounded-buffer places no practical limit on the size of the buffer • bounded-buffer assumes that there is a fixed buffer size Operating Systems, 2011, Danny Hendler & Amnon Meisels 15 Bounded Buffer buffer 0 1 2 item1 3 item2 2 Out 4 item3 producer consumer 5 item4 6 In 7 Operating Systems, 2011, Danny Hendler & Amnon Meisels 16 Implementation using semaphores Two processes or more use a shared buffer in memory The buffer is finite (e.g., bounded) The producer writes to the buffer and the consumer reads from it A full buffer stops the producer An empty buffer stops the consumer Operating Systems, 2011, Danny Hendler & Amnon Meisels 17 Producer-Consumer implementation with semaphores #define N typedef int semaphore semaphore semaphore 100 semaphore; mutex = 1; empty = N; full = 0; /* Buffer size */ /* access control to critical section */ /* counts empty buffer slots */ /* counts full slots */ void producer(void) { int item; while(TRUE) { produce_item(&item); down(&empty); down(&mutex); enter_item(item); up(&mutex); up(&full); } } /* generate something... */ /* decrement count of empty */ /* enter critical section */ /* insert into buffer */ /* leave critical section */ /* increment count of full slots */ Operating Systems, 2011, Danny Hendler & Amnon Meisels 18 Producer-Consumer implementation with semaphores void consumer(void) { int item; while(TRUE) { down(&full); /* decrement count of full */ down(&mutex); /* enter critical section */ remove_item(&item); /* take item from buffer) */ up(&mutex); /* leave critical section */ up(&empty); /* update count of empty */ consume_item(item); /* do something... */ } } Comment: up() and down() are atomic operations Operating Systems, 2011, Danny Hendler & Amnon Meisels 19 Implementing a binary semaphore with TSL In user space, one can use TSL (test-set-lock) mutex_lock: TSL CMP JZE CALL JMP ok: RET REG, mutex REG, #0 ok thread_yield mutex_lock | the equivalent of enter_region mutex_unlock: MOV mutex, #0 RET also called a Spin-lock Operating Systems, 2011, Danny Hendler & Amnon Meisels 20 Implementing a negative semaphore with TSL type semaphore = record value, flag: integer; L: list of process; end; down(S): repeat until test-and-set(S.flag) S.value--; if (S.value < 0) { add this process to S.L; S.flag=0 sleep; } else S.flag=0 -3 up(S): repeat until test-and-set(S.flag) S.value++; if (S.value <= 0) { remove a process P from S.L; wakeup(P); } S.flag=0 Operating Systems, 2011, Danny Hendler & Amnon Meisels 21 More on semaphore implementation TSL implementation can work for multi-processors On a uni-processor, may be implemented by disabling interrupts On a multi-processor, we can use spin-lock mutual exclusion to protect semaphore access Why is this better than busy-waiting in the 1st place? Busy-waiting is now guaranteed to be very short Operating Systems, 2011, Danny Hendler & Amnon Meisels 22 Outline Semaphores and the producer/consumer problem Counting semaphores from binary semaphores Event counters and message passing synchronization Operating Systems, 2011, Danny Hendler & Amnon Meisels 23 Binary Semaphore Assumes only values 0 or 1 Wait blocks if semaphore=0 Signal either wakes up a waiting thread, if there is one, or sets value to 1 How can wakeup signals be accumulated (for the bounded buffer, for example) 24 Operating Systems, 2011, Danny Hendler & Amnon Meisels 24 Implementing a counting semaphore with binary semaphores: take 1 binary-semaphore S1, S2 initially 1; S.value initially 1 down(S): down(S1); S.value--; if(S.value < 0){ up(S1); // L1 down(S2); } //L2 else up(S1); up(S): down(S1); S.value++; if(S.value ≤ 0) up(S2); up(S1) This code does not work 25 Operating Systems, 2011, Danny Hendler & Amnon Meisels 25 Race condition for counting semaphore take 1 1. Processes Q1 – Q4 perform down(S), Q2 – Q4 are preempted between lines L1 and L2: the value of the counting semaphore is now -3 2. Processes Q5-Q7 now perform up(S): the value of the counting semaphore is now 0 3. Now, Q2-Q4 wake-up in turn and perform line L2 (down S2) 4. Q2 runs but Q3-Q4 block. There is a discrepancy between the value of S and the number of processes waiting on it 26 Operating Systems, 2011, Danny Hendler & Amnon Meisels 26 Implementing a counting semaphore with binary semaphores: take 2 binary-semaphore S1=1, S2=0; S.value initially 1 down(S): down(S1); S.value--; if(S.value < 0){ up(S1); //L1 down(S2); } //L2 up(S1); up(S): down(S1); S.value++; if(S.value ≤ 0) up(S2); else up(S1) Does this code work? 27 Operating Systems, 2011, Danny Hendler & Amnon Meisels 27 The effect of the added ‘else’ up(S1) is performed by up(S) only if no process waits on S2 Q5 leaves up(S) without releasing S1 Q6 cannot enter the critical section that protects the counter It can only do so after one of Q2-Q4 releases S1 This generates a “lock-step” situation The critical section that protects the counter is entered alternately by a producer or a consumer 28 Operating Systems, 2011, Danny Hendler & Amnon Meisels 28 Recall the bounded-buffer algorithm #define N 100 typedef int semaphore; semaphore mutex = 1; semaphore empty = N; semaphore full = 0; void producer(void) { int item; while(TRUE) { produce_item(&item); down(&empty); down(&mutex); enter_item(item); up(&mutex); up(&full); } } 29 void consumer(void) { int item; while(TRUE) { down(&full); down(&mutex); remove_item(&item); up(&mutex); up(&empty); consume_item(item); } } Operating Systems, 2011, Danny Hendler & Amnon Meisels 29 A Problematic Scheduling Scenario Consider a Bounded buffer of 5 slots. Assume there are 6 processes each filling five slots in turn. 1 2 3 4 5 6 30 Empty.Value = 5 Operating Systems, 2011, Danny Hendler & Amnon Meisels 30 A Problematic Scheduling Scenario 1. five slots are filled by the first producer 1 2 3 4 5 6 31 Empty.Value = 0 Operating Systems, 2011, Danny Hendler & Amnon Meisels 31 A Problematic Scheduling Scenario 1. The second producer is blocked 1 2 3 4 5 6 32 Empty.Value = -1 Operating Systems, 2011, Danny Hendler & Amnon Meisels 32 A Problematic Scheduling Scenario 1. The third producer is blocked 1 2 3 4 5 6 33 Empty.Value = -2 Operating Systems, 2011, Danny Hendler & Amnon Meisels 33 A Problematic Scheduling Scenario 1. The fourth producer is blocked 1 2 3 4 5 6 34 Empty.Value = -3 Operating Systems, 2011, Danny Hendler & Amnon Meisels 34 A Problematic Scheduling Scenario 1. The fifth producer is blocked 1 2 3 4 5 6 35 Empty.Value = -4 Operating Systems, 2011, Danny Hendler & Amnon Meisels 35 A Problematic Scheduling Scenario 2. All blocked producers are waiting on S2 1 2 3 4 5 6 36 Empty.Value = -5 Operating Systems, 2011, Danny Hendler & Amnon Meisels 36 A Problematic Scheduling Scenario 3. The consumer consumes an item and is blocked on Empty.S1 until a producer adds an item. 1 2 3 4 5 6 37 Empty.Value = -5 Operating Systems, 2011, Danny Hendler & Amnon Meisels 37 A Problematic Scheduling Scenario 3. The consumer consumes an item and is blocked on S1 , one producer adds an item. 1 2 3 4 5 6 38 Empty.Value = -4 Operating Systems, 2011, Danny Hendler & Amnon Meisels 38 A Problematic Scheduling Scenario 4. Consumer must consume, only then another producer wakes up and produces an item 1 2 3 4 5 6 39 Empty.Value = -3 Operating Systems, 2011, Danny Hendler & Amnon Meisels 39 A Problematic Scheduling Scenario 4. Same as in step 3. 1 2 3 4 5 6 40 Empty.Value = -2 Operating Systems, 2011, Danny Hendler & Amnon Meisels 40 A Problematic Scheduling Scenario 5. And again… 1 2 3 4 5 6 41 Empty.Value = -1 Operating Systems, 2011, Danny Hendler & Amnon Meisels 41 Implementing a counting semaphore with binary semaphores: take 3 (P.A. Kearns, 1988) binary-semaphore S1=1, S2=0, value initially 1, integer wake=0 down(S) down(S1); S.value--; if(S.value < 0){ up(S1); //L1 down(S2); //L2 down(S1); S.wake--; //L3 if(S.wake > 0) then up(S2);} //L3 up(S1); 42 up(S): down(S1); S.value++; if(S.value <= 0) { S.wake++; up(S2); } up(S1); Does THIS work? Operating Systems, 2011, Danny Hendler & Amnon Meisels 42 Correctness arguments (Kearns)… The counter S.wake is used when processes performing down(S) are preempted between lines L1 and L2 In such a case, up(S2) performed by processes during up(S) have no effect However, these processes accumulate their waking signals on the (protected) counter S.wake After preemption is over, any single process that wakes up from its block on down(S2) checks the value of S.wake The check is again protected For each count of the wake-up signals, the awakened process performs the up(S2) (in line L3) Each re-scheduled process wakes up the next one 43 Operating Systems, 2011, Danny Hendler & Amnon Meisels 43 Kearns' algorithm is wrong Processes P0..P8 perform down(S), P1..P8 are preempted just before line L2 of the operation Processes P9..P12 perform up(S) and their up(S2) lines release, say, P1..P4 4 processes are waiting on S2 and S.wake = 4 Processes P1..P4 are ready, just before lines L3 Each of P1..P4 will decrement S.wake in its turn, check that it's positive and signal one of P5..P8 Four up(S) have released 8 down(S) 44 Operating Systems, 2011, Danny Hendler & Amnon Meisels 44 Implementing a counting semaphore with binary semaphores: take 4 (Hemmendinger, 1989) binary-semaphore S1=1, S2=0, integer value =1, integer wake=0 down(S) down(S1); S.value--; if(S.value < 0){ up(S1); down(S2); down(S1); S.wake--; if(S.wake > 0) then up(S2);} // L3 up(S1); 45 up(S): down(S1); S.value++; if(S.value <= 0) { S.wake++; if (S.wake == 1) up(S2); } up(S1); This works Operating Systems, 2011, Danny Hendler & Amnon Meisels 45 Implementing a counting semaphore with binary semaphores: take 5 (Barz, 1983) binary-semaphore S1=1, S2=min(1, init_value), value=init_value down(S) down(S2); down(S1); S.value--; if (S.value>0) then up(S2); up(S1); up(S): down(S1); S.value++; if(S.value == 1) { up(S2); } up(S1); This works, is simpler, and was published earlier(!)… Can we switch the order of downs in down(S)? 46 Operating Systems, 2011, Danny Hendler & Amnon Meisels 46 Correctness arguments… The critical section is guarded by S1 and each of the operations down(S) and up(S) uses it to correctly update the value of S.value After updating (and inside the critical section) both operations release the S2 semaphore only if value is positive S.value is never negative, because any process performing down(S) is blocked at S2 Signals cannot be 'wasted' 47 Operating Systems, 2011, Danny Hendler & Amnon Meisels 47 Fairness of semaphores Order of releasing blocked processes: o Fair o Processes are not allowed multiple entries if others are waiting Another option: o Open competition each time the lock is free o Imitating the Java ‘wait’ ‘notify’ mechanism o Starvation is possible 48 Operating Systems, 2011, Danny Hendler & Amnon Meisels 48 Counting and Binary semaphores (open contest) binary-semaphore S1 =1, S2=0; int S.value=k, S.wait=0, S.wake=0 down(S): down(S1); while(S.value == 0){ S.wait++; up(S1); down(S2); down(S1); S.wait--; S.wake--; } if(S.wake > 0) then up(S2); S.value--; up(S1); 49 up(S): down(S1); if (S.wait > 0 && S.wake < S.wait) { S.wake++; if (S.wake==1) up(S2); } S.value++ up(S1) Operating Systems, 2011, Danny Hendler & Amnon Meisels 49 Outline Semaphores and the producer/consumer problem Counting semaphores from binary semaphores Event counters and message passing synchronization 50 Operating Systems, 2011, Danny Hendler & Amnon Meisels 50 Event Counters Integer counters with three operations: o Advance(E): increment E by 1, wake up relevant sleepers o Await(E,v): wait until E ≥ v. Sleep if E < v o Read(E): return the current value of E Counter value is ever increasing The Read() operation is not required for the bounded-buffer implementation in the next slide 51 Operating Systems, 2011, Danny Hendler & Amnon Meisels 51 producer-consumer with Event Counters #define typedef event_counter event_counter N int in = 0; out = 0; 100 event_counter; /* counts inserted items */ /* items removed from buffer */ void producer(void) { int item, sequence = 0; while(TRUE) { produce_item(&item); sequence = sequence + 1; /* counts items produced */ await(out, sequence - N); /* wait for room in buffer */ enter_item(item); /* insert into buffer */ advance(&in); /* inform consumer */ } } 52 Operating Systems, 2011, Danny Hendler & Amnon Meisels 52 Event counters (producer-consumer) void consumer(void) { int item, sequence = 0; while(TRUE) { sequence = sequence + 1; /* count items consumed */ await(in, sequence); /* wait for item */ remove_item(&item); /* take item from buffer */ advance(&out); /* inform producer */ consume_item(item); } } 53 Operating Systems, 2011, Danny Hendler & Amnon Meisels 53 Message Passing – no shared memory In a multi-processor system without shared memory, synchronization can be implemented by message passing Implementation issues: o Acknowledgements may be required (messages may be lost) o Message sequence numbers required to avoid message duplication o Unique process addresses across CPUs (domains..) o Authentication (validate sender’s identity, a multi-machine environment…) Two main functions: o send(destination, &message); o receive(source, &message) block while waiting... 54 Operating Systems, 2011, Danny Hendler & Amnon Meisels 54 Producer-consumer with Message Passing #define N #define MSIZE 100 4 typedef int message(MSIZE); void producer(void) { int item; message m; while(TRUE) { produce_item(&item); receive(consumer, &m); construct_message(&m, item); send(consumer, &m); } /* message size */ /* message buffer */ /*wait for an empty */ /* send item */ } 55 Operating Systems, 2011, Danny Hendler & Amnon Meisels 55 Message passing (cont.) void consumer(void) { int item, i; message m; for(i = 0; i < N; i++) send(producer, &m); /* send N empties */ while(TRUE) { receive(producer, &m); extract_item(&m, &item); send(producer, &m); consume_item(item); } } 56 /* get message with item */ /* send an empty reply */ Operating Systems, 2011, Danny Hendler & Amnon Meisels 56 Message passing variations Messages can be addressed to a process address or to a mailbox o Mailboxes are generated with some capacity. When sending a message to a full mailbox, a process blocks o Buffer management done by mailbox Unix pipes - a generalization of messages … no fixed size message (blocking receive) If no buffer is maintained by the system, then send and receive must run in lock-step. Example: Unix rendezvous 57 Operating Systems, 2011, Danny Hendler & Amnon Meisels 57