CS 162, Spring 2007

Section notes for 13 February 2007

Sections 102/103

GSI: Thomas Kho <cs162-ta@inst.eecs.berkeley.edu> / <tkho@eecs>

Based on notes by Hakim Weatherspoon and Karl Chen

AGENDA
  1. Monitors
  2. Linkers & Loaders

DEADLINES
2/13 - Phase I initial design doc due
2/15-2/16 - Design reviews *SIGNUP*


NACHOS WALKTHROUGH

  - Homework #1 posted: Need to understand this to do project



  - Overall: For this project, really need to understand

     nachos.threads.KThread

     nachos.threads.ThreadedKernel

     nachos.machine.TCB  (not the internals, just the interface)



  - Only modify nachos.threads package - nothing else

    You cannot use Java threads or 'synchronized' keyword



  - Main structure:

     nachos.machine.* -- the internals of the implementation

       Not really important to understand how this works, but

       need to know what the public methods are



       Machine.interrupt().disable()

            Disable interrupts, return flag of previous interrupt state

       Machine.interrupt().enable()

            Enable interrupts

       Machine.interrupt().restore()

            Restore to previously saved flag



       TCB.contextSwitch()

        Context switch to this TCB. Used internally by KThread,

        you don't need to call this yourself but important to

        see where it's used

       TCB.start()

        Used to bootstrap a new TCB - used internally by KThread



    nachos.threads.* -- what you will be modifying

       

       KThread(Runnable target)

            Create a new kernel thread and associate with it the

        code in 'target.run()'.

       KThread.setName(String name)

            Associate a new, can be retrieved with getName

       KThread.fork()

            Fork the given thread - that is, start it running

       KThread.yield()

            Cause the current thread to yield the CPU

       KThread.sleep()

            Cause the current thread to block - will be woken up later

       KThread.ready()

            Move this thread to the ready queue, i.e. wake it up



       Lock.acquire()

            Sleep until this lock can be acquired



        private KThread lockHolder;



        intStatus = Machine.interrupt().disable();

        if (lockHolder == null) {

          waitQueue.acquire(KThread.currentThread());

          lockHolder = KThread.currentThread();

        } else {

          waitQueue.waitForAccess(KThread.currentThread());

          KThread.sleep();

        }

        Machine.interrupt.restore(intStatus);



       Lock.release()

            Release the lock

        (Ask class how this would be implemented)



            int status = Machine.interrupt().disable();

        lockHolder = waitQueue.nextThread();

        lockHolder.ready(); // Wake it up

        Machine.interrupt().restore(intStatus);



PRIORITY INVERSION AND DONATION


