operating systems principles process management and coordination lecture 3: higher-level...
TRANSCRIPT
Operating Systems PrinciplesProcess Management and Coordination
Lecture 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 Coordination
Lecture 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 Coordination
Lecture 3:Higher-Level Synchronization and Communication
Share Memory Methods
Monitors Higher level construct than semaphores. A package of grouped procedures, variables and dat
a, i.e., object oriented. Processes call procedures within a monitor but can
not 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 ti
me. wait and signal operations on condition variables.
The Monitor Abstraction
Internal Data Internal Data
Condition VariablesCondition Variables
Procedure 1 Procedure 1
Procedure 2 Procedure 2
Procedure 3 Procedure 3
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
QueueAddToQueue RemoveFromQueue
Example: Queue Handler
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 */
};
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 */
};
Using C-like Pseudo code.
Since only one process may be executing a procedure within a given time, mutual exclusion is assured.
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 */
};
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?
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 cc.signal (Hoare)c.notify (Mesa)– Calling process wakes up first process on c queue
Question:
How about the procedure thatmakes the c.signal (c.notify) call?
sleep or keep running?
Variations on Semantics
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.
wait/signalwait/notify
More on Condition Variables
“Condition variable” c is not a conventional variable– c has no value– c is an arbitrary name chosen by programmer t
o designate an event, state, or condition– Each c has a waiting queue associated– A process may “block” itself on c -- it waits un
til another process issues a signal on c
Example: Queue Handlermonitor 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 */};
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 Handlermonitor 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 */};
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.
An event denotes that data item is available in
the queue.
An event denotes that some more item can be added to the
queue.
An event denotes that some more item can be added to the
queue.
Example: Queue Handlermonitor 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 */};
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 Handlermonitor 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 */};
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 Handlermonitor 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 */};
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 Handlermonitor 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 */};
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 */};
Hoare Monitorsmonitor 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 */};
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 */};
Queue is full
p1 callAddtoQueue
1
p2 callRemoveFromQueue
3
2 p1 is blocked onfreenodeAvail
5
P1continues
4 P2 Signals freenodeAvail event
5’ P2 is blocked
6 P1 terminates
7 P2continues
......
......
......
0
1
2
n1
n2
Example: Bounded Buffer
DepositDeposit RemoveRemove
......
......
......
0
1
2
n1
n2
Example: Bounded Buffer
DepositDeposit RemoveRemove...
nextin
nextout
count
Example: Bounded Buffercou
nt
nextout
nextinDepositDeposit
RemoveRemove
Example: Bounded Buffermonitor 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;
} };
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;
} };
......
......
......
0
1
2
n 1
n 2
...
...
nextin
nextout
countcount
Priority Waits
Hoare monitor signal resumes longest waiting process. Not always what one wants, so Hoare introduced “Pri
ority 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 Clockmonitor AlarmClock { int now=0; condition wakeup;
wakeme(int n) { int alarm; alarm = now + n; while (now<alarm) wakeup.wait(alarm); wakeup.signal; } tick() {/*invoked automagically by hardware*/ now = now + 1; wakeup.signal; }
}
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 automagically by hardware*/ now = now + 1; wakeup.signal; }
}
Mesa and Java Monitors
notify is 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
P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . .
P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return;
P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return;
B1=FALSE
Mesa and Java Monitors
P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . .
P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return;
P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return;
B1=FALSE
What action should P1 take?
Continue or wait again?
Mesa and Java Monitors
P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . .
P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return;
P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return;
B1=FALSE
What action should P1 take?
Continue or wait again?
Solution
P1: . . . . . . . . if(!B1) c1.wait; . . . . . . . .
P2: . . . . . . . . if(!B2) c2.wait; . . . . . . . . B1=FASLE; return;
P3: . . . . . . . . B1=B2=TRUE; c1.notify; c2.notify; . . . . . . . . return;
B1=FALSE
What action should P1 take?
Continue or wait again?
Replace if to while.
while
while
Example: Queue Handler
QueueAddToQueue RemoveFromQueue
Example: Queue Handlermonitor 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 */};
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 */};
Hoare monitors use `if’.
Example: Queue Handlermonitor 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 */};
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 */};
Mesa monitors use `while’.
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
Defined in the Ada95 language (ADA 1995).
Example: Bounded BufferProtected 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; }
entry remove(char c)
when (count > 0) /* guard */ { c = buffer[nextout]; nextout = (nextout + 1) % n; count = count - 1; }
}
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; }
entry remove(char c)
when (count > 0) /* guard */ { c = buffer[nextout]; nextout = (nextout + 1) % n; count = count - 1; }
}
Operating Systems PrinciplesProcess Management and Coordination
Lecture 3:Higher-Level Synchronization and Communication
Distributed Synchronization
andCommunication
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 vary
very substantially in different systems.
Questions of Send/Receive
Does sender wait for message to be
accepted?
Does receiver wait if there is no message?
Does sender name exactly one receiver?
Does receiver name exactly one sender?
send blocking/synchronous nonblocking/asynchronousexplicitnaming
send message m to receiver r
wait until accepted
send message m to receiver r
implicitnaming
broadcast message mwait until accepted
broadcast message m
receive blocking/synchronous nonblocking/asynchronousexplicitnaming
wait message from sender s if there is a message from sender s, then receive it;else proceed.
implicitnaming
wait message from any sender
if there is a message from any sender, then receive it;else proceed.
Types of Send/Receive
Types of Send/Receive
send blocking/synchronous nonblocking/asynchronousexplicitnaming
send message m to receiver r
wait until accepted
send message m to receiver r
implicitnaming
broadcast message mwait until accepted
broadcast message m
receive blocking/synchronous nonblocking/asynchronousexplicitnaming
wait message from sender s if there is a message from sender s, then receive it;else proceed.
implicitnaming
wait message from any sender
if there is a message from any sender, then receive it;else proceed.
receive blocking/synchronous nonblocking/asynchronousexplicitnaming
wait message from sender s if there is a message from sender s, then receive it;else proceed.
implicitnaming
wait message from any sender
if there is a message from any sender, then receive it;else proceed.
Types of Send/Receive
send blocking/synchronous nonblocking/asynchronousexplicitnaming
send message m to receiver r
wait until accepted
send message m to receiver r
implicitnaming
broadcast message mwait until accepted
broadcast message m
receive blocking/synchronous nonblocking/asynchronousexplicitnaming
wait message from sender s if there is a message from sender s, then receive it;else proceed.
implicitnaming
wait message from any sender
if there is a message from any sender, then receive it;else proceed.
Little practical used
Little practical used
Little practical used no use, e.g.,Some debugging software may like it.
Process Coordination
send blocking/synchronous nonblocking/asynchronousexplicitnaming
send message m to receiver r
wait until accepted
send message m to receiver r
implicitnaming
broadcast message mwait until accepted
broadcast message m
receive blocking/synchronous nonblocking/asynchronousexplicitnaming
wait message from sender s if there is a message from sender s, then receive it;else proceed.
implicitnaming
wait message from any sender
if there is a message from any sender, then receive it;else proceed.
Little practical used
Little practical used
Solving a variety of process
coordination problems.
Example:Printer Sever
send blocking/synchronous nonblocking/asynchronousexplicitnaming
send message m to receiver r
wait until accepted
send message m to receiver r
implicitnaming
broadcast message mwait until accepted
broadcast message m
receive blocking/synchronous nonblocking/asynchronousexplicitnaming
wait message from sender s if there is a message from sender s, then receive it;else proceed.
implicitnaming
wait message from any sender
if there is a message from any sender, then receive it;else proceed.
Little practical used
Little practical used
Implementation forAsynchronous Operations
send blocking/synchronous nonblocking/asynchronousexplicitnaming
send message m to receiver r
wait until accepted
send message m to receiver r
implicitnaming
broadcast message mwait until accepted
broadcast message m
receive blocking/synchronous nonblocking/asynchronousexplicitnaming
wait message from sender s if there is a message from sender s, then receive it;else proceed.
implicitnaming
wait message from any sender
if there is a message from any sender, then receive it;else proceed.
Little practical used
Little practical used
built-in buffers are required to hold messages
Channels, Ports, and Mailboxes
Allow indirect communication:– Senders/Receivers name channel instead of
processes
– Senders/Receivers determined at runtime Sender does not need to know who receives the
message
Receiver does not need to know who sent the message
Named Message Channels
Named Pipe (Win32)
ch1
ch2
P1: . . . . . . . . send(ch1, msg1); . . . . . . . .
P2: . . . . . . . . send(ch2, msg2); . . . . . . . .
P3: . . . . . . . . receive(ch1, x); . . . . . . . . receive(ch2, y); . . . . . . . .
CSP/Occam
CSP: Communicating Sequential ProcessesOccam: a Language
Using Named channel, say, ch1 to connect processes, say, p1 and p2– p1 sends to p2 using: send(ch1,’a’)– p2 receives from p1 using: receive(ch1,x)– Guarded commands:
when (c) s Set of statements s executed only when c is true Allow processes to receive messages selectively
based on arbitrary conditions
CSP/Occam
CSP: Communicating Sequential ProcessesOccam: a Language
Using Named channel, say, ch1 to connect processes, say, p1 and p2– p1 sends to p2 using: send(ch1,’a’)– p2 receives from p1 using: receive(ch1,x)– Guarded commands:
when (c) s Set of statements s executed only when c is true Allow processes to receive messages selectively
based on arbitrary conditions
Bounded buffer with CSP
Communicating Sequential Processes:– Buffer B– Producer P– Consumer C
Problems: – When Buffer full: B can only send to C– When Buffer empty: B can only receive from P– When Buffer partially filled: B must know
whether C or P is ready to act Solution:
– C sends request to B first; B then sends data– Inputs from P and C are guarded with when
countcount
nextoutnextout
nextinnextin
BB
PP
CC
Bounded buffer with CSPcountcount
nextoutnextout
nextinnextin
Buffer
Producer
Consumer
deposit
requestremove
Bounded buffer with CSPcountcount
nextoutnextout
nextinnextin
Buffer
Producer
Consumer
deposit
requestremove
send(deposit, data)
send(request)
receive(remove,data)
receive(request)
send(remove,data)
receive(deposit, data)
Bounded buffer with CSPcountcount
nextoutnextout
nextinnextin
Buffer
Producer
Consumer
deposit
requestremove
receive(request)
send(remove,data)
receive(deposit, data)
The Bounded Buffer uses the following three primitives for synchronization.
Bounded buffer with CSP
countcount
nextoutnextout
nextinnextin
Buff er
Producer
Consumer
deposit
requestremove
countcount
nextoutnextout
nextinnextin
Buff er
Producer
Consumer
deposit
requestremove
process BoundedBuffer { char buf[n]; int nextin=0, nextout=0, count=0; while (1) {
when ((count<n) && receive(deposit, buf[nextin])){ nextin = (nextin + 1) % n; count = count + 1;
} or when ((fullCount>0) && receive(reqest)){
send(remove, buf[nextout]); nextout = (nextout + 1) % n; count = count - 1;
} } }
process BoundedBuffer { char buf[n]; int nextin=0, nextout=0, count=0; while (1) {
when ((count<n) && receive(deposit, buf[nextin])){ nextin = (nextin + 1) % n; count = count + 1;
} or when ((fullCount>0) && receive(reqest)){
send(remove, buf[nextout]); nextout = (nextout + 1) % n; count = count - 1;
} } }
Put data into buffer if buffer not full and producer’s data is available.
Pass data to the consumer if it requests one.
Bounded buffer with CSP
countcount
nextoutnextout
nextinnextin
Buff er
Producer
Consumer
deposit
requestremove
countcount
nextoutnextout
nextinnextin
Buff er
Producer
Consumer
deposit
requestremove
process BoundedBuffer { char buf[n]; int nextin=0, nextout=0, count=0; while (1) {
when ((count<n) && receive(deposit, buf[nextin])){ nextin = (nextin + 1) % n; count = count + 1;
} or when ((fullCount>0) && receive(reqest)){
send(remove, buf[nextout]); nextout = (nextout + 1) % n; count = count - 1;
} } }
process BoundedBuffer { char buf[n]; int nextin=0, nextout=0, count=0; while (1) {
when ((count<n) && receive(deposit, buf[nextin])){ nextin = (nextin + 1) % n; count = count + 1;
} or when ((fullCount>0) && receive(reqest)){
send(remove, buf[nextout]); nextout = (nextout + 1) % n; count = count - 1;
} } }
Pass data to the consumer if it requests one.
Bounded buffer with CSP
countcount
nextoutnextout
nextinnextin
Buff er
Producer
Consumer
deposit
requestremove
countcount
nextoutnextout
nextinnextin
Buff er
Producer
Consumer
deposit
requestremove
process BoundedBuffer { char buf[n]; int nextin=0, nextout=0, count=0; while (1) {
when ((count<n) && receive(deposit, buf[nextin])){ nextin = (nextin + 1) % n; count = count + 1;
} or when ((count>0) && receive(reqest)){
send(remove, buf[nextout]); nextout = (nextout + 1) % n; count = count - 1;
} } }
process BoundedBuffer { char buf[n]; int nextin=0, nextout=0, count=0; while (1) {
when ((count<n) && receive(deposit, buf[nextin])){ nextin = (nextin + 1) % n; count = count + 1;
} or when ((count>0) && receive(reqest)){
send(remove, buf[nextout]); nextout = (nextout + 1) % n; count = count - 1;
} } }
More on Named Channelscountcount
nextoutnextout
nextinnextin
Buffer
Producer
Consumer
deposit
requestremove
• Each named channel serves for a particular purpose.
• Processes are connected through named channels directly.
Ports and Mailboxes
Port Mailboxes
Ports and Mailboxes
Processes are connected through intermediary.– Allowing a receiver to receive from multiple senders (non
deterministically).– Sending can be nonblocking.
The intermediary usually is a queue. The queue is called mailbox or port, depending o
n number of receivers:– mailbox can have multiple senders and receivers– port can have only one receiver
Ports and Mailboxes
Procedure-Based Communication
Send/Receive are too low level (like P/V)
Typical interaction among processes: – Send Request & (then) Receive Result.– Make this into a single higher-level primitive.
Use RPC (Remote Procedure Call) or Rendezvous– Caller invokes procedure on remote machine.– Remote machine performs operation and returns result.– Similar to regular procedure call, but parameters cannot
contain pointers because caller and server do not share any memory.
Procedure-Based Communication
Send/Receive are too low level (like P/V)
Typical interaction among processes: – Send Request & (then) Receive Result.– Make this into a single higher-level primitive.
Use RPC (Remote Procedure Call) or Rendezvous– Caller invokes procedure on remote machine.– Remote machine performs operation and returns result.– Similar to regular procedure call, but parameters cannot
contain pointers because caller and server do not share any memory.
In fact, it `can’ by doing
marshalling on
parameters.
In fact, it `can’ by doing
marshalling on
parameters.
RPC
Caller issues: res = f(params) This is translated into:
res = f(params)
// caller// client process
...send(RP,f,params);receive(RP,res);...
// caller// client process
...send(RP,f,params);receive(RP,res);...
// callee// server process
process RP_server { while (1) {
receive(C,f,params); res=f(params); send(C,res);
}}
// callee// server process
process RP_server { while (1) {
receive(C,f,params); res=f(params); send(C,res);
}}
Rendezvous
With RPC: – Called process p is part of a dedicated server– Setup is asymmetrical Client-sever relation
With Rendezvous:– p is part of an arbitrary process– p maintains state between calls– p may accept/delay/reject call– Setup is symmetrical: Any process may be a
client or a server
“Rendezvous” is French for “meeting.”
Pronunciation “RON-day-voo.”
Rendezvous
Caller Serverq.f(param) accept f(param) S
Name of the remote process(sever)
Procedure name
Procedure parameter
Procedure bodySimilar syntax/semantics to RPC
Keyword
Semantics of a Rendezvous
Caller Serverq.f(param) accept f(param) S
Caller or Server waits for the other. Then, they execute in parallel.
Semantics of a Rendezvous
Caller Serverq.f(param) accept f(param) S
p q
q.f()
accept f()
RendezvousS
p q
q.f()
accept f()
RendezvousS
Rendezvous: Selective Accept
select { [when B1:] accept E1(…) S1; or [when B2:] accept E2(…) S2; or . . . or
[when Bn:] accept En(…) Sn; [else R]}
• Ada provides a select statement that permits multiple accepts (guarded or not) to be active simultaneous.
• Only one could be selected nondeterministically upon Rendezvous.
Rendezvous: Selective Accept
[. . .]: optional
select { [when B1:] accept E1(…) S1; or [when B2:] accept E2(…) S2; or . . . or
[when Bn:] accept En(…) Sn; [else R]}
Example: Bounded Bufferprocess BoundedBuffer { char buffer[n]; int nextin=0, nextout=0, count=0;
while(1) { select { when (fullCount < n): accept deposit(char c) { buffer[nextin] = c; nextin = (nextin + 1) % n; count = count + 1; } or when (count > 0): accept remove(char c) { c = buffer[nextout]; nextout = (nextout + 1) % n; count = count - 1; } } }
}
To provide the following services forever:
1. deposit
2. remove
To provide the following services forever:
1. deposit
2. remove
Example: Bounded Bufferprocess BoundedBuffer { char buffer[n]; int nextin=0, nextout=0, count=0;
while(1) { select { when (count < n): accept deposit(char c) { buffer[nextin] = c; nextin = (nextin + 1) % n; count = count + 1; } or when (count > 0): accept remove(char c) { c = buffer[nextout]; nextout = (nextout + 1) % n; count = count - 1; } } }
}
Example: Bounded Buffer
process BoundedBuffer {char buffer[n];int nextin=0, nextout=0, count=0; while(1) {
select { when (count < n):
accept deposit(char c) { buffer[nextin] = c; nextin = (nextin + 1) % n; count = count + 1;
} or when (count > 0):
accept remove(char c) { c = buffer[nextout]; nextout = (nextout + 1) % n; count = count - 1;
} }
}}
process BoundedBuffer {char buffer[n];int nextin=0, nextout=0, count=0; while(1) {
select { when (count < n):
accept deposit(char c) { buffer[nextin] = c; nextin = (nextin + 1) % n; count = count + 1;
} or when (count > 0):
accept remove(char c) { c = buffer[nextout]; nextout = (nextout + 1) % n; count = count - 1;
} }
}}
BoundedBuffer.deposit(data)
BoundedBuffer.remove(data)
Distributed Mutual Exclusion
CS problem in a Distributed Environment– No shared memory, No shared clock,– Delays in message transmission.
Central Controller Solution– Requesting process sends request to controller– Controller grant it to one processes at a time– Problems:
Single point of failure, Performance bottleneck
Fully Distributed Solution:– Processes negotiate access among themselves– Very complex
Distributed Mutual Exclusion with Token Ring
A practical and elegant compromise version of fully distributed approach.
Distributed Mutual Exclusion with Token Ring
Controler[2]:P[2]: CS2; Program2;
Controler[3]:P[3]: CS3; Program3;
Controler[1]:P[1]: CS1; Program1;
tokentoken
tokentoken
RequestCS
ReleaseCS
RequestCS
ReleaseCS
RequestCS
ReleaseCS
Controler[2]:P[2]: CS2; Program2;
Controler[3]:P[3]: CS3; Program3;
Controler[1]:P[1]: CS1; Program1;
tokentoken
tokentoken
RequestCS
ReleaseCS
RequestCS
ReleaseCS
RequestCS
ReleaseCS
Distributed Mutual Exclusion with Token Ring
process controller[i] { while(1) { accept Token; select { accept Request_CS() {busy=1;} else null; } if (busy) accept Release_CS() {busy=0;} controller[(i+1) % n].Token; } } process p[i] { while(1) { controller[i].Request_CS(); CSi; controller[i].Release_CS(); programi; }}
process controller[i] { while(1) { accept Token; select { accept Request_CS() {busy=1;} else null; } if (busy) accept Release_CS() {busy=0;} controller[(i+1) % n].Token; } } process p[i] { while(1) { controller[i].Request_CS(); CSi; controller[i].Release_CS(); programi; }}
Do four possible jobs:1. Receive token2. Transmit token3. Lock token (RequestCS)4. Unlock token (ReleaseCS)
Controler[2]:P[2]: CS2; Program2;
Controler[3]:P[3]: CS3; Program3;
Controler[1]:P[1]: CS1; Program1;
tokentoken
tokentoken
RequestCS
ReleaseCS
RequestCS
ReleaseCS
RequestCS
ReleaseCS
Distributed Mutual Exclusion with Token Ring
process controller[i] { while(1) { accept Token; select { accept Request_CS() {busy=1;} else null; } if (busy) accept Release_CS() {busy=0;} controller[(i+1) % n].Token; } } process p[i] { while(1) { controller[i].Request_CS(); CSi; controller[i].Release_CS(); programi; }}
process controller[i] { while(1) { accept Token; select { accept Request_CS() {busy=1;} else null; } if (busy) accept Release_CS() {busy=0;} controller[(i+1) % n].Token; } } process p[i] { while(1) { controller[i].Request_CS(); CSi; controller[i].Release_CS(); programi; }}
Operating Systems PrinciplesProcess Management and Coordination
Lecture 3:Higher-Level Synchronization and Communication
Other Classical Problems
Database
Readers/Writers Problem
Database
Readers/Writers Problem
Writers can work only when no reader activated.One Writer can work at a time.
Database
Readers/Writers Problem
Readers can work only when no writer activated.Allows infinite number of readers.
Readers/Writers Problem
Extension of basic CS problem– (Courtois, Heymans, and Parnas, 1971)
Two types of processes entering a CS:– Only one Writer (W) may be inside CS, (exclusive) or– Many Readers (Rs) may be inside CS
Prevent starvation of either process type:– If Rs are in CS, a new R must not enter if W is waiting– If W is in CS, once it leaves, all Rs waiting should enter
(even if they arrived after new Ws)
Solution Using Monitormonitor Readers_Writers { int readCount=0, writing=0; condition OK_R, OK_W; start_read() { if (writing || !empty(OK_W)) OK_R.wait; readCount = readCount + 1; OK_R.signal; } end_read() { readCount = readCount - 1; if (readCount == 0) OK_W.signal; } start_write() { if ((readCount != 0)||writing) OK_W.wait; writing = 1; } end_write() { writing = 0; if (!empty(OK_R)) OK_R.signal; else OK_W.signal; }}
monitor Readers_Writers { int readCount=0, writing=0; condition OK_R, OK_W; start_read() { if (writing || !empty(OK_W)) OK_R.wait; readCount = readCount + 1; OK_R.signal; } end_read() { readCount = readCount - 1; if (readCount == 0) OK_W.signal; } start_write() { if ((readCount != 0)||writing) OK_W.wait; writing = 1; } end_write() { writing = 0; if (!empty(OK_R)) OK_R.signal; else OK_W.signal; }}
Called by a reader that wishes to read.Called by a reader that wishes to read.
Called by a reader that has finished reading.Called by a reader that has finished reading.
Called by a writer that wishes to write.Called by a writer that wishes to write.
Called by a writer that has finished writing.Called by a writer that has finished writing.
Solution Using Monitormonitor Readers_Writers { int readCount=0, writing=0; condition OK_R, OK_W; start_read() { if (writing || !empty(OK_W)) OK_R.wait; readCount = readCount + 1; OK_R.signal; } end_read() { readCount = readCount - 1; if (readCount == 0) OK_W.signal; } start_write() { if ((readCount != 0)||writing) OK_W.wait; writing = 1; } end_write() { writing = 0; if (!empty(OK_R)) OK_R.signal; else OK_W.signal; }}
monitor Readers_Writers { int readCount=0, writing=0; condition OK_R, OK_W; start_read() { if (writing || !empty(OK_W)) OK_R.wait; readCount = readCount + 1; OK_R.signal; } end_read() { readCount = readCount - 1; if (readCount == 0) OK_W.signal; } start_write() { if ((readCount != 0)||writing) OK_W.wait; writing = 1; } end_write() { writing = 0; if (!empty(OK_R)) OK_R.signal; else OK_W.signal; }}
Additional Primitive: empty(c)
Return true if the associated queue of c is empty.
Dining Philosophers
Dining Philosophers
Five philosophers sit around a circular table. In the centre of the table is a large plate of spaghetti. Each philosopher spends his life alternatively thinking and e
ating. A philosopher needs two forks to eat. Requirements
– Prevent deadlock– Guarantee fairness:
no philosopher must starve– Guarantee concurrency:
non-neighbors may eat at the same time
Dining Philosophers
p1
p2
p3 p4
p5
f1
f2
f3
f4
f5
p(i) { while (1) { think(i); grab_forks(i); eat(i); return_forks(i); }}
p(i) { while (1) { think(i); grab_forks(i); eat(i); return_forks(i); }}
grab_forks(i): P(f[i]); P(f[(i+1)%5]);
return_forks(i): V(f[i]); V(f[(i+1)%5]);
Easily lead to deadlock.
Solutions to deadlock
1. Use a counter: At most n 1 philosophers may attempt to grab forks.
2. One philosopher requests forks in reverse order,e.g., grab_forks(1): P(f[2]); P(f[1]);
3. Divide philosophers into two groups: Odd grab Left fork first, Even grab Right fork first
Logical Clocks
Many applications need to time-stamp events – for debugging, recovery, distributed mutual exclusion,
ordering of broadcast messages, transactions, etc.
Time-stamp allows us to determine the causality of events.– C(e1)<C(e2) means e1 happened before e2.
Global clock is unavailable for distributed systems.
Physical clocks in distributed systems are skewed.
The Problem of Clock Skewness
File User (U)
File Server (FS)
e1 e2
e3 e4
CU
CFS
send send
receive receive
delta1 delta2
True causality of events: 1 3 2 4e e e e
The log of events:
10 15
5 20
3 1 2 4e e e e
Impossible sequence!
Logical Clocks
ei es
ek er
send
receive
p1
p2ej
1( )p iL e1
( )p iL e
1 1( ) ( ) 1p s p iL e L e 1 1
( ) ( ) 1p s p iL e L e
2( )p jL e2
( )p jL e
2 2( ) ( ) 1p k p jL e L e 2 2
( ) ( ) 1p k p jL e L e
2 1 2( ) max ( ), ( ) 1p s p s p kL e L e L e
2 1 2( ) max ( ), ( ) 1p s p s p kL e L e L e
Logical Clocks in Action
1( ) 4pL u u
vmax(4,1) 1 2( )pL v 5
xmax(6,12) 1 3( )pL x 13
ymax(7,14) 1
2( )pL y 15
4
5
13
15
6
14
The Elevator Algorithm
The simple algorithm by which a single elevator can decide where to stop is:– Continue traveling in the same
direction while there are remaining requests in that same direction.
– If there are no further requests then change direction.
Scheduling hard disk requests.
The Elevator Algorithm
The Elevator Algorithm
request(i)
request(i)
direction = up
driection = down
Pressing button at floor i or button i inside elevator invokes: request(i)
Door closing, invokes: release()
Scheduler policy:– direction = up:
it services all requests at or above current position;then it reverses direction
– direction = down:it services all requests at or below current position;then it reverses direction
The Elevator Algorithm Using Priority Waits
monitor elevator { int dir=1, up=1, down=-1, pos=1, busy=0; condition upsweep, downsweep;
request(int dest) { /*Called when button pushed*/ if (busy) { if ((pos<dest) || ((pos==dest) && (dir==up))) upsweep.wait(dest); else downsweep.wait(-dest); } busy = 1; pos = dest; }
release() { /*Called when door closes*/ . . . . . . . . . . . . . . . } }
monitor elevator { int dir=1, up=1, down=-1, pos=1, busy=0; condition upsweep, downsweep;
request(int dest) { /*Called when button pushed*/ if (busy) { if ((pos<dest) || ((pos==dest) && (dir==up))) upsweep.wait(dest); else downsweep.wait(-dest); } busy = 1; pos = dest; }
release() { /*Called when door closes*/ . . . . . . . . . . . . . . . } }
Put the request into the proper priority queue and wait.Put the request into the proper priority queue and wait.
The Elevator Algorithm Using Priority Waits
monitor elevator { int dir=1, up=1, down=-1, pos=1, busy=0; condition upsweep, downsweep;
request(int dest) { /*Called when button pushed*/ if (busy) { if ((pos<dest) || ((pos==dest) && (dir==up))) upsweep.wait(dest); else downsweep.wait(-dest); } busy = 1; pos = dest; }
release() { /*Called when door closes*/ . . . . . . . . . . . . . . . } }
monitor elevator { int dir=1, up=1, down=-1, pos=1, busy=0; condition upsweep, downsweep;
request(int dest) { /*Called when button pushed*/ if (busy) { if ((pos<dest) || ((pos==dest) && (dir==up))) upsweep.wait(dest); else downsweep.wait(-dest); } busy = 1; pos = dest; }
release() { /*Called when door closes*/ . . . . . . . . . . . . . . . } }
The Elevator Algorithm Using Priority Waits
monitor elevator { int dir=1, up=1, down=-1, pos=1, busy=0; condition upsweep, downsweep; . . . . . . . . . . . . . . . release() { /*Called when door closes*/ busy = 0; if (dir==up) { if (!empty(upsweep)) upsweep.signal; else { dir = down; downsweep.signal; } } else { /*direction==down*/ if (!empty(downsweep)) downsweep.signal; else { dir = up; upsweep.signal; } } }}
monitor elevator { int dir=1, up=1, down=-1, pos=1, busy=0; condition upsweep, downsweep; . . . . . . . . . . . . . . . release() { /*Called when door closes*/ busy = 0; if (dir==up) { if (!empty(upsweep)) upsweep.signal; else { dir = down; downsweep.signal; } } else { /*direction==down*/ if (!empty(downsweep)) downsweep.signal; else { dir = up; upsweep.signal; } } }}