Improper Nesting Example - Lecture Notes | ECS 150, Study notes of Operating Systems

Material Type: Notes; Professor: Bishop; Class: Operating Systems; Subject: Engineering Computer Science; University: University of California - Davis; Term: Fall 2008;

Typology: Study notes

Pre 2010

Uploaded on 07/30/2009

koofers-user-rfx-1
koofers-user-rfx-1 🇺🇸

9 documents

1 / 14

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
ECS 150, Operating Systems Fall Quarter 2008
Improper Nesting Example
One of the limits on the use of parbegin/parend, and any related constructs, is that the program involved must
be properly nested. Not all programs are. For example, consider the program represented by the following graphs.
The Program as Graphs
Using fork/join Primitives
The program equivalent to these precedence and process flow graphs is:
t 6 : = 2 ;
t 8 : = 3 ;
S1 ; f o r k p2 ; f o r k p5 ; f o r k p7 ; q u i t ;
p2 : S2 ; f o r k p3 ; f o r k p4 ; q u i t ;
p5 : S5 ; j o i n t6 , p 6 ; qu i t ;
p7 : S7 ; j o i n t8 , p 8 ; qu i t ;
p3 : S3 ; j o i n t8 , p 8 ; qu i t ;
p4 : S4 ; j o i n t6 , p 6 ; qu i t ;
p6 : S6 ; j o i n t8 , p 8 ; qu i t ;
p8 : S8 ; q u i t
where Siis the program for pi.
Using parbegin/parend Primitives
To see if this is possible, we must determine if the above program is properly nested. If not, we clearly cannot represent
it using parbegin and parend, which require a block structure, and hence proper nesting.
Let S(a,b)represent the serial execution of processes aand b, and P(a,b)the parallel execution of processes a
and b. Then a process flow graph is properly nested if it can be described by P,S, and functional composition. For
example, the program
parbegin
p1 : a := b + 1;
p2 : c := d + 1;
parend
p3 : e : = a + c ;
would be written as S(P(p1,p2),p3).
We now prove:
Version of October 2, 2008 at 6:44pm Page 1 of 14
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe

Partial preview of the text

Download Improper Nesting Example - Lecture Notes | ECS 150 and more Study notes Operating Systems in PDF only on Docsity!

Improper Nesting Example

One of the limits on the use of parbegin / parend , and any related constructs, is that the program involved must

be properly nested. Not all programs are. For example, consider the program represented by the following graphs.

The Program as Graphs

Using fork/join Primitives

The program equivalent to these precedence and process flow graphs is:

t 6 : = 2 ; t 8 : = 3 ; S1 ; f o r k p2 ; f o r k p5 ; f o r k p7 ; q u i t ; p2 : S2 ; f o r k p3 ; f o r k p4 ; q u i t ; p5 : S5 ; j o i n t 6 , p6 ; q u i t ; p7 : S7 ; j o i n t 8 , p8 ; q u i t ; p3 : S3 ; j o i n t 8 , p8 ; q u i t ; p4 : S4 ; j o i n t 6 , p6 ; q u i t ; p6 : S6 ; j o i n t 8 , p8 ; q u i t ; p8 : S8 ; q u i t

where Si is the program for pi.

Using parbegin/parend Primitives

To see if this is possible, we must determine if the above program is properly nested. If not, we clearly cannot represent

it using parbegin and parend , which require a block structure, and hence proper nesting.

Let S(a, b) represent the serial execution of processes a and b, and P(a, b) the parallel execution of processes a

and b. Then a process flow graph is properly nested if it can be described by P, S, and functional composition. For

example, the program

p a r b e g i n p1 : a : = b + 1 ; p2 : c : = d + 1 ; parend p3 : e : = a + c ;

would be written as S(P(p 1 , p 2 ), p 3 ).

We now prove:

Claim. The example is not properly nested.

Proof. For something to be properly nested, it must be of the form S(pi, p j) or P(pi, p j) at the most interior level.

Clearly the examples most interior level is not P(pi, p j) as there are no constructs of that form in the graph. In the

graph, all serially connected processes pi and p j have at least 1 more process pk starting or finishing at the node ni j

