operating systems principles process management and coordination lecture 3: higher-level...

Post on 18-Jan-2016

251 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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; } } }}

top related