Chapter 7 -- Synchronization

Note:  less emphasis on 7.2.2, stuff of 7.7 and beyond
  • Software solutions
    1. Critical sections
      1. Defined to be areas of code where only one process/thread can execute at a time.
      2. Example: Any code that messes with the task_struct will need to lock out other processess from looking at the same task_struct.
      3. Example: any code managinb buffers for the producer/consumer problem must have locks.
      4. These code areas not always contigious.
      5. Goals (simplified version).
        1. Mutual exclusion -- only one process per time.
        2. Progress -- If no one else is waiting, then I can get in.
        3. Bounded Wait -- No starvation.  But what if the scheduler keeps a process from running?
      6. Types
        1. Busy waiting (spin lock) -- keep CPU while waiting.
          1. Wastes CPU
          2. Avoids context switch.
          3. Real dumb if lock will not be released until another process runs.
          4. Useful on multi-CPU systems.
        2. Blocking wait -- turn CPU over if thwarted.
          1. Uses a context switch.
          2. Makes sure other process can get CPU.
    2. Application Solutions
      1. Normally cannot disable interupts.
      2. Single lines of code can generate multiple asm instructions.
      3. We assume that each single asm instruction is atomic.
      4. Page 195 ("algorithm 3") offers a solution that works for two processes without disabling interrupts.
      5. 6.2.2 Offers a solution that works for multiple processes without disabling interrupts.
      6. Some operating systems offer lock as a system call.
    3. Operating System Solutions.
      1. Can disable interrupts. (Give example of the cli() in pur /proc assigment).
        1. Hurts interrupt latency.
        2. Bad for multi-CPU systems.
      2. Instructions just for locks.
        1. test-and-set
          1. define ...
          2. Use is (also complicated version that has bounded wait).
          3. while (test-and-set(lock)) 
                ;
            CRITICAL SECTION
            lock = false;
            REMAINDER SECTION
        2. swap (also complicated version that has bounded wait).
          1. define...
          2. Useis
          3. key = true;
            repeat swap(key, lock) until key == false;
            CRITICAL SECTION
            lock = false;
            REMIANDER SECTION
  • Semaphores
    1. Define wait(lock) and signal(lock) (spin-lock version).
    2. wait(lock): while lock <= 0 do no-op;
                  lock -= 1;
      signal(lock): lock++;
    3. Define wait(lock) and signal(lock) (blocking wait version)
    4. class semaphore { int value; list-of-processes L; }
      wait(semaphore) : S.value--;
                        if (S.value < 0) 
                            add self to S.L;
                            schedule();
      signal(semaphore): S.value++;
                         if (S.value <= 0) 
                            remove process P from S.L;
                            wakeup(P);
      When is spin-lock better?  When is blocking lock better?
    5. More versitile than critical sections.
      1. Can impose an ordering.
      2. FIRST STUFF
        signal(lock);
        ----------------
        wait(lock);
        SECOND STUFF
      3. Can allow 'n' processes in critical section (by init'ing the lock to 'n').

    Example Using Semaphores

    1. Producer/Consumer problem
      1. // init
        empty = SIZE; full = 0;

        // Producer
        repeat
           produce an item
          
        wait(empty);
           wait(mutex);
           add item to buffer
          
        signal(mutex);
           signal(full);
        until done

        // Consumer
        repeat
           wait(full);
           wait(mutex);
           consume an item from buffer
          
        signal(mutex);
           signal(empty);
        until done
      Chinese Philosphers
          while (1) {
              wait(chopstick(N));
              wait(chopstick((N+1) mod 5);
              eat ....
              signal(chopstick((N+1) mod 5);
              signal(chopstick(N);
         }
      But, what about deadlock?  How can deadlock be solved?
      What happens if you accedentilly switch wait() and signal()?

    Deadlocks

    1. Requirements for deadlocks
      1. Exclusive use
      2. No preemption
      3. Indefinite wait
      4. Cyclic wait.
    2. Deadlock can be described by a wait-for graph
      1. elements are processes and resources.
      2. Bipartite graph
      3. cycle means deadlock.
    3. Easy ways to fix avoid deadlock
      1. Can only request resource if have no resource-- no wait cycle
        1. Works sometimes (like for kernel data structures)
        2. Doesn't work other times (like for devices).
        3. Must allow for multiple simultainious requests.
      2. Death if request cannot be satisfied.
        1. Can request second resource. If cannot immediately satisfy, then must release resource.
        2. Can cause process termination.
        3. Not suitable for kernel.
      3. Order resources -- no wait cycle
        1. Can cause unnecessary resource hogging.
        2. Simple and effective