CS 162 Fall, 2005 Prof. Alan J Smith Lecture Notes, part 2. Topic: Independent and Cooperating Processes + Independent process: one that can't affect or be affected by the rest of the universe. (as we'll see, few processes are really completely independent.) + Its state isn't shared in any way by any other process. (This is the major requirement for being independent.) + Deterministic and Reproducible: input state alone deter- mines results. + Can stop and restart with no bad effects (only time varies). Example: program that sums the integers from 1 to 100. + How often are processes independent? + If a process shares a file with another process, it isn't independent. + If one uses input from another or generates output for another, it isn't. + If they conflict in the resources they want to use (printer, disk, tape, etc.) they aren't. + If they're part of the same command, they probably aren't. + Independent processes are interesting and useful from the viewpoint of scheduling. In real systems, however, there is usually some lack of complete independence. We are therefore going to concentrate (for a couple of lectures) on "cooperat- - .1 - ing processes." + Cooperating processes: + Cooperating processes are those that share some state. + We are interested in the results of the computation - i.e. if we want the sum of N numbers, that sum should always be computed correctly. Relative and absolute tim- ing is not part of the "results" and can change from run to run. + We are therefore interested in reproducible results, despite the fact that some aspects of machine operation are non-deterministic. E.g. run times and relative in- terleaving of processes varies. + (But suppose that we start with a quiescent machine, and the same starting conditions - won't the same things al- ways happen?? + Maybe - first, it is hard to get the same conditions - e.g. requires that angular position of all disks is the same. + Requires that all system data structures (e.g. order of job queue) be the same. + Requires that disk layout (blocks, free lists, VTOCs, etc.) be the same. + May require that system clock be set to same value. + Requires that all electronic gates switch in just the same time, if this is a multiprocessor - otherwise, order of two processors could change. - .2 - + Essentially, there is a distinction between micro level behavior and macro level behavior. For practical pur- poses, exact micro behavior is irreproducible. Macro behavior, on the other hand, should be reproducible - i.e. the computation should produce the same results each time. + Micro level behavior is at the level of gates, the se- quence of memory references at the CPU, etc. + Macro level behavior - the results of the computation. + It is important that results of computations be reprodu- cible, despite considerable shared state, such as the file system. + Why permit processes to cooperate? + Want to share resources: + One computer, many users. + One file of checking account records, many tellers. + Want to do things faster: + Read next block while processing current one. + Divide job into sub-jobs, execute in parallel. + Want to construct systems in modular fashion. (e.g. eqn | tbl | troff) + A counting contest - how we can get into trouble: Process A Process B i = 0; i = 0; - .3 - while (i < 10) i = i+1;while (i > -10) i = i-1; printf(``A is done''); printf(``B is done''); + Variable i is shared. + Reference (read) and assignment are each atomic. + Will process A or process B win? + Will they ever finish? + If one finishes, will the other also finish? (it should) + Does it help A to get a head start? (that depends on the exact sequence. The sequence of read,read,write,write allows B to win.) + When discussing concurrent processes, multiprogramming (mul- tiple processes running on same processor concurrently) is (usually) as dangerous as multiprocessing (multiple proces- sors, sharing memory) unless you have tight control over the multiprogramming. Also, smart I/O devices are as bad as cooperating processes (they share the memory). + In general, in our discussions, we can assume that at any instant, if two processes are ready, the processor can switch from running one to running the other (on an in- struction by instruction basis). Only truly indivisible (atomic) operations cannot be interrupted. + Atomic operations: + In order to synchronize parallel processes, we will need atomic operations. + Atomic operation: An operation that either happens - .4 - in its entirety without interruption, or not at all. Cannot be interrupted in the middle. + Loads and assignments (stores) are atomic in almost all systems. A=B will always get a good value for B, will always set a good value for A (but not necessarily true for arrays, records, or even floating-point numbers - why?). + A=B consists of Load(B), store into A. Load and Store are atomic. A=B is atomic because Store(B) is atomic. + In uniprocessor systems, turning off interrupts can (al- most) make what is between off/on atomic. (note that I/O can interfere.) (note that some interrupts are not mask- able and traps can't be turned off.) + If you don't have any atomic operation, you can't make one. Fortunately, the hardware guys give us atomic ops. + If you have any atomic operation, you can use it to gen- erate higher-level constructs and make parallel programs work correctly. In this class, we'll be using some spe- cial atomic operations that allow us to make computations deterministic. - .5 - Topic: Synchronization: The Too-Much-Milk Problem + The ``too much milk'' problem: Person A Person B 3:00 Look in fridge. Out of milk. 3:05 Leave for store. 3:10 Arrive at store. Look in fridge. Out of milk. 3:15 Leave store. Leave for store. 3:20 Arrive home, put milk away. Arrive at store. 3:25 Leave store. 3:30 Arrive home. Too much milk. + What does correct mean? We mean that somebody gets milk, but we don't get too much milk. I.e. we want exactly one milk, not 0 and not 2. + 1st attempt at computerized milk buying: Processes A & B 1 if (NoMilk) 2 { 3 if (NoNote) 4 { 5 Leave Note; 6 Buy Milk; 7 Remove Note; - .6 - 8 } 9 } + This doesn't always work: A1 A2 A3 A4 B1 B2 B3 B4 B5 B6 ... + The basic problem is that a sequence of operations can be interrupted at any time, and the other process started. Any such interruption must be accounted for in order to get correct results. + What we are trying to do is synchronize the behavior of the two processes, A and B. + Synchronization: using atomic operations to ensure correct operation of cooperating processes. + One of the most important things in synchronization is to figure out what you want to achieve. We can get synchronized if we have some means of mutual exclusion. + Mutual exclusion: Mechanisms that ensure that only one person or process is doing certain things at one time (others are excluded). E.g. only one person goes shopping at a time. Those things for which one wants exclusion is called a criti- cal section. + Critical section: A section of code, or collection of opera- - .7 - tions, in which only one process may be executing at a given time. E.g. shopping. It's a large operation that we want to make ``sort of'' atomic. Alternately, a critical section is the code that manipulates shared state- only one process can manipulate the state at one time. + There are many ways to achieve mutual exclusion. Most involve some sort of locking mechanism: prevent someone from doing something. I.e. we lock them out of our synchronization mechanism while we are using it. For example, before shop- ping, leave a note on the refrigerator. + Three elements of locking: 1. Must lock before using/doing.leave note 2. Must unlock when done. remove note 3. Must wait if locked. don't shop if note + This solution works for people because lines 1-5 are per- formed atomically: you'll see the other person at the refri- gerator and make arrangements. Typically, computers don't both test (look for other person) and set (leave note) at the same time. + In this case, we haven't eliminated the problem; we've just moved it and made it a little less likely (i.e. a little more insidious). This is typical of first at- tempts at solutions to synchronization problems. (Syn- chronization problems are often quite hard to get right.) - .8 - + What happens if we leave the note at the very beginning: does this make everything work? + 2nd attempt: change meaning of note. A buys if there's no note, B buys if there is a note. This gets rid of confusion. + This procedure is executed when milk is desired: Process A 1 if (NoNote) 3 if (NoMilk) 5 Buy Milk; 7 Leave Note; Process B 1 if (Note) 3 if (NoMilk) 5 Buy Milk; 7 Remove Note; + Does this work? (result is A& B buy milk alternately. B doesn't buy milk until the first time A has bought milk. Thereafter they alternate.) + How can you tell? + Ideally, we shouldn't rely on intuitions or informal rea- soning when dealing with with complex parallel programs: - .9 - we should be able to prove that they behave correctly. Unfortunately, formal verification has only been success- ful on very small programs (too hard to do). For exam- ple, in the above example: + A note will be left only by A, and only if there isn't already a note. + A note will be removed only by B, and only if there is a note. + Thus, there is either one note, or no note. + If there is a note, only B will buy milk. + If there is not a note, only A will buy milk. + Thus, only one process will buy milk. + Suppose B goes on vacation. A will buy milk once and won't buy any more until B returns. Thus this really doesn't really do what we want; it's unfair, and leads to starvation. + Conditions for correctness: + If there is no milk, someone will buy it. (i.e. no inde- finite waiting, even if one process moves out of the apartment, goes on vacation, etc.) - i.e. no deadlocks. + At most one unit of milk will be purchased (they don't both go shopping). - .10 - + 3rd attempt: use 2 notes. Process A 1 Leave NoteA; 2 if (NoNoteB) 3 { 4 if (NoMilk) 5 { 6 Buy Milk; 7 } 8 } 9 Remove NoteA; Process B 1 Leave NoteB; 2 if (NoNoteA) 3 { 4 if (NoMilk) 5 { 6 Buy Milk; 7 } 8 } 9 Remove NoteB; (Process B is the same except interchange NoteA and NoteB.) What can you say about this solution? (Look at the boundary - .11 - between statements 1 and 2) + At most one process will buy milk: each process leaves note before it checks. + If one process goes on vacation after step 9, the other process will still buy milk. + Suppose both A and B leave notes at exactly the same time: nobody will buy milk (there is still starvation). + Solution is almost correct. We just need a way to decide who will buy milk when both leave notes (somebody has to hang around to make sure that the job gets done). + 4th attempt: in case of tie, A will buy milk. Process A 1 Leave NoteA; 2 while (NoteB) DoNothing; 3 if (NoMilk) 5 Buy Milk; 7 Remove NoteA; Process B 2 Leave NoteB; 3 if (NoNoteA) 4 if (NoMilk) 6 Buy Milk; - .12 - 8 Remove NoteB; This solution works. But it still has disadvantages: + A may have to wait while B is at the store (if it wants milk it will have to wait anyway). + While A is waiting it is consuming resources (busy- waiting). + Possibility of "death" in critical section. + Possibility of third roommate. + Note again that just putting stuff on one line doesn't make it atomic. Recall what an atomic operation is. + Alternate solution (from Peterson, page 143): Init: flagA=flagB=false turn=A Process A: flagA=true turn=A while ((flagB==true) && (turn ==A)) do noop flagA=false - .13 - Process B: flagB=true turn=B while ((flagA==true) && (turn==B)) do noop 0) { V(OKToWrite); AW = AW+1; WW = WW-1; } V(Mutex); + Writer Process: P(Mutex); if ((AW+AR+WW) == 0) (do we need WW?) { V(OKToWrite); AW = AW+1; } else WW = WW+1; - .23 - V(Mutex); P(OKToWrite); -- write the necessary data; P(Mutex); AW = AW-1; if (WW>0) { V(OKToWrite); AW = AW+1; WW = WW-1; } else while (WR>0) { V(OKToRead); AR = AR+1; WR = WR-1; } V(Mutex); + Go through several examples: + Reader enters and leaves system. + Writer enters and leaves system. + Two readers enter system. + Writer enters system and waits. + Reader enters system and waits. + Readers leave system, writer continues. + Writer leaves system, last reader continues and leaves. - .24 - + Questions: + In case of conflict between readers and writers, who gets priority? + Is the WW necessary in the writer's first if? + Can OKToRead ever get greater than 1? What about OK- ToWrite? + Is the first writer to execute P(Mutex) guaranteed to be the first writer to access the data? Dining Philosophers Problem + Assume 5 philosphers (works for N). Out to dinner at Chinese restaurant. Seated at circular table, with one chopstick between each pair of philosophers. Philosophers need 2 chopsticks to eat. + Assume solution must be: + Symetric - all philosophers use same algorithm + Can't number the philosophers as part of the solution. (can refer to them with numbers) + Efficient - more than one philosopher eats + No central control. + Obvious solution: + (a) pick up left chopstick + (b) pick up right chopstick; wait if necessary, + (c) eat. - .25 - + Fails, due to immediate deadlock - each philosopher ends up with one chopstick. Each is waiting for right chopstick. + Second solution: + (a) pick up left chopstick, + (b) if available, pick up right chopstick, else, + (b1) put down left chopstick, + (b2) wait for right chopstick, + (b3) pick up right chopstick, + (b4) pick up left chopstick; wait if necessary, + (c) eat. + Fails, in opposite order - now each is waiting for left chopstick. + Solution: N philosophers semaphore mutex (init 1) used for mutual exclusion in access to variables array H(0:N), init `not hungry' values `not hungry, hungry, eating' semaphore array prisem(0:N), init (0) ("private semaphore") procedure test(me): if H((left)~=eating and H(me)=hungry and H(right) ~= eating do begin H(me):=eating - .26 - V(prisem(me)) end cycle begin think (philosopher me) P(mutex) H(me):=hungry; test(me) V(mutex) P(prisem(me)) eat P(mutex) H(me):=not hungry test(left) test(right) V(mutex) end + Idea here is that a private semaphore is used to control the progress of each process, and a common semaphore is used to allow for unambiguous inspection and modification of common state variables. + Solution is free of deadlock, but permits unbounded delay. A given philospher can starve to death, depending on the sequence of who eats. - .27 - + Threads (read section 4.5, in Silberschatz and Galvin, 5.1-5.3 in Silberschatz, Galvin and Gagne, and Birrell paper (#4) in readings + Thread, also called a lightweight process, is a type of process. + Thread has its own program counter, register set values, and stack. + Thread shares with 1 or more threads its code, data, and OS resources such as open files. + Task consists of the set of threads sharing code, data, etc. A task with one thread is an ordinary (heavy weight) process. + Switching between threads is much lower overhead than switching between separate processes. Only need to reload user registers, not change entire PCB (e.g. accounting info, etc.). + In some cases, thread switching can be done by code in user-level libraries, so no OS call is required. This is much(!) lower overhead. + Note that if thread switching is done by user, then OS doesn't know about it. Therefore, if one thread is blocked by OS, all are blocked. Also, OS will allocate time per task, even though it may have many threads. + A thread can create child threads of its own. + Note that since memory is shared, there is low overhead sharing, but no protection. + Why Use Threads?: - .28 - + On Uniprocessor, may provide more convenient model for programming than normal sequential program. (Does not inherantly provide higher efficiency.) + On (shared memory) Multiprocessor, may provide parallelism, since different threads can run on different processors in parallel. + Note that OS must do scheduling if multiprocessors involved (at least to set them up and running on each processor.) + Lower overhead (task switching, memory sharing) than separate parallel (heavyweight) processes. - .29 - + Process Synchronization with Condition Variables + Processor or threads can cooperate using wait and signal, along with condition variables. + The operation x.wait means that the process invoking it waits until some other process invokes x.signal. + The x.signal operation resumes exactly one suspended process. If no process is suspended, then x.signal has no effect. + x.signal and x.wait are used to control synchronization within Monitors, which is a special type of critical region. Only one process can be executing in a monitor at a time. + There is one binary semaphore associated with each monitor, mutual exclusion is implicit: P on entry to any routine, V on exit. + Monitors are a higher-level concept than P and V. They are easier and safer to use. + Monitors are a synchronization mechanism combining three features: + Shared data. + Operations on the data. + Synchronization, scheduling. They are especially convenient for synchronization involving lots of state. + Monitors need more facilities than just mutual exclusion. Need some way to wait. - .30 - + Busy-wait inside monitor? + Put process to sleep inside monitor? + Condition variables: things to wait on. + Wait(condition): release monitor lock, put process to sleep. When process wakes up again, re-acquire monitor lock immediately. + Signal(condition): wake up one process waiting on the condition variable (FIFO). If nobody waiting, do nothing (no history). + Broadcast(condition): wake up all processes waiting on the condition variable. If nobody waiting, do nothing. + There are several different variations on the wait/signal mechanism. They vary in terms of who gets the monitor lock after a signal. Our scheme is called ``Mesa semantics'': + On signal, signaller keeps monitor lock. + Awakened process waits for monitor lock with no special priority (a new process could get in before it). This means that the thing you were waiting for could have come and gone: must check again and be prepared to sleep again if someone else took it. + Readers and writers problem with monitors: each synchronization operation gets encapsulated in a monitored procedure: checkRead, checkWrite, doneRead, doneWrite. Use conditions OKToRead, OKToWrite. + This is all part of one monitor - .31 - + checkRead() { if ((AW+WW) > 0) { WR = WR+1; wait(OKToRead); WR =WR-1; } AR = AR+1 ; READ } + doneRead() { AR = AR-1; if (AR==0 & WW>0) signal(OKToWrite); } + checkWrite() { while ((AW+AR) > 0) { WW = WW+1; wait(OKToWrite); WW = WW-1; } AW += 1; WRITE } + doneWrite() { AW -= 1; if (WW > 0) signal(OKToWrite); else broadcast(OKToRead); } - .32 - + Producers and Consumers (from Hoare): bounded buffer: monitor begin buffer: array 0..N-1 of portion last pointer:0..N-1; count:0..N; nonempty, nonfull: condition; procedure append(x; portion); begin if count==N then nonfull.wait; buffer[lastpointer] =x; last pointer = (lastpointer + 1) mod N count = count+1; nonempty.signal end append; procedure remove (result x; portion); begin if count==0 then nonempty.wait; x=buffer[(lastpointer - count) mod N]; count=count-1; nonfull.signal; end remove count=0; lastpointer=0; end bounded buffer - .33 - + Disk Head Scheduler (from Hoare): procedure request- called before issuing command to move head to dest. procedure release - called after cylinder is finished. headpos - current location of head sweep - up or down - direction of head movement busy - whether disk is busy diskhead: monitor begin: headpos: cylinder; direction: (up, down); busy: Boolean; upsweep, downsweep: condition; procedure request(dest:cylinder); begin if busy then [if {((headpos < dest) or [headpos == dest & direction==up]) then upsweep.wait(dest) else downsweep.wait(dest)}]; else [busy=true; headpos=dest;] end request; procedure release; begin busy=false; if direction==up then if {upsweep.queue then upsweep.signal else {direction=down; downsweep.signal}} else if downsweep.queue then downsweep.signal else {direction=up; upsweep.signal} end release; - .34 - headpos=0; direction=up; busy=false; end diskhead - .35 - + Summary: + Not present in very many languages, but extremely useful. + Semaphores use a single structure for both exclusion and scheduling, monitors use different structures for each. + Monitors enforce a style of programming where complex synchronization code doesn't get mixed with other code: it is separated and put in monitors. + A mechanism similar to wait/signal is used internally in Unix for scheduling OS processes. - .36 - + Unix: + Unix uses generalized semaphores. They are created in sets. Several operations on semaphores can be done simultaneously, and increments and decrements can be by values greater than 1. The kernel does these operations atomically. + Associated with each semaphore is a queue of processes suspended on that semaphore. + The semop system call takes a list of semaphore ops (sem-op), each defined on a semaphore in the set, and does them one at a time. + If sem-op is positive, kernel increments value of semaphore and awakens all processes waiting for the value of the semaphore to increase + If sem-op is zero, kernel checks semaphore value. If 0, continues with list. Otherwise, blocks process, and has it wait on this semaphore. + If sem-op is negative, and its absolute value is less than or equal to the semaphore value, the kernel adds sem-op (a negative number) to the semaphore value. If the result is 0, the kernel awakens all processes waiting for the value of the semaphore to equal 0. + If sem-op is negative, and its absolute value is greater than the semaphore value, the kernel suspends the process until the value of the sema- phore increases. - .37 - + Unix also uses signals. Processes may send each other signals, which are software interrupts. A signal is pro- cessed when a process wakes up, or when it returns from a system call. There are about 20 defined signals. (e.g. interrupt, quit, illegal instruction, trace trap, float- ing point exception, kill, bus error, segmentation viola- tion, write on a pipe with no readers, alarm clock, death of a child, power failure, etc.)