between pi and p j. But if S(pi, p j) is in the innermost level, there can be no such pk (else a more interior P or S is

needed, contradiction). Hence, it is not S(pi, p j) either.

Classical Synchronization Problems

This handout states three classical synchronization problems that are often used to compare language constructs

that implement synchronization mechanisms and critical sections.

The Producer-Consumer Problem

In this problem, two processes, one called the producer and the other called the consumer, run concurrently and share

a common buffer. The producer generates items that it must pass to the consumer, who is to consume them. The

producer passes items to the consumer through the buffer. However, the producer must be certain that it does not

deposit an item into the buffer when the buffer is full, and the consumer must not extract an item from an empty buffer.

The two processes also must not access the buffer at the same time, for if the consumer tries to extract an item from

the slot into which the producer is depositing an item, the consumer might get only part of the item. Any solution to

this problem must ensure none of the above three events occur.

A practical example of this problem is electronic mail. The process you use to send the mail must not insert the

letter into a full mailbox (otherwise the recipient will never see it); similarly, the recipient must not read a letter from

an empty mailbox (or he might obtain something meaningless but that looks like a letter). Also, the sender (producer)

must not deposit a letter in the mailbox at the same time the recipient extracts a letter from the mailbox; otherwise, the

state of the mailbox will be uncertain.

Because the buffer has a maximum size, this problem is often called the bounded buffer problem. A (less common)

variant of it is the unbounded buffer problem, which assumes the buffer is infinite. This eliminates the problem of the

producer having to worry about the buffer filling up, but the other two concerns must be dealt with.

The Readers-Writers Problem

In this problem, a number of concurrent processes require access to some object (such as a file.) Some processes

extract information from the object and are called readers; others change or insert information in the object and are

called writers. The Bernstein conditions state that many readers may access the object concurrently, but if a writer is

accessing the object, no other processes (readers or writers) may access the object. There are two possible policies for

doing this:

1. First Readers-Writers Problem. Readers have priority over writers; that is, unless a writer has permission to access

the object, any reader requesting access to the object will get it. Note this may result in a writer waiting indefinitely

to access the object.

2. Second Readers-Writers Problem. Writers have priority over readers; that is, when a writer wishes to access the

object, only readers which have already obtained permission to access the object are allowed to complete their

access; any readers that request access after the writer has done so must wait until the writer is done. Note this

may result in readers waiting indefinitely to access the object.

So there are two concerns: first, enforce the Bernstein conditions among the processes, and secondly, enforcing the

appropriate policy of whether the readers or the writers have priority. A typical example of this occurs with databases,

when several processes are accessing data; some will want only to read the data, others to change it. The database

must implement some mechanism that solves the readers-writers problem.

The Dining Philosophers Problem

In this problem, five philosophers sit around a circular table eating spaghetti and thinking. In front of each philosopher

is a plate and to the left of each plate is a fork (so there are five forks, one to the right and one to the left of each

philosopher’s plate; see the figure). When a philosopher wishes to eat, he picks up the forks to the right and to the left

of his plate. When done, he puts both forks back on the table. The problem is to ensure that no philosopher will be

allowed to starve because he cannot ever pick up both forks.

There are two issues here: first, deadlock (where each philosopher picks up one fork so none can get the second)

must never occur; and second, no set of philosophers should be able to act to prevent another philosopher from ever

eating. A solution must prevent both.

Solving the Critical Section Problem for 2 Processes

This handout presents several proposed solutions to the 2 process critical section problem. We will analyze them

in class. In these solutions, one process is numbered 0 and the other is numbered 1. The variable i holds the number

corresponding to the process executing the code, and the variable j holds the number corresponding to the other

process. All the code shown is shared by both processes, but the variables i and j hold different values.

First Proposed Solution

Here, turn contains the number of the process whose turn it is to execute the critical section.

1 s ha re d i n t t u r n ; 2 l o o p { 3 w h i l e ( t u r n != i ) / ∗ do n o t h i n g ∗ / ; 4 / ∗ c r i t i c a l s e c t i o n h e r e ∗ / 5 t u r n = j ; 6 / ∗ r e m a i n d e r s e c t i o n h e r e ∗ / 7 }