(using http://inst.eecs.berkeley.edu/~cs162-tc/pri)


Priority Scheduling


Priority Inversion

Priority Donation




Monitors
--------

• CVs + locks

• Mesa vs Hoare Condition variables

• H2O example

• Need 2 hydrogen threads + 1 oxygen thread to produce water molecule

• Problems:
• Starvation
• Deadlock
• State variables accessed outside critical section
T/F
Every condition variable is associated with some lock?
    True.

Multiple condition variables may not be associated with the same lock?
    False.

Condition.sleep, wake, wakeAll can be used without holding associated lock?
    False.

Can a thread wait for access to one object while having access to another?
   True.

What does condition.sleep() do?
     - atomically release the lock and relinkquish the CPU until woken; then reacquire the lock.

What does condition.wake() do? condition.wakeAll()?
     - wake() wakes up a single thread sleeping in 'this' condition variable, if possible.
     - wakeAll() wakes up all threads sleeping in 'this' condition variable.

Whats the difference between Mesa-style condition variables and
Hoare style condition variables?  What does nachos use?
What is the advantage/disadvantage?

      - Mesa-style condition variables define wake() to wake up another thread and place woken thread on the ready queue. It is the responsibility of the woken thread to reacquire the lock (this reacquire is taken care of in sleep()).

      By contrast, Hoare-style condition variables define wake() to wake up another thread, give it the lock, and place directly on the CPU (i.e. woken thread runs immediately.

      Nachos uses Mesa-style semantics.

      The consequence of using Mesa-style semantics is that some other thread can acquire the lock and change data structures, before the woken thread gets a chance to run. The advance to Mesa-style semantics is that it is a lot easier to implement.

   * Monitors
        - Use locks to provide mutual exclusion and condition variables to synchronize.
        - Monitor encapsulates a lock and one or more condition variables.
        - Condition Varibles are like queue, with sleep resembling enqueue and wake resembling dequeue.
        - Only the lock holder can call sleep() or wake()/wakeAll()
            - Thus sleep() must atomically realease the lock before going to sleep.
        - when sleep returns the thread must have reaccquired the lock.

Mesa vs. Hoare Monitors

Complete Monitor Example (Infinite synchronized queue)
Lock lock;
Condition dataready;
Queue queue;
AddToQueue(item) {
lock.Acquire(); // Get Lock
queue.enqueue(item); // Add item
dataready.signal(); // Signal any waiters
lock.Release(); // Release Lock
}
RemoveFromQueue() {
lock.Acquire(); // Get Lock
while (queue.isEmpty()) {
dataready.wait(&lock); // If nothing, sleep
}
item = queue.dequeue(); // Get next item
lock.Release(); // Release Lock
return(item);
}


--------------------------------------------------------------------------
    Producers and Consumers using Condition Variables
--------------------------------------------------------------------------

  Lock lock;
  Condition wantToAdd = new Condition(lock);
  Condition wantToTake = new Condition(lock);

  Producer() {
    lock.acquire();
    while (numCokes == MAXCOKES) { // Like a semaphore.P()
      wantToAdd.sleep(); // Lock released!
    }
    numCokes++;   // Lock reacquired!
    wantToTake.wakeAll();          // Like a semaphore.V()
    lock.release();
  }

  Consumer() {
    lock.acquire();
    while (numCokes == 0) {        // Like a semaphore.P()
      wantToTake.sleep(); // Lock released!
    }
    numCokes--;  // Lock reacquired
    wantToAdd.wakeAll();           // Like a semaphore.V()
    lock.release();
  }

- This is much nicer
- Question: What if we changed 'wakeAll()' to 'wake()' in the above?
  - Answer: Would have to wait for another producer or consumer
    to come around before the next producer or consumer would wake up!
- Note that we're really building "semaphores" when we do it this way!
- So different synchronization primitives are more useful or convenient
  in different situations.




    Nachos ::  Condition.java


    public void sleep() {
        Lib.assert(conditionLock.isHeldByCurrentThread());

        Semaphore waiter = new Semaphore(0);
        waitQueue.add(waiter);  
  
        conditionLock.release();
        waiter.P();
        conditionLock.acquire();  
     }


     public void wake() {
    Lib.assert(conditionLock.isHeldByCurrentThread());

    if (!waitQueue.isEmpty())
        ((Semaphore) waitQueue.removeFirst()).V();
      }

Sections
--------

• Fundamental insight in computer science: code == data

• Machine cares not whether 32bits in memory represent integer or float or
code!

• But matters for efficiency -- running multiple copies of one program

• Remember Play vs Script?

• Sections in unix:
• Code
• Data
• BSS = initialized data


Compiling & running
-------------------

foo.c:
main() { printf("Meaning of life = %d\n", bar()); }

bar.c:
bar() { return 6*7; }

1. gcc -o foo.o -c foo.c
2. gcc -o bar.o -c bar.c
3. gcc -o foobar.exe foo.o bar.o


• Steps 1,2: compiling

• How does compiler know at step 1 how to generate code?

• Placeholder for "bar" (undefined external symbol)

• If the location of bar() in memory is unknown during step 2, how to
generate addresses even within bar.c?
• -> Relative addresses

• Step 3: linking

• Fix placeholders

• Calculate addresses

• Re-order sections (e.g. put all data sections first)

• Draw: [foo.data,bar.data,foo.code,bar.code]