1.05k likes | 1.26k Views
Operating Systems Principles Process Management and Coordination Lecture 3: Higher-Level Synchronization and Communication. 主講人:虞台文. Content. Motivation Shared Memory Methods Monitors Protected Types Distributed Synchronization/Communication Message-Based Communication
E N D
Operating Systems PrinciplesProcess Management and CoordinationLecture 3:Higher-Level Synchronization and Communication 主講人:虞台文
Content • Motivation • Shared Memory Methods • Monitors • Protected Types • Distributed Synchronization/Communication • Message-Based Communication • Procedure-Based Communication • Distributed Mutual Exclusion • Other Classical Problems • The Readers/Writers Problem • The Dining Philosophers Problem • The Elevator Algorithm • Event Ordering with Logical Clocks
Operating Systems PrinciplesProcess Management and CoordinationLecture 3:Higher-Level Synchronization and Communication Motivation
Motivation • Semaphores and Events • Powerful but low-level abstractions • Such programs are difficult to design, debug, and maintain • Programming with them is highly error prone, e.g., deadlock • Insecure for share memory • Unusable in distributed systems • Need higher-level primitives • Based on semaphores or messages
Solutions • High-level share memory models • Monitors • Protected Types • Distributed schemes for interprocess communication/Synchronization • Message-Based Communication • Procedure-Based Communication • Distributed Mutual Exclusion
Operating Systems PrinciplesProcess Management and CoordinationLecture 3:Higher-Level Synchronization and Communication Share Memory Methods
Monitors • Higher level construct than semaphores. • A package of grouped procedures, variables and data, i.e., object oriented. • Processes call procedures within a monitor but cannot access internal data. • Can be built into programming languages, e,g., • Mesa from Xerox was used to build a real operating system (Pilots). • Synchronization enforced by the compiler. • Only one process allowed within a monitor at one time. • wait and signal operations on condition variables.
Internal Data Condition Variables Procedure 1 Procedure 2 Procedure 3 The Monitor Abstraction Shared among processes Processes cannot access them directly wait/signal primitives for processes communication or synchronization. Processes access the internal data only through these procedures. Procedure are mutually exclusive, i.e., only one process or thread may be executing a procedure within a given time.
Example: Queue Handler AddToQueue RemoveFromQueue Queue
Since only one process may be executing a procedure within a given time, mutual exclusion is assured. Example: Queue Handler Using C-like Pseudo code. • monitor QueueHandler { • struct Queue queue; • void AddToQueue( int val ) • { • … add val to end of queue … • } /* AddToQueue */ • int RemoveFromQueue() • { • … remove value from queue, return it … • } /* RemoveFromQueue */ • };
How about a process call RemoveFromQueue when the queue is empty? Process Synchronization • monitor QueueHandler { • struct Queue queue; • void AddToQueue( int val ) • { • … add val to end of queue … • } /* AddToQueue */ • int RemoveFromQueue() • { • … remove value from queue, return it … • } /* RemoveFromQueue */ • };
How about the procedure that makes the c.signal (c.notify) call? Question: sleep or keep running? Condition Variables • Monitors need more facilities than just mutual exclusion. Need some way to wait. • For coordination, monitors provide: c.wait • Calling process is blocked and placed on waiting queue associated with condition variable c c.signal (Hoare) c.notify (Mesa) • Calling process wakes up first process on c queue
Variations on Semantics wait/signal wait/notify • Hoare semantics: awakened process gets monitor lock immediately. • Process doing the signal gets “thrown out” temporarily. • Probably need to signal as the last thing in the monitor (Hansen). • Mesa semantics: signaler keeps monitor lock. • Awakened process waits for monitor lock with no special priority (a new process could get in before it). • This means that the event it was waiting for could have come and gone: must check again (use a loop) and be prepared to wait again if someone else took it. • Signal and broadcast are therefore hints rather than guarantees.
More on Condition Variables • “Condition variable” c is not a conventional variable • c has no value • c is an arbitrary name chosen by programmer to designate an event, state, or condition • Each c has a waiting queue associated • A process may “block” itself on c -- it waits until another process issues a signal on c
Example: Queue Handler • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • }; One must ensure that the queue is not full before adding the item. An item is available here. One must ensure that the queue is nonempty before remove an item. A free node is available here.
An event denotes that data item is available in the queue. Example: Queue Handler An event denotes that some more item can be added to the queue. • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • }; One must ensure that the queue is not full before adding the item. An item is available here. One must ensure that the queue is nonempty before remove an item. A free node is available here.
Example: Queue Handler • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • }; An item is available here. One must ensure that the queue is nonempty before remove an item. A free node is available here.
Example: Queue Handler • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • }; An item is available here. A free node is available here.
Example: Queue Handler • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • }; A free node is available here.
Example: Queue Handler • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • };
p1 call AddtoQueue 1 p1 is blocked on freenodeAvail 2 Queue is full 5 P1 continues p2 call RemoveFromQueue 3 P1 terminates 6 P2 Signals freenodeAvail event 4 P2 is blocked 5’ P2 continues 7 Hoare Monitors • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • };
0 1 2 . . . . . . . . . n2 n1 Example: Bounded Buffer Deposit Remove
0 1 2 . . . nextout . . . . . . count nextin . . . n2 n1 Example: Bounded Buffer Deposit Remove
nextout count nextin Example: Bounded Buffer Remove Deposit
Example: Bounded Buffer • monitor BoundedBuffer { • char buffer[n]; • int nextin=0, nextout=0, count=0; • condition notempty, notfull; • deposit(char data) { • if (count==n) notfull.wait; • buffer[nextin] = data; • nextin = (nextin+1) % n; • count = count+1; • notempty.signal; • } • remove(char data) { • if (count==0) notempty.wait; • data = buffer[nextout]; • nextout = (nextout+1) % n; • count = count - 1; • notfull.signal; • } • };
Priority Waits • Hoare monitor signal resumes longest waiting process. • Not always what one wants, so Hoare introduced “Priority Waits” (aka “conditional” or “scheduled”): c.wait(p) • p is an integer (priority) • Blocked processes are kept sorted by p c.signal • Wakes up process with lowest p
Example: Alarm Clock . . . The current time (now) of the alarm clock is increased periodically (tick). 0 1 3 2 4 5 Wakeup Queue The wakeup queue is used to hold processes to be waken up orderly according to their wakeup time (alarm).
Call at time 150 Example: Alarm Clock p1: . . . AlarmClock.Wakeme(100); . . . Wakeup Queue p1(250)
Call at time 200 Call at time 150 Example: Alarm Clock p1: . . . AlarmClock.Wakeme(100); . . . p2: . . . AlarmClock.Wakeme(150); . . . Wakeup Queue p1(250) p2(350) p1(250)
Call at time 210 Call at time 200 Call at time 150 Example: Alarm Clock p1: . . . AlarmClock.Wakeme(100); . . . p2: . . . AlarmClock.Wakeme(150); . . . Wakeup Queue p3: . . . AlarmClock.Wakeme(30); . . . p1(250) p2(350) p1(250) p3(240)
Call at time 210 Call at time 230 Call at time 200 Call at time 150 Example: Alarm Clock p1: . . . AlarmClock.Wakeme(100); . . . p2: . . . AlarmClock.Wakeme(150); . . . Wakeup Queue p3: . . . AlarmClock.Wakeme(30); . . . p1(250) p2(350) p1(250) p4: . . . AlarmClock.Wakeme(10); . . . p3(240) p4(240)
Example: Alarm Clock • monitor AlarmClock { • int now=0; • condition wakeup; • wakeme(int n) { • int alarm; • alarm = now + n; • while (now<alarm) wakeup.wait(alarm); • wakeup.signal; • } • tick() { • /*invoked automatically by hardware*/ • now = now + 1; • wakeup.signal; • } • }
Call at time 210 Call at time 230 Call at time 200 Call at time 150 Example: Alarm Clock p1: . . . AlarmClock.Wakeme(100); . . . p2: . . . AlarmClock.Wakeme(150); . . . Wakeup Queue p3: . . . AlarmClock.Wakeme(30); . . . p1(250) p2(350) p1(250) p4: . . . AlarmClock.Wakeme(10); . . . p3(240) p4(240)
Mesa and Java Monitors • notifyis a variant of signal • After c.notify: • Calling process continues • Woken-up process continues when caller exits • Problems • Caller may wake up multiple processes, e.g., Pi, Pj, Pk, … • Pi could change condition on which Pj was blocked
Mesa and Java Monitors P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . . P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; . . . . . . . .
Mesa and Java Monitors B1=FALSE P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . . P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return; P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return;
What action should P1 take? Continue or wait again? Mesa and Java Monitors B1=FALSE P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . . P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return; P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return;
What action should P1 take? Continue or wait again? Mesa and Java Monitors B1=FALSE P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . . P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return; P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return;
What action should P1 take? Continue or wait again? Solution Replaceiftowhile. B1=FALSE P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . . while P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return; P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return; while
AddToQueue RemoveFromQueue Queue Example: Queue Handler
Hoare monitors use `if’. Example: Queue Handler • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • if ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • if ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • };
Mesa monitors use `while’. Example: Queue Handler • monitor QueueHandler{ • struct Queue queue; • condition itemAvail, freenodeAvail; • void AddToQueue( int val ) { • while ( queue is full ) { • freenodeAvail.wait; • } • . . . add val to the end of the queue . . . • itemAvail.signal; • } /* AddToQueue */ • int RemoveFromQueue() { • while ( queue is empty ) { • itemAvail.wait; • } • . . . remove value from queue . . . • freenodeAvail.signal; • return value; • } /* RemoveFromQueue */ • };
Defined in the Ada95 language (ADA 1995). Protected Types • Special case of monitor where: • c.wait is the first operation of a procedure • c.signal is the last operation • Typical in producer/consumer situations • wait/signal combined into a when clause • when c forms a “barrier” or “guarded” • Procedure continues only when c is true
Example: Bounded Buffer • Protected body BoundedBuffer { • char buffer[n]; • int nextin=0, nextout=0, count=0; • entry deposit(char c) • when (count < n) /* guard */ { • buffer[nextin] = c; • nextin = (nextin + 1) % n; • count = count + 1; • } • entryremove(char c) • when (count > 0) /* guard */ { • c = buffer[nextout]; • nextout = (nextout + 1) % n; • count = count - 1; • } • }
Operating Systems PrinciplesProcess Management and CoordinationLecture 3:Higher-Level Synchronization and Communication Distributed Synchronization and Communication
Distributed Synchronization • Semaphore-based primitive • Requires Shared Memory • For Distributed Memory: • send(p,m) Send message m to process p • receive(q,m) Receive message from process q in variable m • Semantics of send and receive varyvery substantially in different systems.
Questions of Send/Receive • Does senderwait for message to be accepted? • Does receiverwait if there is no message? • Does sendername exactly one receiver? • Does receivername exactly one sender?
Little practical used no use, e.g., Some debugging software may like it. Types of Send/Receive Little practical used Little practical used