“Software” mutual exclusion by shimeiyan1


									Chapter 5
Mutual Exclusion and Synchronization
(Part 2)

                CS 472 Operating Systems
     Indiana University – Purdue University Fort Wayne

Outline of Chapter 5 – Part 2
 Study of various techniques to manage mutual
 exclusion and synchronization
   “Software” mutual exclusion
   Hardware supported mutual exclusion
   Semaphore mutual exclusion / synchronization
   Monitor mutual exclusion / synchronization
   Message passing mutual exclusion / synchronization
   Chapter 6 is devoted a study of deadlock and starvation

“Software” mutual exclusion
  What if the only hardware support is no
  simultaneous access to memory locations?
  Dekker’s algorthm implements mutual exclusion
  for two processes in software without deadlock or
  Text presents four attempts that fail
  (See Appendix A, pp. 745 - 750)
  1: mutual exclusion OK, but two processes must alternate access
  2: mutual exclusion not guaranteed
  3: mutual exclusion OK, but deadlock possible (both insist on entering critical sections)
  4: mutual exclusion without deadlock, but starvation possible (both are too courteous)

“Software” mutual exclusion
 With Dekker’s correct algorithm, two processes
 take turns insisting on right to enter critical section
 Peterson’s algorithm (Fig. A.3, p. 750) is a simpler
 correct solution
 Neither algorithm is efficient
 Both involve some busy waiting

Peterson’s algorithm
boolean flag [2];
int turn;

void P0( ){                            void P1( ){
    while (true){                          while (true){
       flag [0] = true;                       flag [1] = true;
       turn = 1;                              turn = 0;
       while (flag [1] && turn == 1)          while (flag [0] && turn == 0)
           < do nothing >;                        < do nothing >;
       < critical section >;                  < critical section >;
       flag [0] = false;                      flag [1] = false;
       < remainder >;                         < remainder >;
    }                                      }
}                                      }

void main( ){                          NOTES:
    flag [0] = false;                   flag[i] announces Pi’s desire to enter critical section
    flag [1] = false;                   turn indicates which process should insist on
    parbegin (P0, P1);                      entering
}                                       Mutual exclusion enforced by flag in while statement
                                        Deadlock/starvation prevented by turn
                                        Only one processes is blocked at a time

Hardware supported mutual exclusion

 Interrupt disabling
 Special instructions

