Download presentation
Presentation is loading. Please wait.
Published byAxel Pfeiffer Modified over 6 years ago
1
CSCI 511 Operating Systems Chapter 5 (Part B) Mutual Exclusion, Semaphores
Dr. Frank Li
2
Objectives Hardware Support for Synchronization Higher-level Synchronization Abstractions Semaphores, monitors Three Classical Problems of Synchronization
3
Critical Section in OS design
Recall: Critical Section Race condition Preemptive kernel vs. non-preemptive kernel A preemptive kernel allows a process to be preempted while it’s running in kernel mode An non-preemptive kernel does NOT allow a process to be preempted while it’s running in kernel mode Q1. Can you give some examples ? As we know, many kernel mode processes (kernel code) may be active in the OS Q2. Which type of kernel has race condition? Q3. Why would anyone favor a preemptive kernel?
4
Three Requirements for Critical-Section Problem
Mutual Exclusion - If process Pi is executing in its critical section, then no other processes can be executing in their critical sections Progress - If no process is executing in its critical section and there exist some processes that wish to enter their critical section, then the selection of the processes that will enter the critical section next cannot be postponed indefinitely 3. Bounded Waiting - A bound must exist on the number of times that other processes are allowed to enter their critical sections after a process has made a request to enter its critical section and before that request is granted
5
Peterson’s Solution A classic software-based solution that is restricted to two processes / threads alternate execution. Only assume LOAD and STORE instructions are atomic; The two processes share two variables: int turn; indicates whose turn it is to enter the critical section. Boolean flag[2] indicate if a process is ready to enter the critical section. flag[i] = true implies that process Pi is ready.
6
Peterson’s Solution To enter the critical section, Pi sets flag[i] = true and then sets turn = j. do { flag[i] = TRUE; turn = j; while ( flag[j] && turn == j); CRITICAL SECTION flag[i] = FALSE; REMAINDER SECTION } while (TRUE); If both processes wants to enter, turn will be overwritten. The eventual value of turn decides which of the two processes is allowed to enter. Hand simulation to understand how it works … Two Problems: only works for two processes, lockstep progress Does this work for only one process running? NO
7
High-Level Picture The abstraction of threads is good:
Maintains sequential execution model Allows simple parallelism to overlap I/O and computation Unfortunately, too complicated to access state shared between threads Consider “too much milk” example Peterson’s solution is very limited. Implementing a concurrent program with only loads and stores would be tricky and error-prone Today, we’ll implement higher-level operations on top of atomic operations provided by hardware Develop a “synchronization toolbox” Explore some common programming paradigms
8
Where are we going with synchronization?
Hardware Higher-level API Programs Shared Programs Locks Semaphores Monitors Send/Receive Load/Store Disable Ints Test&Set Swap We are going to implement various higher-level synchronization primitives using atomic operations Everything is pretty painful if only atomic primitives are load and store Need to provide primitives useful at user-level
9
How to implement Locks? Lock: prevents someone from doing something
Lock before entering critical section and before accessing shared data Unlock when leaving, after accessing shared data Wait if locked Important idea: all synchronization involves waiting The quality of waiting matters! Do{ Acquire lock Critical Section Release lock Remainder section } while (true)
10
Naïve use of Interrupt Enable/Disable
How can we build multi-instruction atomic operations? Recall: dispatcher gets control in two ways. Internal: Thread does something to relinquish the CPU External: Interrupts cause dispatcher to take CPU On a uniprocessor, can avoid context-switching by: Avoiding internal events (no yield, no I/O, avoid page fault) Preventing external events by disabling interrupts Consequently, naïve Implementation of locks: LockAcquire { disable Ints; }//before critical section LockRelease { enable Ints; } //after critical section Problems with this approach: Can’t let user do this! Consider following: LockAcquire(); While(TRUE) {;} A heavy-weight solution Critical Sections might be arbitrarily long What happens with I/O or other important events? “Reactor about to meltdown. Help!”
11
Better Implementation of Locks by Disabling Interrupts
Key idea: maintain a lock variable and impose mutual exclusion only during operations on that variable int lock = FREE; Acquire() { disable interrupts; if (lock == BUSY) { put thread on wait queue; Go to sleep(); } else { lock = BUSY; } enable interrupts; } Release() { disable interrupts; if (anyone on wait queue) { take thread off wait queue Place on ready queue; } else { lock = FREE; } enable interrupts; } How long is the interrupt disabled? Only disable interrupt for a few instruction, not the whole critical section!
12
New Lock Implementation: Discussion
Why do we need to disable interrupts ? Avoid interruption between checking and setting lock value. Otherwise two threads could think that they both have lock Note: unlike previous solution, the critical section (inside Acquire()) is very short User of lock can take as long as they like in their own critical section: doesn’t impact global machine behavior. e.g. critical interrupts taken in time! Acquire() { disable interrupts; if (lock == BUSY) { put thread on wait queue; Go to sleep(); } else { lock = BUSY; } enable interrupts; } Critical Section
13
Atomic Read-Modify-Write Instructions
Problems with disabling interrupt solution: Can’t give lock implementation to users Doesn’t work well on multi-processor computers Disabling interrupts on all processors requires messages and would be very time consuming Alternative: atomic instruction sequences These instructions read a value from memory and write a new value atomically Hardware is responsible for implementing this correctly on both uni-processors (not too hard) and multiprocessors (requires help from cache coherence protocol)
14
Two Common Read-Modify-Write Operations
/* most architectures */ test&set (boolean *target) { boolean rv = *target; *target = true; return rv; } /* x86 */ Swap( boolean *a, boolean *b) { boolean temp = *a; *a = *b; *b = temp; } Note: they are supported by OS as system call
15
1st Lock implementation: with test&set
A simple solution: boolean lock = false; //Initially, lock is Free do{ while ( test&set(&lock) ) ; // while busy Critical section lock = false; }while( true ); Explanation: If lock is false (free), test&set reads false and sets true, so lock is now busy, while exits, and enter critical section. If lock is busy, test&set reads true and sets true (lock value has no change). It returns true, so while loop continues. When a thread leaves critical section and releases the lock (set it to false) , another thread (maybe still in while loop) can get that lock. Busy-Waiting: thread consumes cycles while waiting Spinning till the time quota for this thread is off Very inefficient
16
Can we build test&set locks without busy-waiting?
2nd Lock using test&set Can we build test&set locks without busy-waiting? Can’t entirely, but can minimize! Idea: only busy-wait to atomically check lock value Note: sleep has to be sure to reset the guard variable Why can’t we do it just before or just after the sleep? int guard = 0; int lock = FREE; Acquire() { while (test&set(guard)) ; if (lock == BUSY) { put thread on wait queue; go to sleep() & guard = 0; } else { lock = BUSY; guard = 0; } } Release() { while (test&set(guard)) ; if anyone on wait queue { take thread off wait queue Place on ready queue; } else { lock = FREE; } guard = 0; The thread is only busy-waiting when another thread, which gets the guard, is checking the lock.
17
Another Bounded-Waiting lock: with Test&Set
The first lock does NOT satisfy the bounded-waiting requirement. Recall: A bound must exist on the number of times that other processes are allowed to enter their critical sections after a process has made a request to enter its critical section and before that request is granted Solution and explanation (pp 199 – 200)
18
2nd implementation: Bounded-Waiting Locks with Test&Set
do{ waiting[i] = true; key = true; while (waiting[i] && key) key = TestAndSet( &lock ); waiting[i] =false; //Critical Section j = (i + 1) % n; while ( (j != i) && !waiting[j] ) j = (j+1) % n; if (j == i) lock = false; else waiting[j] = false; //Remainder Section } while (true); //two global data boolean waiting [n]; //initialize all to false boolean lock; //initialize to false thread i enters critical section only if either waiting[i] == false or key == false. Key can become false only if TestAndSet is executed. Waiting[i] can become false only if another thread leaves its critical section.
19
Higher-level Primitives than Locks
Goal of last couple of lectures: What is the right abstraction for synchronizing threads that share memory? Programmers want as high a level primitive as possible Good primitives and practices important Semaphore, Monitors
20
Semaphores Semaphores are a kind of generalized lock
First defined by Dijkstra in late 60’s Main synchronization primitive used in original UNIX Definition: a Semaphore has a non-negative integer value and supports the following two operations: P and V P( ): an atomic operation that waits for semaphore to become positive, then decrements it by 1 wait( ) operation V( ): an atomic operation that increments the semaphore by 1, waking up a waiting P, if any signal() operation (P stands for “proberen” (to test) and V stands for “verhogen” (to increment) in Dutch) P: if semaphore is zero, go to sleep, until be awaken
21
Semaphores are like integers, except
Semaphore Operations Semaphores are like integers, except No negative values Only operations allowed are P and V – can’t read or write value, except to set it initially Operations are atomic ! Two P’s together can’t decrement value below zero Similarly, thread going to sleep in P won’t miss wakeup from V – even if they both happen at same time Semaphore from railway analogy A railway with two arms, a semaphore initialized to ? for resource control Value=0
22
The Uses of Semaphores “Mutex lock”, (initial value = 1)
Binary semaphore: “Mutex lock”, (initial value = 1) Used for mutual exclusion: wait( mutex ); //Critical section signal( mutex ); Counting semaphore: integer value can range over an unrestricted domain Scheduling Constraints E.g., Synchronize two processes: S1 is a statement in P1, S2 is a statement in P2. To assure S1 is executed before S2 mutex = 0; //a semaphore shared … S1; //in P1 signal( mutex ); … wait( mutex ); //in P2 S2;
23
Semaphore Implementation (1)
After initialization, a semaphore can only be accessed via two indivisible (atomic) operations All the modification to the integer value of the semaphore must be executed indivisibly wait (S) { while (S <= 0); S--; } signal (S) { S++; This is called spinlock. (busy waiting) The process “spins” while waiting for the lock Is this implementation good or bad? Good: no context switch, if critical section is short, or having multiple CPUs (one process waits on a spinlock at a CPU, another process holds the lock running at another CPU) Bad: if critical section is long, or only get one CPU
24
Semaphore Implementation (2)
In the 2nd implementation, with each semaphore there is an associated waiting queue. Each entry in a waiting queue has two data items: value (an integer) pointer to next process/thread in the waiting queue typedef struct { int value; struct process *list; } semaphore; Two operations: (system calls provided by OS) block suspends the process that invokes it, and place the process on the appropriate waiting queue. wakeup remove one of processes in the waiting queue and place it in the ready queue.
25
Semaphore Implementation (3)
wait (semaphore *S){ S value--; //any difference ? if ( S value < 0) { add this process to S list; block( ); } } signal (semaphore *S){ S value++; if (Svalue < = 0) { //any difference ? remove a process P from S list; wakeup(P); } Must disable interrupt before enter wait( ) and signal( ). We have NOT completely eliminated busy waiting.
26
Deadlock and Starvation
Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes e.g., Let S and Q be two semaphores initialized to 1 P0 P1 wait (S); wait (Q); wait (Q); wait (S); signal (S); signal (Q); signal (Q); signal (S); Starvation – indefinite blocking. A process may never be removed from the semaphore queue in which it is suspended. e.g., if the waiting queue using LIFO, this might cause ..
27
Three Classical Problems of Synchronization
Bounded-Buffer Problem Readers and Writers Problem Dining-Philosophers Problem
28
1) Producer-Consumer problem with a bounded buffer
Problem Definition Producer puts things into a shared buffer Consumer takes things out the shared buffer Don’t want producer and consumer to have to work in lockstep, so put a fixed-size buffer between them Need to synchronize access to this buffer Producer needs to wait if buffer is full Consumer needs to wait if buffer is empty e.g 1: GCC compiler cpp | cc1 | cc2 | as | ld e.g 2: Coke machine Producer can put limited number of cokes in machine Consumer can’t take cokes out if machine is empty
29
Analysis of Constraints
Correctness Constraints: Consumer must wait for producer to fill buffers, if none of slot is fill (scheduling constraint) Producer must wait for consumer to empty buffers, if all slots are full (scheduling constraint) Only one thread can manipulate buffer at a time (mutual exclusion) How to use semaphore in this case? Rule of thumb: Use a separate semaphore for each constraint!!! // consumer’s constraint Semaphore full; // producer’s constraint Semaphore empty; // mutual exclusion Semaphore mutex;
30
The Solution to Bounded Buffer
Semaphore full = 0; // Initially, empty buffer Semaphore empty = numBuffers; // Initially, num of empty slots Semaphore mutex = 1; // No one access buffer Consumer do { wait (full); wait (mutex); //remove an item from buffer signal (mutex); signal (empty); // consume the removed item } while (true); Producer do { //produce an item wait (empty); wait (mutex); //add an item to buffer signal (mutex); signal (full); } while (true);
31
Discussion about Bounded-Buffer Problem
Why asymmetry? Producer does: wait (empty), signal (full), Consumer does: wait (full), signal (empty); Actually, it’s symmetric solution. Why? What if there are 2 producers or 2 consumers? Can we switch two statements wait (empty); and wait (mutex);
32
2) Readers-Writers Problem
A data set is shared among a number of concurrent processes Readers – only read the data set; they do not perform any updates Writers – can both read and write. Problem – allow multiple readers to read at the same time. Only one single writer can access the shared data at the same time. first readers-writers problem: No reader will be kept waiting unless a writer has already obtained the shared data set. Shared Data Data set Semaphore wrt initialized to 1. Semaphore mutex initialized to 1. Integer readcount initialized to 0. //keep track of how many processes are reading the data set
33
Solution of Readers-Writers Problem
The structure of a writer process do{ wait (wrt) ; //writing is performed signal (wrt) ; } while (true) The structure of a reader process do{ wait (mutex) ; readcount ++ ; if (readercount == 1) wait (wrt) ; signal (mutex) // reading is performed readcount - - ; if( redacount == 0) signal (wrt) ; signal (mutex) ; } while (true)
34
Discussion of Readers-Writers Problem
Semaphore wrt is common to both reader and writer Mutual exclusion for the writers Also used by the 1st and last reader that enters / exits the critical section Semaphore mutex ensures mutual exclusion on variable readcount Reader-writer lock: a generalized lock to provide a solution of reader-writer problem on some systems Multiple processes could acquire read mode of Reader-writer lock; while only one process can acquire write mode of Reader-writer lock;
35
Discussion of Readers-Writers Problem
The 1st readers-writers problem: writers may starve why? The 2nd readers-writers problem Once a writer is ready, that writer performs its write as soon as possible. If a writer is waiting to access the shared data, no new readers may start reading readers may starve
36
3) Dining-Philosophers Problem
Why this is a important / interesting problem? A simple representation of the need to allocate several resources among several processes Shared data Bowl of rice (data set) Semaphore chopstick [5] initialized to 1
37
Solution of Dining-Philosophers Problem
The procedure of Philosopher i Do { wait ( chopstick[i] ); wait ( chopStick[ (i + 1) % 5] ); // eat signal ( chopstick[i] ); signal (chopstick[ (i + 1) % 5] ); // think } while (true) ; Q: Any problem with this solution?
38
Remedies to the deadlock at Dining-Philosophers Problem
Allow at most 4 philosophers to be sitting Allow a philosopher picks up chopsticks only if both chopsticks are available (must pick them up in a critical section) Odd people picks left chopstick first, then the right chopstick; Even people does that in the reverse order. Note: A deadlock-free solution does NOT necessarily eliminate the possibility of starvation.
39
Pitfall of using Semaphores
Correct use of semaphore operations: What if inverse the order signal (mutex) …. wait (mutex) How about … … Or omitting of wait (mutex) or signal (mutex) (or both)
40
Important concept: Atomic Operations
In Conclusion … Important concept: Atomic Operations An operation that runs to completion or not at all These are the primitives on which to construct various synchronization primitives Hardware supported atomic primitives: Disabling of interrupts, test&set, and swap Showed several constructions of Locks Must be very careful not to waste/tie up machine resources Shouldn’t disable interrupts for long Shouldn’t busy wait for long Key idea: Separate lock variable, use hardware mechanisms to protect modifications of that variable Semaphore – a higher level synchronization primitive
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.