Process Synchronization
description
Transcript of Process Synchronization
Process Synchronization
COS 431
Fall, 2000
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
Students
012345
process A process BnumStudents = 3
Joe
Mary
Zelda
num++;numStudents = num;
num++;numStudents = num;
interrupt
interrupt
num = numStudents;
num = numStudents;
Joey
Students[num] = “Joey”;
Students[num] = “Henry”;
Henry
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
#define FALSE 0#define TRUE 1#define N 2 /* num processes */
int turnint 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;}
• If only one process ready…• If two processes ready, one in its
critical section…• Two processes both simultaneously
call enter_region...
“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 0jnz enter ! if not zero, then it was locked,
! so loopret ! otherwise, return
leave:mov LOCK,#0 ! just clear the lockret
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
void producer() { int item; while (TRUE) { item = produce_item(); if (count == N) sleep(); enter_item(item); count++; if (count == 1) wakeup(consumer); }}
#define N 100int count 0;int buffer[N];pid_t producer, consumer;
void consumer() { int item; if (count == 0) sleep(); item = remove_item(); count--; if (count == N-1) wakeup(producer); consume_item(item); }}
int produce_item() { int item; printf(“\nNext item? “); scanf(“%d”,&item); return(item);}
void consume_item(int item) { printf(“item=%d\n”,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);
}
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);
T S
1 1
0
0
-1
blocked-1
blocked
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