260 likes | 407 Views
Process Synchronization. COS 431 Fall, 2000. process A. process B. numStudents = 3. Joe. 0 1 2 3 4 5. Mary. interrupt. interrupt. Zelda. Students[num] = “Joey”;. Henry. Joey. Students[num] = “Henry”;. Students. Coordination of Processes.
E N D
Process Synchronization COS 431 Fall, 2000
process A process B numStudents = 3 Joe 0 1 2 3 4 5 Mary interrupt interrupt Zelda Students[num] = “Joey”; Henry Joey Students[num] = “Henry”; Students Coordination of Processes • Often, processes need to synchronize their activities • sharing resources • when cooperating • Unfortunately, processes are asynchronous by nature • Without some means of synchronization: race condition num = numStudents; num = numStudents; num++; numStudents = num; num++; numStudents = num;
Critical Sections/Regions • Solution: mutual exclusion • Critical regions (critical sections) • Criteria for solution to race conditions: • mutual exclusion • make no assumption about speed, num. CPUs • no process outside its critical region can block another • no starvation
A Naïve Approach: Disable Interrupts • One CPU, if process can disable all interrupts, can guarantee no race conditions • have to disable interrupts from I/O • have to disable timer interrupts • Problem: • too much power for user process -- usurps kernel’s power • if anything goes wrong -- may hang entire computer! • doesn’t work for multiple CPUs • Kernel needs the ability to do this, though • In some multithreaded applications, this can be used (e.g., Lisp)
Another Simple Approach: Locks • Use a single shared variable as a lock • Could also be a shared file • Process looks at variable -- if 0, busy waits, if not set to 0 • Problems • May not be atomic • Processes may cheat • Some processors have special instructions (TSL) that help this out, though
Turn-Taking: Strict Alternation • Idea: …non-critical section… while (turn != my-turn) ; /* busy wait */ critical_section(); turn = next_turn(turn); …non-critical section… • What if one of the processes is faster than the others? • What if a process is I/O bound? • Processes block others when not in its critical region
Turn-Taking: Alternation with Interest(Peterson’s Solution) • Idea -- process declares its interest before it tries for critical region • Other processes that are interested block if it’s not their turn and if someone else who is interested is in critical section • If only one process ready… • If two processes ready, one in its critical section… • Two processes both simultaneously call enter_region... #define FALSE 0 #define TRUE 1 #define N 2 /* num processes */ int turn int interested[N]; void enter_region(int process) { int other; other = 1 - process; interested[process] = TRUE; turn = process; while (turn == process && interested[other] == TRUE); } void leave_region(int process) { interested[process] = FALSE; }
“Bakery” Algorithm • For multiple processes • Every process that wants to enter critical section given a number • Lowest number goes first • Can’t guarantee that two won’t have same number… • …in that case, use tie-breaking convention BOOLEAN choosing[MAXPROC]; /* picking number or not */ int number[MAXPROC]; /* each element corresponds to a process */ void enter(i) { choosing[i] = TRUE; /* we’re picking a number */ number[i] = find_max(number) + 1; /* select next highest number */ choosing[i] = FALSE; /* done */ for (j=0; j<n; j++) { while (choosing[j]) ; /* busy wait if something is choosing number */ /* busy wait till lower proc. with lower number is done */ while ( (number[j] != 0) && customer_lt(j,i) ); } } void leave(i) { number[i] = 0; } • What if just one process...? • What if n, with one in critical region...? • What if n try to enter critical region at same time...?
Hardware Support for Synchronization • Locks don’t work in general: • atomicity • multiple processors? • Some processors : test-and-set-lock (TSL) instruction • When TSL executed: • locks memory bus • then: • sets a register to the value of some memory location (the lock) • sets the lock to a value meaning “locked” (e.g., 1) • then unlocks the memory bus • Why does this work?
Mutual Exclusion Using TSL enter: tsl r1,LOCK ! check LOCK cmp r1,#0 ! compare old value to 0 jnz enter ! if not zero, then it was locked, ! so loop ret ! otherwise, return leave: mov LOCK,#0 ! just clear the lock ret
What’s Wrong with Busy Waiting? • Wastes processor time • Priority inversion problem • Example: • Processes busy wait when can’t enter critical region • H: high-priority job • L: low-priority job • Policy: always run high-priority jobs when possible • Scenario: • H: waiting on I/O • L: runs, enters critical region • H: I/O completes - context switch to H • H: tries to enter its critical region • What does H do? • What does L do?
Better Idea: Blocking • Make process not runnable instead of busy waiting • How is this done? • When does the process wake up? • Traditional example: the bounded buffer problem • Buffer: storage area with n slots • One process is a producer • Other is a consumer • Buffer full: producer blocks • Buffer empty: consumer • Example: queues • Relies on producer waking consumer and vice versa
Producer/Consumer Problem #define N 100 int count 0; int buffer[N]; pid_t producer, consumer; void producer() { int item; while (TRUE) { item = produce_item(); if (count == N) sleep(); enter_item(item); count++; if (count == 1) wakeup(consumer); } } void consumer() { int item; if (count == 0) sleep(); item = remove_item(); count--; if (count == N-1) wakeup(producer); consume_item(item); } } void consume_item(int item) { printf(“item=%d\n”,item); } int produce_item() { int item; printf(“\nNext item? “); scanf(“%d”,&item); return(item); }
Problems with Sleep/Wakeup • Signals can be lost due to timing • producer sees buffer full, but interrupted before sleep • consumer takes something from buffer, sends signal -- which is lost • producer goes to sleep • consumer continues taking stuff out, ultimately ==> sleeps • both are sleeping! • Can add some machinery to kernel -- bit in PCB, e.g. -- tells when signal is waiting • But with > 2 processes, need more bits for the processes ==> arbitrary number of bits needed
Semaphores • Motivation: problems with blocking • Idea: • standard way to block processes • data structure (semaphores) + two functions defined on them • Semaphores: • enforce mutual exclusion • can be used for producer-consumer problems • Data structure: essentially just an integer -- say, S • Operations: • P(S) -- also known as Down(S) -- decrement S • V(S) -- also known as Up(S) -- increment S • Down(S) and S=0: block caller. • Up(S) and process blocked on S => unblock it • Down and Up have to be atomic ==> system calls
Semaphores • One way to implement: typedef structure semaphore { int count = 1; queue procs; }; void Down(semaphore S) { S.count--; if (S.count < 0) { enqueue(current_proc(),S.procs); block(current_proc()); } } void Up(semaphore S) { process P; S.count++; if (S.count <= 0) { P = dequeue(S.queue); wakeup(P); } }
Using Semaphores for Mutual Exclusion • Traditional name for mutual exclusion semaphore is mutex • Example from the student roster program before: int num_students; int roster[MAXSTUDENTS]; semaphore mutex; ... void add_student(int id) { int currno; Down(mutex); /* critical region starts here */ currno = num_students; roster[currno] = id; currno++; num_students = currno; /* end critical region */ Up(mutex); return; } ...
Semaphores and Bounded Buffers • Single-bounded buffer • Ex: a print queue • Something else (we won’t worry about what right now) takes items out of the queue • What if the queue is full? • What if the queue is empty? • Semaphores needed: • mutex - a binary semaphore • empty - • counts empty slots • initialized to number of slots in queue
Print Queue Example filename buffer[SLOTS]; int next_slot = 0; semaphore mutex, empty; set_count(empty,SLOTS); /* would really be set by daemon */ ... void print_file(filename file) { Down(empty); Down(mutex); /* within critical region now */ buffer[next_slot] = file; next_slot++; Up(mutex); } • Why is there no Up(empty)? • What if buffer is empty? • What if buffer is full?
Producer/Consumer Problems and Semaphores • Complete bounded buffer problem • Ex.: Message queue: • process A writes to it • process B reads from it • limited size • Synchronization problem? • Solution uses three semaphores: • mutex, a binary semaphore • empty: a counting semaphore -- keeps track of empty slots • full: a counting semaphore -- keeps track of full slots
Producer/Consumer message buffer[BUFFSIZE]; semaphore empty, full, mutex; set_count(empty,BUFFSIZE); set_count(full,0); void producer() { message msg; while (TRUE) { msg = compose_message(); Down(empty); Down(mutex); /* critical region */ put_message(buffer,msg); /* end critical region */ Up(mutex); Up(full); } } void consumer() { message msg; while (TRUE) { Down(full); Down(mutex); /* critical region */ msg = get_message(buffer); /* end critical region */ Up(mutex); Up(empty); use_message(msg); } }
Common Problems When Using Semaphores • Careful not to get carried way with mutex calls! Don’t: void fcn1() { Down(mutex); fcn2(); Up(mutex); } void fcn2() { Down(mutex); ... Up(mutex); }
T S 1 1 0 0 -1 blocked -1 blocked Problems Using Semaphores • Careful not to cross semaphore calls: P1: Down(S); Down(T); ... Up(T); Up(S); • P2: • Down(T); • Down(S); • ... • Up(S); • Up(T);
Semaphores in Unix • POSIX-compliant Unix: • sem_t is the semaphore type • sem_wait ==> Down • sem_post ==> Up • Used for threads (or processes, if shared) • Some Unix systems: only allocate arrays of semaphores • semget -- creates a set of semaphores as shared memory objects, or returns an identifier for such a set • semop--perform operations on the semaphore set