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.