synchronization csci 3753 operating systems spring 2005 prof. rick han
Post on 19-Dec-2015
247 views
TRANSCRIPT
Synchronization
CSCI 3753 Operating Systems
Spring 2005
Prof. Rick Han
Announcements
• HW #3 is online, due Friday Feb. 25 at 11 am
• PA #2 is coming, assigned next Tuesday
• Midterm is tentatively Thursday March 10
• Read chapter 8
From last time...
• Many cases where processes and threads want to access shared common variables, buffers, etc.
• Producer-Consumer with a shared bounded buffer in between– Producer increments counter++– Consumer decrements counter--
• race conditions between producer and consumer– Machine-level instructions can be interleaved,
resulting in unpredictable value of shared counter variable
Synchronization
while(1) { while(counter==MAX); buffer[in] = nextdata; in = (in+1) % MAX; counter++;}
Data
Bounded Buffer
counter
ProducerProcess
ConsumerProcess
while(1) { while(counter==0); getdata = buffer[out]; out = (out+1) % MAX; counter--;}
Producer writes new data intobuffer and increments counter
Consumer reads new data frombuffer and decrements counter
counterupdates
can conflict!
buffer[0]
buffer[MAX]
Synchronization
counter++; can translate into several machine language instructions, e.g.
reg1 = counter;
reg1 = reg1 + 1;
counter = reg1;
counter--; can translate into several machine language instructions, e.g.
reg2 = counter;
reg2 = reg2 - 1;
counter = reg2;
If these low-level instructions are interleaved, e.g. the Producer processis context-switched out, and the Consumer process is context-switched in,and vice versa, then the results of counter’s value can be unpredictable
Synchronization
// counter++ reg1 = counter; reg1 = reg1 + 1; counter = reg1;
// counter--;
reg2 = counter;
reg2 = reg2 - 1;
counter = reg2;
(1) [5](3) [6]
(2) [5](4) [4]
(5) [6]
(6) [4]
• Suppose we have the following sequence of interleaving, where the brackets [value] denote the local value of counter in either the producer or consumer’s process. Let counter=5 initially.
• At the end, counter = 4. But if steps (5) and (6) were reversed, then counter=6 !!!
• Value of shared variable counter changes depending upon (an arbitrary) order of writes - very undesirable!
Critical Section• Kernel data structures - e.g. access to list of open files subject to
race condition• Kernel developer must ensure that no such race conditions occur• Identify critical sections in code where each process access shared
variables
do { entry section critical section (manipulate common var’s) exit section remainder code} while (1)
mutual exclusion, bounded waiting, progress
Critical Section
• How do we protect access to critical sections?– want to prevent another process from
executing while current process is modifying this variable - mutual exclusion
– disable interrupts• bad, because I/O may be prevented• bad, because program may have an infinite loop
Critical Section
• Don’t disable interrupts for the entire time you’re in the critical section
• Solution: Set a variable/flag called a lock to indicate that the critical section is busy, then unset the flag when critical section is done– doesn’t disable interrupts in the critical section– also Peterson’s solution
Using a Lock Variableshared boolean lock = FALSE;shared int counter;
Code for p1 Code for p2
/* Acquire the lock */ /* Acquire the lock */ while(lock) ; while(lock) ; lock = TRUE; lock = TRUE;/* Execute critical sect */ /* Execute critical sect */ counter++; counter--;/* Release lock */ /* Release lock */ lock = FALSE; lock = FALSE;
Busy Wait Conditionshared boolean lock = FALSE;shared int counter;
Code for p1 Code for p2
/* Acquire the lock */ /* Acquire the lock */ while(lock) ; while(lock) ; lock = TRUE; lock = TRUE;/* Execute critical sect */ /* Execute critical sect */ counter++; counter--;/* Release lock */ /* Release lock */ lock = FALSE; lock = FALSE;
p1
p2
Blo
cked
at while
lock = TRUE
lock = FALSE
Inte
rrup
t
Inte
rrup
t
Inte
rrup
t
Unsafe “Solution”shared boolean lock = FALSE;shared double balance;
Code for p1 Code for p2
/* Acquire the lock */ /* Acquire the lock */ while(lock) ; while(lock) ; lock = TRUE; lock = TRUE;/* Execute critical sect */ /* Execute critical sect */ counter++; counter--;/* Release lock */ /* Release lock */ lock = FALSE; lock = FALSE;
• Worse yet … another race condition …
• Is it possible to solve the problem?
Critical Section
while (lock==TRUE) ;lock = TRUE;
• test-and-set solution doesn’t work if process is interrupted right after while()
• Want an atomic instruction testandset() that tests shared variable lock then sets it
• Some hardware provides such an instruction
Atomic Lock Manipulationshared boolean lock = FALSE;shared int counter;
Code for p1 Code for p2
/* Acquire the lock */ /* Acquire the lock */ while(TestandSet(&lock)) ; while(TestandSet(&lock)) ;
/* Execute critical sect */ /* Execute critical sect */ counter++; counter--;/* Release lock */ /* Release lock */ lock = FALSE; lock = FALSE;______________________________________________________________
boolean TestandSet(boolean *target) { // this is atomic, one machine instruction boolean rv = *target; *target = TRUE; return rv;}
Atomic Lock Manipulation
• The system is exclusively occupied for only a short time - the time to test and set the lock, and not for entire critical section– about 10 instructions
• don’t have to disable and reenable interrupts - time-consuming
• disadvantage: requires user to put in while()• low level assembly language manipulation of a
machine instruction TS
Bounded Buffer Problem
ProducerProducer ConsumerConsumer
Empty Pool
Full Pool
Bounded Buffer Problem (2)producer() { buf_type *next, *here; while(TRUE) { produce_item(next); /* Claim an empty */ P(empty); P(mutex); here = obtain(empty); V(mutex); copy_buffer(next, here); P(mutex); release(here, fullPool); V(mutex); /* Signal a full buffer */ V(full); }}semaphore mutex = 1;semaphore full = 0; /* A general (counting) semaphore */semaphore empty = N; /* A general (counting) semaphore */buf_type buffer[N];fork(producer, 0);fork(consumer, 0);
consumer() { buf_type *next, *here; while(TRUE) { /* Claim full buffer */ P(mutex); P(full); here = obtain(full); V(mutex); copy_buffer(here, next); P(mutex); release(here, emptyPool); V(mutex); /* Signal an empty buffer */ V(empty); consume_item(next); }}
Bounded Buffer Problem (3)producer() { buf_type *next, *here; while(TRUE) { produce_item(next); /* Claim an empty */ P(empty); P(mutex); here = obtain(empty); V(mutex); copy_buffer(next, here); P(mutex); release(here, fullPool); V(mutex); /* Signal a full buffer */ V(full); }}semaphore mutex = 1;semaphore full = 0; /* A general (counting) semaphore */semaphore empty = N; /* A general (counting) semaphore */buf_type buffer[N];fork(producer, 0);fork(consumer, 0);
consumer() { buf_type *next, *here; while(TRUE) { /* Claim full buffer */ P(full); P(mutex); here = obtain(full); V(mutex); copy_buffer(here, next); P(mutex); release(here, emptyPool); V(mutex); /* Signal an empty buffer */ V(empty); consume_item(next); }}
Readers-Writers Problem
Readers
Writers
Readers-Writers Problem (2)
ReaderReader
Shared Resource
ReaderReaderReaderReader
ReaderReaderReaderReaderReaderReader
ReaderReaderReaderReader
WriterWriterWriterWriterWriterWriter
WriterWriterWriterWriterWriterWriter
WriterWriter
Readers-Writers Problem (3)
ReaderReader
Shared Resource
ReaderReaderReaderReaderReaderReaderReaderReader
ReaderReaderReaderReaderReaderReader
WriterWriterWriterWriterWriterWriter
WriterWriterWriterWriterWriterWriter
WriterWriter
Readers-Writers Problem (4)
ReaderReader
Shared Resource
ReaderReaderReaderReader
ReaderReaderReaderReaderReaderReader
ReaderReaderReaderReader
WriterWriterWriterWriterWriterWriter
WriterWriterWriterWriterWriterWriter
WriterWriter
First Solutionreader() { while(TRUE) { <other computing>; P(mutex); readCount++; if(readCount == 1) P(writeBlock); V(mutex); /* Critical section */ access(resource); P(mutex); readCount--; if(readCount == 0) V(writeBlock); V(mutex); }}resourceType *resource;int readCount = 0;semaphore mutex = 1;semaphore writeBlock = 1;fork(reader, 0);fork(writer, 0);
writer() { while(TRUE) { <other computing>; P(writeBlock); /* Critical section */ access(resource); V(writeBlock); }}
•First reader competes with writers•Last reader signals writers
First Solution (2)reader() { while(TRUE) { <other computing>; P(mutex); readCount++; if(readCount == 1) P(writeBlock); V(mutex); /* Critical section */ access(resource); P(mutex); readCount--; if(readCount == 0) V(writeBlock); V(mutex); }}resourceType *resource;int readCount = 0;semaphore mutex = 1;semaphore writeBlock = 1;fork(reader, 0);fork(writer, 0);
writer() { while(TRUE) { <other computing>; P(writeBlock); /* Critical section */ access(resource); V(writeBlock); }}
•First reader competes with writers•Last reader signals writers•Any writer must wait for all readers•Readers can starve writers•“Updates” can be delayed forever•May not be what we want
Writer Precedencereader() { while(TRUE) { <other computing>;
P(readBlock); P(mutex1); readCount++; if(readCount == 1) P(writeBlock); V(mutex1); V(readBlock);
access(resource); P(mutex1); readCount--; if(readCount == 0) V(writeBlock); V(mutex1); }}int readCount = 0, writeCount = 0;semaphore mutex = 1, mutex2 = 1;semaphore readBlock = 1, writeBlock = 1, writePending = 1;fork(reader, 0);fork(writer, 0);
writer() { while(TRUE) { <other computing>; P(mutex2); writeCount++; if(writeCount == 1) P(readBlock); V(mutex2); P(writeBlock); access(resource); V(writeBlock); P(mutex2) writeCount--; if(writeCount == 0) V(readBlock); V(mutex2); }}
1
23
4
Writer Precedence (2)reader() { while(TRUE) { <other computing>; P(writePending); P(readBlock); P(mutex1); readCount++; if(readCount == 1) P(writeBlock); V(mutex1); V(readBlock); V(writePending); access(resource); P(mutex1); readCount--; if(readCount == 0) V(writeBlock); V(mutex1); }}int readCount = 0, writeCount = 0;semaphore mutex = 1, mutex2 = 1;semaphore readBlock = 1, writeBlock = 1, writePending = 1;fork(reader, 0);fork(writer, 0);
writer() { while(TRUE) { <other computing>; P(mutex2); writeCount++; if(writeCount == 1) P(readBlock); V(mutex2); P(writeBlock); access(resource); V(writeBlock); P(mutex2) writeCount--; if(writeCount == 0) V(readBlock); V(mutex2); }}
1
23
4
The Sleepy Barber
Waiting Room
Entrance to WaitingRoom (sliding door)
Entrance to Barber’sRoom (sliding door)
Shop Exit
• Barber can cut one person’s hair at a time
• Other customers wait in a waiting room
Sleepy Barber (aka Bounded Buffer)customer() {
while(TRUE) { customer = nextCustomer(); if(emptyChairs == 0) continue; P(chair); P(mutex); emptyChairs--; takeChair(customer); V(mutex); V(waitingCustomer); }}
semaphore mutex = 1, chair = N, waitingCustomer = 0;int emptyChairs = N;fork(customer, 0);fork(barber, 0);
barber() { while(TRUE) { P(waitingCustomer); P(mutex); emptyChairs++; takeCustomer(); V(mutex); V(chair); }}
Cigarette Smoker’s Problem
• Three smokers (processes)
• Each wish to use tobacco, papers, & matches– Only need the three resources periodically– Must have all at once
• 3 processes sharing 3 resources– Solvable, but difficult
Implementing Semaphores
• Minimize effect on the I/O system
• Processes are only blocked on their own critical sections (not critical sections that they should not care about)
• If disabling interrupts, be sure to bound the time they are disabled
Implementing Semaphores:enter() & exit()
class semaphore { int value;public: semaphore(int v = 1) { value = v;}; P(){ disableInterrupts(); while(value == 0) { enableInterrupts(); disableInterrupts(); } value--; enableInterrupts(); }; V(){ disableInterrupts(); value++; enableInterrupts(); };};
Implementing Semaphores:Test and Set Instruction
FALSEm
PrimaryMemory
…R3 …
DataRegister
CCRegister
(a) Before Executing TS
TRUEm
PrimaryMemory
FALSER3 =0
DataRegister
CCRegister
(b) After Executing TS
• TS(m): [Reg_i = memory[m]; memory[m] = TRUE;]
Using the TS Instruction
boolean s = FALSE; . . . while(TS(s)) ; <critical section> s = FALSE; . . .
semaphore s = 1; . . . P(s) ; <critical section> V(s); . . .
Implementing the General Semaphore
struct semaphore { int value = <initial value>; boolean mutex = FALSE; boolean hold = TRUE;};
shared struct semaphore s;
P(struct semaphore s) { while(TS(s.mutex)) ; s.value--; if(s.value < 0) ( s.mutex = FALSE; while(TS(s.hold)) ; } else s.mutex = FALSE;}
V(struct semaphore s) { while(TS(s.mutex)) ; s.value++; if(s.value <= 0) ( while(!s.hold) ; s.hold = FALSE; } s.mutex = FALSE;}
Active vs Passive Semaphores• A process can dominate the semaphore
– Performs V operation, but continues to execute
– Performs another P operation before releasing the CPU
– Called a passive implementation of V
• Active implementation calls scheduler as part of the V operation.– Changes semantics of semaphore!– Cause people to rethink solutions
Atomic Lock Manipulation
shared double balance;
shared int lock = FALSE;
P1
enter(lock);
counter++;
exit(lock);
P2
enter(lock); // atomic system call
counter--;
exit(lock);
Atomic Lock Manipulation
enter(lock) { exit(lock) { disableInterrupts(); disableInterrupts();/* Loop until lock is TRUE */ lock = FALSE; while(lock) { enableInterrupts(); /* Let interrupts occur */ } enableInterrupts(); disableInterrupts(); } lock = TRUE; enableInterrupts();}
• Bound the amount of time that interrupts are disabled• Can include other code to check that it is OK to assign a lock• … but this is still overkill …
Critical Section
do { flag[i] = TRUE
turn = j; while (flag[j] && turn==j);
critical section
flag[i] = FALSE
remainder section
} while (TRUE)
Critical Section
• Solution: instead disable only on entry and exit. Use a shared variable to control access to another shared variable.– this introduces complexity - what if you’re
interrupted while having the lock?