Second Proposed Solution

Here, inCS[0] is true when process 0 is in the critical section, and false otherwise. A similar statement holds for

inCS[1].

1 s ha re d b o o l inCS [ 0.. 1 ] = { f a l s e , f a l s e } ; 2 l o o p { 3 w h i l e ( inCS [ j ] ) / ∗ do n o t h i n g ∗ / ; 4 inCS [ i ] = t r u e ; 5 / ∗ c r i t i c a l s e c t i o n h e r e ∗ / 6 inCS [ i ] = f a l s e ; 7 / ∗ r e m a i n d e r s e c t i o n h e r e ∗ / 8 }

Third Proposed Solution

Here, interested[0] is true when process 0 wants to enter the critical section, and false otherwise. A similar

statement holds for interested[1].

1 s ha re d b o o l i n t e r e s t e d [ 0.. 1 ] = { f a l s e , f a l s e } ; 2 l o o p { 3 i n t e r e s t e d [ i ] = t r u e ; 4 w h i l e ( i n t e r e s t e d [ j ] ) / ∗ do n o t h i n g ∗ / ; 5 / ∗ c r i t i c a l s e c t i o n h e r e ∗ / 6 i n t e r e s t e d [ i ] = f a l s e ; 7 / ∗ r e m a i n d e r s e c t i o n h e r e ∗ / 8 }

Fourth Proposed Solution

This combines the first and third proposed solutions.

1 s ha re d b o o l i n t e r e s t e d [ 0.. 1 ] ; = { f a l s e , f a l s e } ; 2 s ha re d i n t t u r n ; 3 l o o p { 4 i n t e r e s t e d [ i ] = t r u e ; 5 t u r n = j ; 6 w h i l e ( i n t e r e s t e d [ j ] && t u r n == j ) / ∗ do n o t h i n g ∗ / ; 7 / ∗ c r i t i c a l s e c t i o n h e r e ∗ / 8 i n t e r e s t e d [ i ] = f a l s e ; 9 / ∗ r e m a i n d e r s e c t i o n h e r e ∗ / 10 }

Test and Set Solution

This algorithm solves the critical section problem for n processes using a Test and Set instruction (called TaS here).

This instruction does the following function atomically:

b o o l TaS ( b o o l ∗ Lock ) { b o o l tmp = ∗ Lock ; ∗ Lock = t r u e ; r e t u r n ( tmp ) ; }

The solution is:

1 s ha re d b o o l w a i t i n g [ 0.. n −1] ; 2 s ha re d b o o l Lock ; 3 i n t ( 0.. n −1) j ; 4 b o o l key ; 5 l o o p { / ∗ p r o c e s s P i ∗ / 6 w a i t i n g [ i ] = t r u e ; 7 key = t r u e ; 8 w h i l e w a i t i n g [ i ] and key 9 key = TaS ( Lock ) ; 10 w a i t i n g [ i ] = f a l s e ; 11 / ∗ c r i t i c a l s e c t i o n h e r e ∗ / 12 j = i + 1 mod n ; 13 w h i l e j != i and n ot w a i t i n g [ j ] 14 j = ( j + 1 ) mod n ; 15 i f ( j == i ) 16 Lock = f a l s e ; 17 e l s e 18 w a i t i n g [ j ] = f a l s e ; 19 / ∗ r e m a i n d e r s e c t i o n h e r e ∗ / 20 }

lines 1–2: These are global to all processes, and are all initialized to false.

lines 3–4: These are local to each processi and are uninitialized.

lines 5–10: This is the entry section. Basically, waiting[i] is true as long as process i is trying to get into its critical

section; if any other process is in that section, then Lock will also be true, and process i will loop in lines 8-9. Once

process i can go on, it is no longer waiting for permission to enter, and sets waiting[i] to false (line 10); it then

proceeds into the critical section. Note that Lock is set to true by the TaS instruction in line 9 that returns false.

lines 12–18: This is the exit section. When process i leaves the critical section, it must choose which other waiting

process may enter next. It starts with the process with the next higher index (line 12). It checks each process to see if

