CMSC 421 Operating Systems Lecture Notes (c) 1994 Howard E. Motteler Interprocess Communication =========================== (Section 2.2 in Tannenbaum) There are many applications for communicating processes: Interprocess communication is fundamental: - client-server models (e.g. X windows) - many other utilities (e.g. print spoolers) - speeding up computation Two general models of interprocess communication - Shared memory (or files) and synchronization operations - Message passing Shared Memory with Synchronization Operations ============================================== Shared memory must be managed carefully! Example of 2 processes writing to a shared buffer: Suppose p is a pointer accessed by both process P0 and process P1 - P0 contains *p++ = a ; - P1 contains *p++ = b ; If both P0 and P1 execute these statements once, we expect - p is incremented twice - both a and b are saved (in some order) The statement *p++ = a is at least 2 machine instructions - *p = a ; - p++ ; Consider the following possible sequence of events, where two processes both copy data to a buffer P0: P1: : *p = a ; (context switch) : *p = b ; p++ ; : (context switch) p++ ; : The value `a' has been overwritten by `b' This is sometimes called a "race condition," because the final result depends on instruction timing Note that the *last* variable assigned "wins", i.e., gets saved Mutual Exclusion in Critical Sections -------------------------------------- A ``critical section'' is a part of a program that accesses shared data A process can have several critical sections for different data The ``mutual exclusion problem'' is to find a way to guarantee that only one process at a time can access the shared data A solution to the mutual exclusion problem should satisfy 1. `mutual exclusion:' only one process in the critical section at a time 2. `progress:' a process outside its critical section can't hold up others that want into their critical sections 3. `bounded waiting:' a process waiting to enter its critical section eventually gets in 4. `timing:' no assumptions are made about speed, timing, or number of CPUs Disabling Interrupts -------------------- The simplest way to get mutual exclusion is to disable interrupts when a processes enters its critical section (Without clock interrupts, there's no context switch) Problem: user would need special `enter' and `exit' critical section commands These commands could be used to hog the CPU Also, with multiple CPUs, disabling interrupts typically only effects one CPU Disabling interrupts is sometimes useful for the kernel itself Two-process software solutions to the Mutual Exclusion Problem --------------------------------------------------------------- We will try and develop some code to ``guard'' a critical section, so it can be shared by two processes (All these methods use ``busy waiting'' which is sometimes unavoidable.) Simple Locks ------------ Suppose we try adding a `lock' variable, initially 0 and set to 1 whenever a process is in its CS P0, P1: while ( lock != 0 ) ; // wait until lock is 0 lock = 1 // set lock CS() ; // enter the critical section lock = 0 ; // reset lock Problem: P0: P1: finds lock = 0, (context switch) finds lock = 0, sets lock = 1, enters CS (context switch) sets lock = 1, enters CS Mutual exclusion fails just as in our initial example Alternation ----------- Suppose we allow the processes to take `turns' P0: while (alive) { while ( turn != 0 ) ; // wait until turn = 0 CS() ; // enter the critical section turn = 1 ; // other process's turn P1: while (alive) { while ( turn != 1 ) ; // wait until turn = 1 CS() ; // enter the critical section turn = 0 ; // other process's turn Mutual exclusion is satisfied, but `progress' fails Suppose P0 enters and leaves its critical section Then P0 cannot re-enter it's critical section until P1 enters and leaves its critical section Flags for `in the CS' --------------------- Idea: use two flags, both FALSE initially; flag0 is true when P0 is in CS, flag 1 is true when P1 is in the CS P0: while (alive) { while ( flag1 ) ; // wait until flag1 = false flag0 = TRUE ; CS() ; flag0 = FALSE ; } P1: while (alive) { while ( flag0 ) ; // wait until flag0 = false flag1 = TRUE ; CS() ; flag1 = FALSE ; } Mutual exclusion fails: P0 does `while' test, finds flag1 = FALSE P1 does `while' test, finds flag0 = FALSE P0 sets flag0 = TRUE and enters CS P1 sets flag1 = TRUE and enters CS Flags for `wanna enter' (similar to previous example) ------------------------------------------------------ P0: while (alive) { flag0 = TRUE ; while ( flag1 ) ; // wait until flag1 = false CS() ; flag0 = FALSE ; } P1: while (alive) { flag1 = TRUE ; while ( flag0 ) ; // wait until flag0 = false CS() ; flag1 = FALSE ; } In this case `progress' fails P0 sets flag0 = true P1 sets flag1 = true and both are stuck! Peterson's Solution ------------------- Use both a turn and flag variables; both flags are initially FALSE P0: while (alive) { flag0 = TRUE ; turn = 1 ; while ( flag1 && turn == 1 ) ; // wait until flag1 = FALSE CS() ; // or turn = 0 flag0 = FALSE ; } P1: while (alive) { flag1 = TRUE ; turn = 0 ; while ( flag0 && turn == 0 ) ; // wait until flag0 = FALSE CS() ; // or turn = 1 flag1 = FALSE ; } Partial analysis -Suppose P0 wants CS, and P1 is not in and does not want CS: Then P0 enters CS, as flag1 is FALSE (part of progress) -Suppose P0 wants CS, and P1 is in CS. Then P0 waits for flag1 to go FALSE (mutual exclusion) -Suppose both P0 and P1 want CS Then the *first* one to set turn is the one to get in (mutual exclusion & progress) P0 flag0 = TRUE; P1 flag1 = TRUE; P0 turn = 1; P1 turn = 0; P1 is second to set turn P0 while (flag1 && turn == 1) false, turn is 0, so enter CS P1 while (flag0 && turn == 0) true, loop until P0 leaves CS, enter CS when flag0 goes FALSE Must check bounded waiting: - Suppose both P0 and P1 want to enter the CS - P0 sets turn=1 and enters CS first - P1 is waiting in while loop for either flag0 to be FALSE, or turn to be 1 - Suppose P0 exits CS - P0 sets flag0 = FALSE; - Suppose P1 is unlucky - P0 sets flag0 = TRUE before next P1 test - P0 sets turn = 1 - P1 enters CS on turn = 1 Text's version of Peterson's Solution -------------------------------------- define N 2; // number of processes int turn; // whose turn is it? int waiting[N]; // who wants in (initially all 0) enter(Pa) { Pb = 1 - Pa; // assumes only 2 processes !! waiting[Pa] = 1; // we want in turn = Pa while ( turn==Pa && waiting[Pb] ); // we wait our turn } exit(Pa) { waiting(Pa) = 0; } The `Test & Set Lock' Instruction --------------------------------- Suppose we have a machine instruction TSL(v) that `atomically' (i.e., uninterruptibly) - sets v to TRUE - returns the old value of v Then it is very easy to protect a critical sections with a `lock' variable: entry: while TSL(lock); // wait until lock is FALSE CS(); exit: lock = FALSE; How it works: Suppose we hit `entry' and lock is FALSE: - Then TSL sets lock to TRUE and we enter CS - On leaving CS, lock is set back to FALSE Suppose we hit entry section & lock is TRUE: - Then some other process is in the CS - While loop runs until some other process sets lock = FALSE - Then 1st process sets lock back to TRUE & enters CS Suppose 2 processes hit entry at the same time, with lock FALSE: - first process to execute TSL gets in Suppose 2 processes hit entry at same time, with a 3rd in CS: - 1st process to do TSL after 3rd sets lock = FALSE gets in Problem: in theory, bounded waiting can be violated (even with just 2 processes) Initially lock=FALSE P0: while TSL(lock); P0 enters CS P1: while TSL(lock); P1 waits P0: CS() P0 does stuff in CS P1: while TSL(lock); P1 waits P0: lock = FALSE P0 is done with CS [ now suppose the scheduler doesn't run P1 for a bit ] P0: while TSL(lock); P0 enters CS again P1: while TSL(lock); P1 is still waiting P1 can be bypassed any number of times! The scheduler may be fair in that P1 is being given cycles, but they are wasted in the busy waiting (code can be added to guarantee bounded waiting) The `Priority inversion' problem with busy waiting --------------------------------------------------- Suppose the scheduler runs high priority processes whenever they are ready Suppose P0 has low priority and P1 has high priority If P0 is in its CS() and P1 starts busy waiting, P0 may never get any cycles to finish in its CS!!