CSC443 Feb19

slide version

single file version

Contents

  1. A Simple Threads Implementation
  2. Basic Representation
  3. A Collection of Threads
  4. Thread Switch (in C)
  5. Context Pointer
  6. Synchronization
  7. Multiple Processors
  8. Hardware Support
  9. Naive Spin Lock
  10. Better Spin Lock
  11. Blocking Locks
  12. Multiprocessor Working Blocking Locks (?)
  13. Linux Futexes
  14. Ancillary Functions
  15. Attempt 1
  16. Attpempt 2
  17. Interrupts
  18. Synchronization and Interrupts
  19. Non-Preemptive Kernel Sync.
  20. Deferred Work
  21. Deferred Processing
  22. Invoking a Signal Handler
  23. Signal Handling
  24. Signal Handling
  25. Signal Handling
  26. Signal Handling
  27. Signal Handling
  28. Signal Handling
  29. Homework
  30. Prob 4
  31. Prob 5
  32. Prob 7

A Simple Threads Implementation[1] [top]

Basic Representation[2] [top]

Thread Control Block

Thread object includes space to hold th thread's stack and the object also contains

A Collection of Threads[3] [top]

Thread Switch (in C)[4] [top]

void thread_switch( ) {
  thread_t NextThread, OldCurrent;

  NextThread = dequeue(RunQueue);
  OldCurrent = CurrentThread;
  CurrentThread = NextThread;
  swapcontext(&OldCurrent->context, &NextThread->context);

  // We're now in the new thread's context

}
    

What if there are no runnable threads; i.e., what if the RunQueue is empty?

Context Pointer[5] [top]

The thread object includes the context which includes a pointer to the thread's current stack frame.

Synchronization[6] [top]

Mutex lock and unlock could be implemented for this basic system like this:

Why isn't

void mutex_lock(mutex_t *m) {
  if (m->locked) {
    enqueue(m->queue, CurrentThread);
    thread_switch();
  } else
    m->locked = 1;
}
    
void mutex_unlock(mutex_t *m) {
  if (queue_empty(m->queue))
    m->locked = 0;
  else
    enqueue(runqueue, dequeue(m->queue));
}
    

Don't we need to do something to make these operations atomic?

Multiple Processors[7] [top]

How do we ensure that all processors are utilized?

What about synchronization?

Hardware Support[8] [top]

Compare and swap instruction

This executes as the following, but is atomic!

int CAS(int *ptr, int old, int new) {
   int tmp = *ptr;
   if (*ptr == old)
      *ptr = new
   return tmp;
}
    

Naive Spin Lock[9] [top]

void spin_lock(int *mutex) {
   while(!CAS(mutex, 0, 1))
      ;
}

void spin_unlock(int *mutex) {
   *mutex = 0;
}
      
    

Better Spin Lock[10] [top]

The atomic guarantee of CAS slows down processors. So some checking is done without it first:

void spin_lock(int *mutex) {
   while (1) {
      if (*mutex == 0) {
         // the mutex was at least momentarily unlocked
         if (!CAS(mutex, 0, 1)
            break;  // we have locked the mutex
         // some other thread beat us to it, so try again
      }
   }
}
    

Blocking Locks[11] [top]

For multiprocessors, spin locks should be restricted to protecting very short code.

Spin locks are not useful for uniprocessors.

The following works on uniprocessors.

(This is closer to the project's uthread_mutex_t struct.)

void blocking_lock(mutex_t *mut) {
  if (mut->holder != 0)
    enqueue(mut->wait_queue, CurrentThread);
    thread_switch();
  } else
    mut->holder = CurrentThread;
}
      
void blocking_unlock(mutex_t *mut) {
  if (queue_empty(mut->wait_queue))
    mut->holder = 0;
  else {
    mut->holder = dequeue(mut->wait_queue);
    enqueue(RunQueue, mut->holder);
  }
}

    

Multiprocessor Working Blocking Locks (?)[12] [top]

Does this work for multiprocessors?

void blocking_lock(mutex_t *mut) {
  spin_lock(mut->spinlock);
  if (mut->holder != 0)
    enqueue(mut->wait_queue,
       CurrentThread);
    spin_unlock(mut->spinlock);
    thread_switch();
  } else {
    mut->holder = CurrentThread;
    spin_unlock(mut->spinlock);
  }
}
void blocking_unlock(mutex_t *mut) {
  spin_lock(mut->spinlock);
  if (queue_empty(
     mut->wait_queue)) {
    mut->holder = 0;
  else {
    mut->holder =
     dequeue(mut->wait_queue);
    enqueue(RunQueue,
     mut->holder);
  }
  spin_unlock(mut->spinlock);
}
    

Suppose

Linux Futexes[13] [top]

Fast mutexes

Ancillary Functions[14] [top]

      int atomic_inc(int *val)
         - add 1 to *val, return its original value
      int atomic_dec(int *val)
         - subtract 1 from *val, return its original value
    

Attempt 1[15] [top]


void lock(futex_t *futex) {
  int c;
  while ((c = atomic_inc(&futex->val)) != 0)
    futex_wait(futex, c+1);
}

void unlock(futex_t *futex) {
  futex->val = 0;
  futex_wake(futex);
}

Attpempt 2[16] [top]

void lock(futex_t *futex) {
  int c;
  if ((c = CAS(&futex->val, 0, 1) != 0)
    do {
      if (c == 2 || (CAS(&futex->val, 1, 2) != 1))
        futex_wait(futex, 2);
    while ((c = CAS(&futex->val, 0, 2)) != 0))
}

void unlock(futex_t *futex) {
  if (atomic_dec(&futex->val) != 1) {
    futex->val = 0;
    futex_wake(futex);
  }
}

Interrupts[17] [top]

Basic problem:

Interrupts are asynchronous to the interrupted thread.

Interrupt handlers don't have control blocks for saving their state.

So switching to another interrupt handler or to another thread and later continuing an interrupt handler doesn't work.

That is, thread_switch (or an analogous interrupt_switch) doesn't work.

Synchronization and Interrupts[18] [top]

Non-Preemptive Kernel Sync.[19] [top]

int x;

void AccessXThread()
{
    int oldIPL;
    oldIPL = setIPL(IHLevel);
    x = x + 1;
    setIPL(oldIPL);
}


void AccessXInterrupt()
{
   ...
   x = x + 1;
   ...
}      
    

For preemptive kernel, synchronization is more complicated.

Deferred Work[20] [top]

Interrupt handlers run with interrupts masked both when executed in interrupt context or thread context

may interfere with handling of other interrupts

Solution

do minimal work now

do rest later without interrupts masked

Deferred Processing[21] [top]

void TopLevelInterruptHandler(int dev) {
  InterruptVector[dev](); // call appropriate handler
  if (PreviousContext == ThreadContext) {
    UnMaskInterrupts();
    while(!Empty(WorkQueue)) {
      Work = DeQueue(WorkQueue);
      Work();
    }
  }
}

void NetworkInterruptHandler() {
  // deal with interrupt
  
  EnQueue(WorkQueue, MoreWork);
}
      
    

We'll come back to this, but for now look at signal handling which is simpler but has some of the same characteristics.

Invoking a Signal Handler[22] [top]

Basic idea is to set up the user stack so that the handler is called as a subroutine and so that when it returns, normal execution of the thread may continue.

Complications:

Signal handling semantics requires that the signal that caused handler to execute is masked while in the handler and unmasked when the handler returns.

Signal Handling[23] [top]

Signal Handling[24] [top]

Signal Handling[25] [top]

Signal Handling[26] [top]

Signal Handling[27] [top]

Signal Handling[28] [top]

Homework[29] [top]

Problems 4, 5, 7, 11a, and 11b

Prob 4[30] [top]

Does this implement mutexes correctly? Why or why not?


kmutex_lock(mutex_t *mut) {
  if (mut->locked) {
    enqueue(mut->wait_queue, CurrentThread);
    thread_switch();
  }
  mut->locked = 1;
}

kmutex_unlock(mutex_t *mut) {
  mut->locked = 0;
  if (!queue_empty(mut->wait_queue))
    enqueue(RunQueue, dequeue(mut->wait_queue));
}

Prob 5[31] [top]

Assuming that all threads are either runnable or waiting on a mutex what can you say if there are no threads on the run queue with this version of thread_switch?

void thread_switch( ) {
  thread_t NextThread, OldCurrent;

  NextThread = dequeue(RunQueue);
  OldCurrent = CurrentThread;
  CurrentThread = NextThread;
  swapcontext(&OldCurrent->context, &NextThread->context);

  // We're now in the new thread's context

}
    

Prob 7[32] [top]

Explain why the following implementation the semaphore P and V operations in terms of futexes doesn't work.

void P(futex_t *f) {
   /* f->val contains the value of the semaphore */
   int v;
   while (1) {
      while ((v = f->val) > 0) {
         /* semaphore is positive */
         if (CAS(&f->val, v, v-1) == v) {
            /* we were able to decrement it without
               interference */
            return;
         }
         /* try again while semaphore is still positive
      }
      /* semaphore is zero: wait for a V operation
      futex_wait(f, 0);
   }
    }

void V(futex_t *f) {
   if (atomic_inc(&f->val) == 0) {
      /* semaphore was zero: there might be
         waiting threads */
      futex_wake(f);
   }
   return;
}