that process is waiting for access (lines 13–14); if no-one is, it simply releases the lock (by setting Lock to false ;

lines 15–16). However, if some other process process j is waiting for entry, process i simply changes waiting[j] to

false to allow process j to enter the critical section (lines 17–18).

Producer/Consumer Problem with Semaphores

This algorithm uses semaphores to solve the producer/consumer (or bounded buffer) problem.

1 semaphore f u l l = 0 , empty = n , mutex = 1 ; 2 i t e m n e x t p , n e x t c ; 3 s ha re d i t e m b u f f e r [ 0.. n −1] ; 4 5 p a r b e g i n 6 l o o p { / ∗ p r o d u c e r p r o c e s s ∗ / 7 / ∗ p r o d u c e an i t e m i n n e x t p ∗ / 8 down ( empty ) ; 9 down ( mutex ) ; 10 / ∗ d e p o s i t n e x t p i n b u f f e r ∗ / 11 up ( mutex ) ; 12 up ( f u l l ) ; 13 } 14 l o o p { / ∗ consumer p r o c e s s ∗ / 15 down ( f u l l ) ; 16 down ( mutex ) ; 17 / ∗ e x t r a c t an i t e m i n n e x t c ∗ / 18 up ( mutex ) ; 19 up ( empty ) ; 20 / ∗ consume t h e i t e m i n n e x t c ∗ / 21 } 22 parend

lines 1-3 Here, buffer is the shared buffer, and contains n spaces; full is a semaphore the value of which is the

number of filled slots in the buffer (initially 0), empty is a semaphore the value of which is the number of emoty

slots in the buffer (initially n), and mutex is a semaphore used to enforce mutual exclusion (so only one process can

access the buffer at a time; initially 1). nextp and nextc are the items produced by the producer and consumed by the

consumer, respectively.

line 7 Since the buffer is not accessed while the item is produced, we don’t need to put semaphores around this part.

lines 8–10 Depositing an item into the buffer, however, does require that the producer process obtain exclusive access

to the buffer. First, the producer checks that there is an empty slot in the buffer for the new item and, if not, waits until

there is (down(empty)). When there is, it waits until it can obtain exclusive access to the buffer (down(mutex)). Once

both these conditions are met, it can safely deposit the item.

lines 11–12 As the producer is done with the buffer, it signals that any other process needing to access the buffer may

do so (up(mutex)). It then indicates it has put another item into the buffer (up(full)).

lines 15–17 Extracting an item from the buffer also requires that the consumer process obtain exclusive access to the

buffer. First, the consumer checks that there is a slot in the buffer with an item deposited and, if not, waits until there

is (down(full)). When there is, it waits until it can obtain exclusive access to the buffer (down(mutex)). Once both

these conditions are met, it can safely extract the item.

lines 18–19 As the consumer is done with the buffer, it signals that any other process needing to access the buffer may

do so (up(mutex)). It then indicates it has extracted another item into the buffer (up(empty)).

line 20: Since the buffer is not accessed while the item is consumed, we do not need to put semaphores around this

part.

Producer Consumer Problem with Monitors

This algorithm uses a monitor to solve the producer/consumer (or bounded-buffer) problem.

1 monitor b u f f e r { 2 i t e m s l o t s [ 0.. n −1] ; 3 i n t c o u n t , i n , o u t ; 4 c o n d i t i o n n o t empty , n o t f u l l ; 5 6 e n t r y d e p o s i t ( it em d a t a ) { 7 i f ( c o u n t == n ) 8 n o t f u l l. w a i t ; 9 s l o t s [ i n ] = d a t a ; 10 i n = ( i n + 1 ) % n ; 11 c o u n t ++; 12 n o t e m p t y. s i g n a l ; 13 } 14 15 e n t r y e x t r a c t ( it em ∗ d a t a ) { 16 i f ( c o u n t == 0 ) 17 n o t e m p t y. w a i t ; 18 ∗ d a t a = s l o t s [ o u t ] ; 19 o u t = ( o u t + 1 ) % n ; 20 c o u n t −−; 21 n o t f u l l. s i g n a l ; 22 } 23 24 c o u n t = i n = o u t = 0 ; 25 }

