Process Synchronization Mechanisms

Tom Kelliher, CS 318

Feb. 4, 1998

Announcements:

From last time:

  1. Nachos internals.

  2. The critical section problem.

  3. Producer/Consumer model.

  4. Disabling interrupts as a primitive solution.

Outline:

  1. Cooperating processes (review).

  2. A hardware solution to the C. S. problem.

  3. Semaphores.

Assignment: Read Chapters 4 and 6.

Cooperating Processes

  1. Must cooperating processes synchronize under all conditions? (Don't forget single writer performing atomic writes/multiple readers.)

  2. What does atomic mean?

  3. Recall necessary and sufficient conditions: Mutual exclusion, progress, and bounded waiting.

A Hardware Solution: TAS Instruction

TAS: Test And Set. Semantics:

int TAS(int& val)
{
   int temp;

   temp = val;   // Body performed atomically.
   val = 1;
   return temp;
}

A partial solution to the critical section problem for n processes:

// Initialization
int lock = 0;

void MutexBegin()
{
   while (TAS(lock))   // Ugh.  A spin lock.
      ;
}


void MutexEnd()
{
   lock = 0;
}
Prove that this is a solution to the C. S. problem.

Semaphores

  1. Created by Dijkstra (Dutch)

  2. A semaphore is an integer flag, indicating that it is safe to proceed.

  3. Two operations:

    1. Wait (p) --- proberen, test:
         wait(s) {
            while (s == 0)
               ;
            s--;
         }
      

      Test and (possible) decrement executed atomically (usually achieved through hardware means).

    2. Signal (v) --- verhogen, increment:
         signal(s) {
            s++;
         }
      

    3. Why not resort to hardware methods?

  4. These are operations provided by the kernel. Wait and signal are atomic operations.

Critical Section Problem Solution

  1. Critical section solution:

    semaphore mutex = 1;
    
    mutexbegin: wait(mutex);
    mutexend:   signal(mutex);
    

    1. Mutual exclusion is achieved: consider a contradiction.

    2. Progress is achieved: someone got the semaphore.

    3. Bounded waiting depends on how the wait queue is implemented (if at all).

Usage Examples

  1. Interrupt signalling:

    semaphore sig = 0;
    
    int_hndl:
    signal(sig);
    
    driver:
    startread();
    wait(sig);
    

  2. Process synchronization:

    semaphore flag = 0;
    
    
    process1()
    {
       p1Part1();   // This will complete before p2part2() begins.
       signal(flag);
       p1Part2();
    }
    
    
    process2()
    {
       p2part1();
       wait(flag);
       p2part2();
    

  3. Resource management (pool of buffers)

    Producer/Consumer problem:

    semaphore count = N;
    semaphore mutex = 1;
    
    getbuf:
    wait(count);               /* order important here */
    wait(mutex);
    <grab unallocated buffer>
    signal(mutex);
    return(buffer);
    
    relbuf:
    wait(mutex);
    <release buffer>
    signal(mutex);
    signal(count);
    

A Better Semaphore

  1. Above semaphores inefficient --- spinlocks. Let waits which cause busy waits actually block the process:

    Associate a ``blocked'' queue with each semaphore.

    typedef struct semaphore {
       int   value;
       pcb   *head;
    }
    

    Semaphore creation:

    semaphore *createsem(int value) {
    
       semaphore   *sem;
    
       sem = get_next_sem();
       sem->value = value;
       sem->head = NULL;
       return (sem);
    }
    
    void wait(semaphore *sem) {        /* need mutex goo here */
    
       if (--sem->value < 0) {
          <update status of current process>
          insqu(sem->head->prev, current);
          scheduler();
       }
    }
    
    void signal(semaphore *sem) {         /* mutex */
    
       pcb   *proc;
    
       if (++sem->value <= 0) {
          proc = remqu(sem->head->next);
          <update status of proc>
          ordinsqu(ready, proc);
          if (proc->prio > current->prio)
             scheduler();
       }
    }
    



Thomas P. Kelliher
Mon Feb 2 09:20:02 EST 1998
Tom Kelliher