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().