lines 2–4 slots is the buffer, count the number of items in the buffer, and in and out the indices of the next element

of slots where a deposit is to be made or from which an extraction is to be made.We care about whether the buffer is not

full (represented by the condition variable notfull) or not empty (represented by the condition variable notempty).

line 6 The keyword entry means that this procedure may be called from outside the monitor. It is called by placing

the name of the monitor first, then a period, then the function name; so, buffer.deposit(...).

lines 7–8 This code checks to see if there is room in the buffer for a new item. If not, the process blocks on the

condition notfull; when some other process does extract an element from the buffer, then there will be room and that

process will signal on the condition notfull, allowing the blocked one to proceed. Note that while blocked on this

condition, other processes may access procedures within the monitor.

lines 9–11 This code actually deposits the item into the buffer. Note that the monitor guarantees mutual exclusion.

line 12 Just as a producer will block on a full buffer, a consumer will block on an empty one. This indicates to any

such consumer process that the buffer is no longer empty, and unblocks exactly one of them. If there are no blocked

consumers, this is effectively a no-op.

line 15 As with the previous procedure, this is called from outside the monitor by buffer.extract(...).

lines 16–17 This code checks to see if there is any unconsumed item in the buffer. If not, the process blocks on the

condition notempty; when some other process does deposit an element in the buffer, then there will be something for

the consumer to extract and that producer process will signal on the condition notempty, allowing the blocked one to

proceed. Note that while blocked on this condition, other processes may access procedures within the monitor.

lines 18–20 This code actually extracts the item from the buffer. Note that the monitor guarantees mutual exclusion.

line 21 Just as a consumer will block on an empty buffer, a producer will block on a full one. This indicates to any

such producer process that the buffer is no longer full, and unblocks exactly one of them. If there are no blocked

producers, this is effectively a no-op.

lines 24 This is the initialization part.

First Readers Writers Problem with Monitors

This algorithm uses a monitor to solve the first readers-writers problem.

1 monitor r e a d e r w r i t e r { 2 i n t r e a d c o u n t ; 3 b o o l w r i t i n g ; 4 c o n d i t i o n o k t o r e a d , o k t o w r i t e ; 5 6 e n t r y b e g i n r e a d ( v o i d ) { 7 r e a d c o u n t ++; 8 i f ( w r i t i n g ) 9 o k t o r e a d. w a i t ; 10 } 11 12 e n t r y e n d r e a d ( v o i d ) { 13 r e a d c o u n t −−; 14 i f ( r e a d c o u n t == 0 ) 15 o k t o w r i t e. s i g n a l ; 16 } 17 18 e n t r y b e g i n w r i t e ( v o i d ) { 19 i f ( ( r e a d c o u n t > 0 ) | | w r i t i n g ) 20 o k t o w r i t e. w a i t ; 21 w r i t i n g = t r u e ; 22 } 23 24 e n t r y e n d w r i t e ( v o i d ) { 25 i n t i ; 26 27 w r i t i n g = f a l s e ; 28 i f ( r e a d c o u n t > 0 ) { 29 f o r ( i = 1 ; i <= r e a d c o u n t ; i ++) 30 o k t o r e a d. s i g n a l ; 31 } 32 e l s e 33 o k t o w r i t e. s i g n a l ; 34 } 35 36 r e a d c o u n t = 0 ; 37 w r i t i n g = f a l s e ; 38 }

lines 2–4: readcount holds the number of processes reading the file. writing is true when a writer is writing to the

file. oktoread and oktowrite correspond to the logical conditions of being able to read and write the file.

lines 7–9 In this routine, the reader announces that it is ready to read (by adding 1 to readcount). If a writer is

accessing the file, it blocks on the condition variable oktoread; when done, the writer will signal on that condition

variable, and the reader can proceed.

lines 13–15 In this routine, the reader announces that it is done (by subtracting 1 from readcount). If no more readers

are reading, it indicates a writer may go ahead by signalling on the condition variable oktowrite.

lines 19–21 In this routine, the writer first sees if any readers or writers are accessing the file; if so, it waits until they