Interrupt disabling
 In a single processor system, a process runs until it
 invokes an operating system service or until it is
 So disabling interrupts can guarantee mutual exclusion

                   while( true ){
                     < disable interrupts >;
                     < critical section >;
                     < enable interrupts >;
                     < remainder >;

Problems with interrupt disabling
 Delays response to external events
 Critical sections must be very short
 Approach does not work with multiprocessing
   Disabling interrupts on one processor will not
   guarantee mutual exclusion

Special instructions: TestAndSet

 The TestAndSet instruction tests a memory location for 0
    If the location contains 0 . . .
        The location is loaded with 1
        TestAndSet returns true
    Otherwise, the location is unaltered and false is returned
 The TestAndSet instruction is “atomic”
 VAX system has BBCS (Branch on Bit Clear and Set bit)
 instruction together with others

Special instructions: TestAndSet
 Each process follows the same protocol
   < program mutualexclusion >
     const int n = < number of processes >;
     int bolt;
     void P(int i) {
         while ( true) {
              while ( ! testset ( bolt ) )
                 < do nothing >;
              < critical section >;
                                           void main( ){
              bolt = 0;
                                              bolt = 0;
              < remainder >;
                                              parbegin (P(1), P(2), . . . ,P(n));

Special instructions: TestAndSet
 Any number of processes and processors are
 One “bolt” for each critical resource
 Busy waiting is a disadvantage
 Starvation and deadlock possible

Special instructions: TestAndSet

 Starvation is possible when a process leaves a
 critical section and more than one process is
 No queues exist to regulate fairness

Special instructions: TestAndSet
 Deadlock is possible if a low priority process is in
 a critical section and loses the processor due to
 a higher priority process
 If the higher priority process tries to enter a
 critical section for the same resource, it will be
 denied and enter a busy wait loop

Special instructions
 Other special instructions are covered in text pp.
   Exchange instruction
 Similar to TestAndSet

Semaphore Mutual Exclusion
 OS managed
 Two kinds of semaphore
   binary semaphore containing boolean value
   counting semaphore containing integer value
 Binary semaphore defined
   Figure 5.4, pp. 221-222

A counting semaphore “sem” . . .
 Consists of private integer variable s and queue q
 s and q are operated on by indivisible operations
 wait( sem ) and signal( sem )
 Other semaphore operations exist to create,
 initialize, etc.

Operations wait(sem) and signal(sem)
wait( sem ):
   s = s – 1;
   if ( s < 0 ){
           < block this process and enqueue this process in q>

signal( sem ):
   s = s + 1;
   if ( s <= 0 ){
           <dequeue next process from q and enqueue in ready queue>

Counting semaphore
 If the queue q is FIFO, then the semaphore is
 This guarantees processes using the semaphore
 freedom from starvation
 Study Figure 5.3, p. 221
 A “weak” semaphore does not prevent starvation

Meaning of the value of s
 s >= 0:
      s is the number of processes that can call
      wait( sem ) without blocking
 s < 0:
      |s| is the number of processes currently
      blocked in queue q
 Initialize s to the number of copies of some
 resource available
 Think of a library book …

Using a semaphore for mutual exclusion
  Initialize s to 1
  Each process follows the same protocol:
       wait( sem );
       <critical section>;
       signal( sem );

Semaphore implementation
 Best done in microcode or by hardware
 Failing this, the OS could implement using
 TestAndSet or Exchange
   But this involves busy waits
   However, busy waits are short on a multiprocessor
   system because wait and signal are short

Implementation using TestAnd Set
wait( sem ):
    while ( ! testAndSet( flag ) )
        <do nothing>;
    s = s – 1;
    if (s < 0 ){
        <place this process in queue q, block this process, and set flag to 0>;
    flag = 0;

signal( sem ):
    while ( ! testAndSet( flag ) )
        <do nothing>;
    s = s + 1;
    if (s <= 0 ){
        <remove a process from queue q and place that process on the ready list>;
    flag = 0;

Implementation in Ada
 Implement using an Ada task representing a
 counting semaphore
 Task specification
    task type Semaphore is
      entry Initialize( N : in Natural );
      entry Signal;
      entry Wait;
    end Semaphore;

task body Semaphore is
  S : Natural;
begin -- Semaphore
  accept Initialize( N : in Natural ) do
                                                     Task body
    S := N;
  end Initialize;
     accept Initialize( N : in Natural ) do
     end Initialize;
     accept Signal do
       S := S + 1;
     end Signal;
    or                                        This implementation provides a
     when S >= 1 =>
       accept Wait do
                                              strong semaphore due the the
        S := S - 1;                           semantics of the Ada
       end Wait;
    or                                        rendezvous.
    end select;
  end loop;
                                              Callers to entry Wait actually
end Semaphore;                                wait in a FIFO queue.

Semaphore weaknesses
 There is no way to examine s except for wait( sem )
   Providing this ability would be pointless
 The semaphore is …
   a low level concept
   not structured
   complex to use

Semaphore weaknesses

 Reliability issues
   Operations are scattered throughout a program
   It may be difficult to produce a correct program
   Programmers must obey the protocol !
   No enforcement

Producer / Consumer Problem
for a queue with capacity N (“bounded-buffer”)

 Producer           c b a              Consumer

  Each item produced must be consumed once and
  only once
  Two issues must be addressed
     Mutual exclusion for access to the queue
     Synchronization between producer and consumer
Producer / Consumer Problem
Use three semaphores

  semaphore   initial value   purpose
    Items            0        synchronization
    Spaces           N        synchronization
    Turn             1        mutual exclusion

 Producer / Consumer Problem
         Producer:                      Consumer:
             loop                             loop
               Produce( Next );                 Wait( Items );
               Wait( Spaces );                  Wait( Turn );
               Wait( Turn );      exclusion     Dequeue( Next );
 exclusion     Enqueue( Next );                 Signal( Turn );
               Signal( Turn );                  Signal( Spaces );
               Signal( Items);                  Consume( Next );
             end loop;                        end loop;

Note: Deadlock may occur if the Wait calls in the Consumer are reversed
Barbershop problem
 3 barbers, each with a barber chair
 -- Haircuts may take varying amounts of time
 Sofa can hold 4 customers, max of 20 in shop
 -- Customers wait outside if necessary
 When a chair is empty . . .
 -- Customer sitting longest on sofa is served
 -- Customer standing the longest sits down
 After haircut, go to cashier for payment
 -- Only one cash register
 -- Barbers perform cashier duties (this is also a critical section)

The Barbershop

Fair Barbershop
/* program barbershop2 */
     semaphore      max_capacity = 20;
     semaphore      sofa = 4;
     semaphore      barber_chair, coord =3;
     semaphore      mutex1, mutex2 =1;
     semaphore      cust_ready, leave_b_chair, payment, receipt =0;
     semaphore[50] finished = {0};
     int             count;

void customer(){                void barber(){                        void cashier(){
  int custnr;                     int var b_cust;                       while( true ){
  wait (max_capacity );           while( true ){                           wait( payment );
  enter_shop();                      wait( cust_ready );                   wait( coord );
  wait( mutex1 );                    wait( mutex2 );                       accept_payment();
  count++;                           dequeue1( b_cust );                   signal( coord );
  custnr = count;                    signal( mutex2 );                     signal( receipt );
  signal( mutex1 );                  wait( coord );                     }
  wait( sofa );                      cut_hair();                      }
  sit_on_sofa();                     signal( coord );
  wait( barber_chair );              signal( finsihed[b_cust] );
  get_up_from_sofa();                wait( leave_b_chair );
  signal( sofa );
  sit_in_barber_chair();          }
                                     signal( barber_chair );
                                                                                Note the complexity of
  wait( mutex2 );
  enqueue1( custnr );
                                                                                  semaphore solution
  signal( cust_ready );
  signal( mutex2 );
                                                                                 (10 semaphores plus
  wait( finished[custnr] );
                                                                                one for each customer)
  signal( leave_b_chair );      void main(){
  pay();                          count = 0;
  signal( payment );              parbegin(customer, …--50 times-- … customer, barber, barber, barber, cashier);
  wait( receipt );              }
  signal( max_capacity );
 A monitor is a programming language construct,
 proposed by C. Hoare in 1974
 Resembles an “object” that responds to only one
 process at a time
 Involves local private data and procedures that
 act on the data (and an initialization procedure)
 Local data variables are accessible only by the
 monitor’s procedures

 A process enters a monitor by invoking one of its
 Only one process may be executing in the
 monitor at a time
 Other processes wait in a queue
 Any other process that calls a monitor procedure
 is suspended until the monitor becomes
 Thus access to the monitor’s data is under
 mutual exclusion

Monitor condition variables
 Synchronization is provided through “condition
 variables” and two functions that operate on these
 variables: cwait and csignal.
 Each condition variable is associated with a queue
 A process may suspend itself to wait in the queue
 for a condition variable c by invoking cwait(c)
 This allows another process to enter the monitor

Monitor condition variables
 Another process may release the suspended
 process at the head of the queue by signaling
 condition variable c
 This is done by invoking csignal(c)
 The released process runs immediately
 csignal(c) does nothing if no process is waiting
 on c (in this case the signal is lost)
 Synchronization is simplified vrs. semaphores
 because it is confined to the monitor (not spread
 all over the application)
A Monitor for the Bounded-Buffer Producer/Consumer Problem
Semaphore implementation using a monitor
monitor class Semaphore {

    private int s := 0;
    private condition sIsPositive;          /* associated with s > 0 */

    void p() {
       if ( s = 0 ) cwait( sIsPositive );
       s := s – 1;

    void v() {                                 Note: Semaphore method …
       s := s + 1;                               -- wait() is denoted by p()
       csignal( sIsPositive );                   -- signal() denoted v()

Monitor condition variables
 Note: In the monitor example, csignal occurs
 only at the end of monitor procedures
 Concurrent Pascal only allows csignal at end
 When csignal is allowed to occur in the middle of
 procedures, the complexity greatly increases
 Recall: the released process must run
 Need an “urgent queue” for the signaling

Problems with a traditional monitor
 Q: Why must the released process run
 A: The condition under which csignal(c) was
     issued could change if the process calling
     csignal(c) or some other process can act
 Q: What is wrong with the urgent queue?
 A: If the process issues csignal before it is done
     with the monitor, two additional process
     switches are needed: to suspend to the urgent
     queue and to resume later
Lampson/Redell Monitor
 Overcomes problems with the traditional monitor
 Replace csignal(c) with cnotify(c)
 cnotify(c) “notifies” the condition queue for c
   The signaling process is allowed to continue
   The process at the head of the queue for c is
   resumed when convenient
   However, the waiting process must recheck the
   condition again in case it has changed

Pros and Cons
 At least one extra evaluation of the condition
 variable is needed
 No extra process switches
 No constraints on when the waiting process
 must run after a cnotify

Lampson/Redell Monitor
 There is also condition variable operation
 cbroadcast releases all processes waiting on c
 This is convenient when a process does not
 know how many other processes should be
 Java uses this monitor model, but without the
 condition variables

void append (char x){
   while(count == N)              /* this is not a busy wait */
         cwait(notfull);          /* buffer is full; avoid overflow */
   buffer[nextin] = x;
   nextin = (nextin + 1) % N;
   count++;                       /* one more item in buffer */
   cnotify(notempty);             /* notify any waiting consumer */

void take (char x){
   while(count == 0)              /* this is not a busy wait */
         cwait(notempty);          /* buffer is empty; avoid underflow */
   x = buffer[nextout];
   nextout = (nextout + 1) % N;
   count--;                       /* one fewer item in buffer */
   cnotify(notfull);                       /* notify any waiting producer */

Figure 5.17 Bounded Buffer Monitor Code for a Lampson/Redell Monitor

Message Passing
 Involves some form of two primitives
    send( destination, message )
    receive( source, message )
 There are a number of common options . . .

Message passing options
 Blocking/nonblocking send/receive options
   Blocking send / blocking receive
       Permits tight synchronization
       Ada rendezvous uses this
       Ada adds the ability to override using select
   Nonblocking send / blocking receive
       Doesn’t guarantee that the message was received
       A reply message is needed to ACK
       Very useful combination
       The sender may broadcast a message
   Nonblocking send / nonblocking receive also used
Message passing options
 Source / destination addressing
   “direct addressing”
       Destination of send( ) is explicit
       Source of receive( ) is either explicit or implicit
          • Explicit source: receiver must expect sender to send
              – Not the case in Ada
          • Implicit source: source program may provide identity of sender
              – Not the case in Ada

Message passing options
 Source / destination addressing
   “indirect addressing”
       Messages sent to a shared queue holding messages
       Message queue is a “mailbox”
          • Nonblocking send and blocking receive
       Indirect addressing decouples sender from receiver
       Senders and receivers may be . . .
          •   1-1 (private)
          •   Many-1 (client server or “port”)
          •   1-many (broadcast)
          •   Many-many
       Mailbox primitives exist for dynamic creation, connect,
        disconnect, etc.
       Mailbox ownership - typically the creating process or the OS
Message passing options
 Message format
   Fixed length
       Low overhead
   Variable length
       More flexible
   Typical items in a message
       Message type
       Message body
       Message length
       Source
       Destination
       Sequence number
       Priority
       Etc.
Message passing options
 Queuing discipline for messages
   Receiver chooses
 Queuing discipline for processes with a mailbox
   Waiting processes are queued with blocking receive
   Only one process receives one message and the
   others are blocked

Mutual exclusion using a mailbox

 Initialize mailbox mutex with one dummy message
 Each process should execute its critical section
 using the following protocol

    receive( dummyMsg, mutex );
    < critical section >;
    send( dummyMsg, mutex );

Bounded-Buffer Producer / Consumer
problem with mailboxes
  Set capacity of N
  Use two mailboxes
        items: the items form a queue of messages
        spaces: N dummy messages are initially queued
Producer:                          Consumer:
  loop                               loop
    produce( next );                   receive( next, items );
    receive( dummyMsg, spaces );       send( dummyMsg, spaces );
    send( next, items );               consume( next );
  end loop;                          end loop;

Remember that receive is blocking and send is nonblocking

To top