Spinlock Vs. Sleeping Lock Spinlock Implementation(1)
Total Page:16
File Type:pdf, Size:1020Kb
Semaphores EECS 3221.3 • Problems with the software solutions. Operating System Fundamentals – Complicated programming, not flexible to use. – Not easy to generalize to more complex synchronization problems. No.6 • Semaphore (a.k.a. lock): an easy-to-use synchronization tool – An integer variable S Process Synchronization(2) – wait(S) { while (S<=0) ; S-- ; Prof. Hui Jiang } Dept of Electrical Engineering and Computer – signal(S) { Science, York University S++ ; } Semaphore usage (1): Semaphore usage (2): the n-process critical-section problem as a General Synchronization Tool • The n processes share a semaphore, Semaphore mutex ; // mutex is initialized to 1. • Execute B in Pj only after A executed in Pi • Use semaphore flag initialized to 0 Process Pi do { " wait(mutex);! Pi Pj " critical section of Pi! ! … … signal(mutex);" A wait (flag) ; signal (flag) ; B remainder section of Pi ! … … ! } while (1);" " " Spinlock vs. Sleeping Lock Spinlock Implementation(1) • In uni-processor machine, disabling interrupt before modifying • Previous definition of semaphore requires busy waiting. semaphore. – It is called spinlock. – spinlock does not need context switch, but waste CPU cycles wait(S) { in a continuous loop. do { – spinlock is OK only for lock waiting is very short. Disable_Interrupt; signal(S) { • Semaphore without busy-waiting, called sleeping lock: if(S>0) { S-- ; Disable_Interrupt ; – In defining wait(), rather than busy-waiting, the process makes Enable_Interrupt ; S++ ; system calls to block itself and switch to waiting state, and return ; Enable_Interrupt ; put the process to a waiting queue associated with the } return ; semaphore. The control is transferred to CPU scheduler. Enable_Interrupt ; } – In defining signal(), the process makes system calls to pick a } while(1) ; process in the waiting queue of the semaphore, wake it up by } moving it to the ready queue to wait for CPU scheduling. – Sleeping Lock is good only for long waiting. 1 Spinlock Implementation(1) Spinlock Implementation(2) • In multi-processor machine, inhibiting interrupt of all • In uni-processor machine, disabling interrupt before modifying processors is not easy and efficient. semaphore. • Use software solution to critical-section problems – e.g., bakery algorithm. wait(S) { – Treat wait() and signal() as critical sections. do { • Or use hardware support if available: signal(S) { Disable_Interrupt; – TestAndSet() or Swap() if(S>0) { S-- ; Disable_Interrupt ; Enable_Interrupt ; S++ ; • Example: implement spinlock among two processes. return ; Enable_Interrupt ; – Use Peterson’s algorithm for protection. } return ; – Shared data: Enable_Interrupt ; } while(1) ; } } Semaphore S ; Initially S=1 boolean flag[2]; initially flag [0] = flag [1] = false. int turn; initially turn = 0 or 1. Spinlock Implementation(3) Spinlock Implementation(2) wait(S) { • In multi-processor machine, inhibiting interrupt of all int i=process_ID(); //0P0, 1P1 signal(S) { processors is neither easy nor efficient. int i=process_ID(); //0P0, 1P1 int j=(i+1)%2 ; • Use software solution to critical-section problems int j=(i+1)%2 ; do { – e.g., bakery algorithm. flag [ i ]:= true; //request to enter – Treat wait() and signal() as critical sections. flag [ i ]:= true; //request to enter turn = j; • Or use hardware support if available: while (flag [ j ] and turn = j) ; turn = j; while (flag [ j ] and turn = j) ; – TestAndSet() or Swap() if (S >0) { //critical section S--; S++; //critical section • Example: implement spinlock between N processes. flag [ i ] = false; – Use Bakery algorithm for protection. return ; flag [ i ] = false; – Shared data: } else { flag [ i ] = false; return ; Semaphore S ; Initially S=1 } } } while (1); boolean choosing[N]; (Initially false) } int number[N]; (Initially 0 ) Spinlock Implementation(3) Sleeping Lock (I) wait(S) { int i=process_ID(); signal(S) { int i=process_ID(); • Define a sleeping lock as a structure: choosing[ i ] = true; number[ i ] = max(number[0], number[1], choosing[ i ] = true; typedef struct { …, number [N – 1])+1; number[ i ] = max(number[0], number[1], int value; // Initialized to 1 choosing[ i ] = false; …, number [N – 1])+1; for (j = 0; j < N; j++) { choosing[ i ] = false; struct process *L; while (choosing[ j ]) ; for (j = 0; j < N; j++) { } semaphore; while ((number[ j ] != 0) && while (choosing[ j ]) ; (number[ j ],j)< (number[ i ],i)) ; while ((number[ j ] != 0) && } (number[ j ],j)< (number[ i ],i)) ; • Assume two system calls: if (S >0) { //critical section } S--; – block() suspends the process that invokes it. number[i] = 0; S++; //critical section – wakeup(P) resumes the execution of a blocked process P. return ; } number[i] = 0; number[i] = 0; • Equally applicable to multiple threads in one process. } while (1); return ; } } 2 Sleeping Lock (II) Two Types of Semaphores: Binary vs. Counting • Semaphore operations now defined as: • Binary semaphore (a.k.a. mutex lock) – integer value wait(S): can range only between 0 and 1; simpler to implement S.value--; by hardware. if (S.value < 0) { add this process to S.L; • Counting semaphore – integer value can range over an block(); unrestricted domain. } signal(S): • We can implement a counting semaphore S by using S.value++; two binary semaphore. if (S.value <= 0) { remove a process P from S.L; • Binary semaphore is normally used as mutex lock. wakeup(P); } • Counting semaphore can be used as shared counter, load controller, etc… Implementing counting semaphore Implementing S with two Binary Semaphores • wait(S) operation: wait_binary(S1); C--; if (C < 0) { • Data structures: signal_binary(S1); binary-semaphore S1, S2; wait_binary(S2); int C: } signal_binary(S1); • Initialization: • signal(S) operation: S1 = 1 wait_binary(S1); S2 = 0 C ++; C = initial value of semaphore S if (C <= 0) signal_binary(S2); else signal_binary(S1); Bounded-Buffer P-C Problem Classical Synchronization Problems • A producer produces some data for a consumer to consume. They share a bounded-buffer for data • The Bounded-Buffer P-C Problem transferring. • Shared memory: A buffer to hold at most n items • The Readers-Writers Problem • Shared data (three semaphores) • The Dining-Philosophers Problem Semaphore filled, empty; /*counting*/ Semaphore mutex; /* binary */ Initially: filled = 0, empty = n, mutex = 1 3 Bounded-Buffer Problem: Bounded-Buffer Problem: Producer Process Consumer Process do { do { … wait(filled) produce an item in nextp wait(mutex); … … wait(empty); remove an item from buffer to nextc wait(mutex); … … add nextp to buffer signal(mutex); … signal(empty); signal(mutex); … signal(filled); consume the item in nextc … } while (1); } while (1); The Readers-Writers Problem The 1st Readers-Writers Problem • Many processes concurrently access a data object • Use semaphore to implement 1st readers-writer problem – Readers: only read the data. – Writers: update and may write the data object. • Shared data: • Only writer needs exclusive access of the data. int readcount = 0 ; // keep track the number of readers // accessing the data object • The first readers-writers problem: – Unless a writer has already obtained permission to use the shared data, readers are always allowed to access data. Semaphore mutex = 1 ; // mutually exclusive access to // readcount among readers – May starve a writer. • The second readers-writer problem: Semaphore wrt = 1 ; // mutual exclusion to the data object // used by every writer – Once a writer is ready, the writer performs its write as soon as possible. //also set by the 1st reader to read the data – May starve a reader. // and clear by the last reader to finish reading The Dining-Philosophers Problem The 1st Readers-Writers Problem • Five philosophers are Reader Process Writer Process thinking or eating …! • Using only five …! chopsticks wait(mutex);" wait(wrt);" readcount++; "" • When thinking, no need for chopsticks. …! if (readcount == 1) wait(wrt);" writing is performed! • When eating, need two signal(mutex);" closest chopsticks. …! …! • Can pick up only one signal(wrt);" reading is performed! chopsticks … …! • Can not get the one wait(mutex);" already in the hand of a readcount--;" neighbor. if (readcount == 0) signal(wrt);" signal(mutex);" … 4 The Dining-Philosophers Problem: Semaphore Solution Incorrect Semaphore Usage • Represent each chopstick with a semaphore Semaphore chopstick[5]; // Initialized to 1 Mistake 1: Mistake 2: Mistake 3: Mistake 4: do {" Philosopher i … … … wait(chopstick[i]) ;" … (i=0,1,2,3,4) signal(mutex) ; wait(mutex) ; wait(mutex) ; wait(chopstick[(i+1) % 5]) ;" Critical … … … …" Section Critical Critical Critical … eat! Section Section Section …" signal(mutex) ; … … … signal(chopstick[i]);" wait(mutex) ; wait(mutex) ; signal(chopstick[(i+1) % 5]);" …" think! …" } while (1); Starvation and Deadlock double_rq_lock() in Linux Kernel • Starvation – infinite blocking. A process may never be double_rq_lock(struct runqueue *rq1, removed from the semaphore queue in which it is struct runqueue *rq2) suspended. { if (rq1 == rq2) • Deadlock – two or more processes are waiting infinitely for an event that can be caused by only one of the waiting spinlock(&rq1->lock); processes. else { • Let S and Q be two semaphores initialized to 1 if (rq1 < rq2) { P0 P1 spin_lock(&rq1->lock); wait(S); wait(Q); spin_lock(&rq2->lock); wait(Q); wait(S); } else { spin_lock(&rq2->lock); signal(S); signal(Q); spin_lock(&rq1->lock); signal(Q) signal(S); } } } Why not? double_rq_unlock() in Linux Kernel double_rq_lock(struct runqueue *rq1, struct runqueue *rq2)