are done. Then it indicates that it is writing to the file by setting the boolean writing to true.

lines 27–33 Here, the writer first announces it is done by setting writing to false. Since readers have priority, it then

checks to see if any readers are waiting; if so, it signals all of them (as many readers can access the file simultaneously).

If not, it signals any writers waiting.

lines 36–37 This initializes the variables.

Monitors and Priority Waits

This is an example of a monitor using priority waits. It implements a simple alarm clock; that is, a process calls

alarmclock.wakeme(n), and suspends for n seconds. Note that we are assuming the hardware invokes the procedure

tick to update the clock every second.

1 monitor a l a r m c l o c k { 2 i n t now ; 3 c o n d i t i o n wakeup ; 4 5 e n t r y wakeme ( i n t n ) { 6 a l a r m s e t t i n g = now + n ; 7 w h i l e ( now < a l a r m s e t t i n g ) 8 wakeup. w a i t ( a l a r m s e t t i n g ) ; 9 wakeup. s i g n a l ; 10 } 11 12 e n t r y t i c k ( v o i d ) { 13 now ++; 14 wakeup. s i g n a l ; 15 } 16 }

lines 2–3 Here, now is the current time (in seconds) and is updated once a second by the procedure tick. When a

process suspends, it will do a wait on the condition wakeup.

line 6 This computes the time at which the process is to be awakened.

lines 7–8 The process now checks that it is to be awakened later, and then suspends itself.

line 9 Once a process has been woken up, it signals the process that is to resume next. That process checks to see if

it is time to wake up; if not, it suspends again (hence the while loop above, rather than an if statement). If it is to

wake up, it signals the next process.

lines 13–14 This is done once a second (hence the addition of 1 to now). The processes to be woken up are queued in

order of remaining time to wait with the next one to wake up first. So, when tick signals, the next one to wake up

determines if it is in fact time to wake up. If not, it suspends itself; if so, it proceeds.

Producer Consumer Problem with IPC

This algorithm uses blocking send and receive primitives to solve the producer/consumer (or bounded-buffer)

problem. In this solution, the buffer size depends on the capacity of the link.

1 v o i d p r o d u c e r ( v o i d ) { 2 i t e m n e x t p ; 3 4 w h i l e ( 1 ) { 5 / ∗ p r o d u c e i t e m i n n e x t p ∗ / 6 s e n d ( ” C o n s u m e r p r o c e s s ” , n e x t p ) ; 7 } 8 } 9 10 v o i d c ons ume r ( v o i d ) { 11 i t e m n e x t c ; 12 13 w h i l e ( 1 ) { 14 r e c e i v e ( ” P r o d u c e r p r o c e s s ” , n e x t c ) ; 15 / ∗ consume i t e m i n n e x t c ∗ / 16 } 17 } 18 19 p r o d u c e r P r o d u c e r p r o c e s s ; 20 c ons ume r C o n s u m e r p r o c e s s ; 21 22 p a r b e g i n 23 P r o d u c e r p r o c e s s ; 24 C o n s u m e r p r o c e s s ; 25 parend

line 1 Here, nextp is the item the producer produces.

lines 2–8 This procedure simply generates items and sends them to the consumer process (named Consumerprocess).

Suppose the capacity of the link is n items. If n items are waiting to be consumed, and the producer tries to send the

n + 1-st item, the producer will block (suspend) until the consumer has removed one item from the link (i.e., done a

receive on the producer process). Note the name of the consumer process is given explicitly, so this is an example

of “explicit naming” or “direct communication.” Also, since the send is blocking, it is an example of “synchronous

communication.”

line 1 Here, nextc is the item the consumer consumes.

lines 9–15 This code simply receives items from the producer process (named Producerprocess) and consumes

them. When the receive statement is executed, if there are no items in the link, the consumer will block (suspend)

until the producer has put an item from the link (i.e., done a send to the consumer process). Note the name of the

producer process is given explicitly; again this is an example of “explicit naming” or “direct communication.” Also,

since the receive is blocking, it is an example of “synchronous communication.”

lines 17–20 This starts two concurrent processes, the Consumerprocess and the Producerprocess.