CS361: Operating System

Jian Huang — Spring 2012

EECS | University of Tennessee - Knoxville

Preemption and Synchronization

Preemptive vs non-preemptive concerns two separate domains -- the user mode, and the kernel mode. It is important to understand that preemption is not simply about user-processes. Linux did not use preemptive kernels until version 2.6.

Peterson's Solution

Peterson's solution is a classic software-based solution to the critical-section problem, solely for two processes that alternate execution between their critical sections and remainder sections. It does not work anymore on today's load/store architecture, especially due to load/store.

int turn;        // whose turn it is to enter critical section
boolean flag[2]; // whether process i is ready to enter critical section

do {
   // myid can be 0 or 1, for Process 0 and Process 1
   flag[myid] = TRUE;
   turn = 1-myid;
   while (flag[1-myid] && turn == 1-myid);

   ---
   critical section
   ---

   flag[myid] = FALSE;

   ---
   remainder section
   ---

}while(TRUE);

To prove that any synchronization algorithm works, one must show:

mutual exclusion is preserved          - safety
the progress requirement is satisfied  - efficiency
bounded-waiting requirement is met     - fairness

Synchronization with hardware support

While pure software solutions aren't likely to work in general settings, it does not require much hardware to properly implement synchronization for preemptive systems. The general problem setting follows the example of the Peterson's algorithm.

do {
   acquire lock

   ---
   critical section
   ---

   release lock

   ---
   remainder section
   ---
}while(TRUE);

What does the hardware look like? On a nonpreemptive uniprocessor system, just need to turn off interrupt when changing a shared variable. On a multiprocessor system, need to have atomic operation, i.e. a sequence of instructions to be executed without interruption.

Example - TestAndSet

boolean TestAndSet(boolean * target)
{
  boolean rv = * target;
  *target = TRUE;
  return rv;
}

boolean lock = FALSE;
do{
   while (TestAndSet(&lock)) ;
   critical section
   lock = FALSE;
   remainder section
} while(TRUE)

Example - Swap

void swap(boolean * a, boolean * b)
{
   boolean temp = *a;
   *a = *b;
   *b = temp;
}

boolean lock = FALSE;
do {
   key = TRUE;
   while (key == TRUE)
     swap(&lock, &key);

   critical section

   lock = FALSE;

   remainder section
} while (TRUE); 

Example - Multiple Waits

boolean waiting[n]; //initialized to FALSE
boolean lock = FALSE;
do {
   waiting[i] = TRUE;
   key = TRUE;
   while (waiting[i] && key)
     key = TestAndSet(&lock);
   waiting[i] = FALSE;
   ---
   critical section
   ---
   j = (i + 1) % n;
   while ((j!=i) && !waiting[j])
     j = (j+1) % n;
   if (j == i)
     lock = FALSE;
   else
     waiting[j] = FALSE;
   ---
   remainder section
   ---
} while(TRUE);

Linux implementation

Linux uses spinlocks: linux/spinlock.h (spin_lock_init and spin_lock/unlock), and uses enable/disable preemption in kernel preempt_disable/enable().


Jian Huang / EECS /UTK / revised 01/2012