Chapter 6 PowerPoint

advertisement
Operating System Concepts
chapter 6
CS 355
Operating Systems
Dr. Matthew Wright
Background
• Concurrent access to shared data may result in data inconsistency
• Maintaining data consistency requires mechanisms to ensure the orderly
execution of cooperating processes
• Suppose that we wanted to provide a solution to the consumer-producer
problem that fills all the buffers. We can do so by having an integer count that
keeps track of the number of full buffers. Initially, count is set to 0. It is
incremented by the producer after it produces a new buffer and is decremented
by the consumer after it consumes a buffer.
/* producer code */
while (count = BUFFER_SIZE)
; // do nothing
/* consumer code */
while (count = 0)
; // do nothing
//add an item to the buffer
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
count++;
//add an item to the buffer
item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
Implementation
• count++ could be implemented as:
register1 = count
register1 = register1 + 1
count = register 1
• count-- could be implemented as:
register2 = count
register2 = register2 – 1
count = register 2
• Consider this executing interleaving with count = 5 initially:
1.
producer executes
register1 = count
2.
producer executes
register1 = register1 + 1
3.
consumer executes
register2 = count
4.
consumer executes
register2 = register2 – 1
5.
producer executes
count = register1
6.
consumer executes
count = register2
• This creates a race condition.
(register1 = 5)
(register1 = 6)
(register2 = 5)
(register2 = 4)
(count = 6)
(count = 4)
Critical Section
• Suppose we have n processes: P0, P1, …, Pn
• Each process has a segment of code, called a critical section, in which
the process changes shared memory
• If one process is executing in its critical section, no other process ought
to execute in its critical section
• Critical-Section Problem: design a protocol to facilitate process
communication and protect shared resources
/* typical process
• Standard design:
1. Entry section: a process must request structure */
while (true) {
permission to enter critical section
entry section
2. Process executes critical section
3. Exit section: process notifies others
critical section
that it has exited its critical section
exit section
4. Remainder section: any code
remainder section
following the critical section
}
Requirements
A solution to the critical-section problem must satisfy:
1. Mutual Exclusion: If process Pi is executing in its critical section,
then no other processes can be executing in their critical sections.
2. Progress: If no process is executing in its critical section and there
exist some processes that wish to enter their critical section, then
the selection of the processes that will enter the critical section
next cannot be postponed indefinitely.
3. Bounded Waiting: A bound must exist on the number of times
that other processes are allowed to enter their critical sections
after a process has made a request to enter its critical section and
before that request is granted.
• Assume that each process executes at a nonzero speed
• No assumption concerning relative speed of the processes
Peterson’s Solution
• Works for two processes, denoted P0 and P1
• Requires that machine-language load and store operations are
atomic—that is, they cannot be interrupted
• The two processes share two data items:
int turn;
boolean flag[2];
• Variable turn indicates whose turn it is to enter the critical
section.
• Array flag indicates whether each process is ready to enter its
critical section. If flag[i] = true, then Pi is ready.
Peterson’s Solution
while (true) {
flag[i] = true;
turn = j;
while (flag[j] && turn == j)
/* wait */ ;
critical section
flag[i] = false;
remainder section
}
Examine the code to see
that this satisfies the three
requirements:
1. Mutual exclusion
2. Progress
3. Bounded Waiting
Locks
• We can prevent race conditions by protecting critical section with
locks.
• A process must acquire a lock before entering its critical section.
• A process releases a lock when it exits its critical section.
• Locks may be implemented in software or hardware.
/* solution using locks */
while (true) {
acquire lock
critical section
release lock
remainder section
}
Hardware Solutions
• Many systems provide hardware support for critical section code.
• On a single-processor system, we could disable interrupts when a
process executes its critical section.
– Currently running code would execute without preemption
– Generally too inefficient on multiprocessor systems
– Operating systems using this not broadly scalable
• Modern machines provide special atomic (non-interruptible)
hardware instructions. For example:
– Test memory word and set value
– Swap contents of two memory words
• Java HardwareData class presents an abstract view of atomic
hardware instructions
Java HardwareData
/**
/**
* Sample
data structure
for hardware solution
/**
*
Using
HardwareData
getAndSet() to implement a lock
*
*
Using
HardwareData
* Gagne, Galvin, Silberschatzswap() to implement a lock
* @author
* Gagne, Galvin, Silberschatz
* @author
* Operating
System
Concepts
with Galvin,
Java, 8th
Ed., Fig. 6.4
* @author
Gagne,
Silberschatz
*
Operating
System
Concepts
with
Java,
8th Ed., Fig. 6.5
*/
*
Operating
System
Concepts
with
Java, 8th Ed., Fig. 6.6
*/
*/
public class HardwareData
//lock is shared by all threads
{
//locklock
is shared
by all threads
HardwareData
=
new HardwareData(false);
private
boolean
value
=
false;
HardwareData lock = new HardwareData(false);
(true) {
publicwhile
HardwareData(boolean
value)
{
//each
thread has
a local
copy of key
while
(lock.getAndSet(true))
this.value =HardwareData
value;
Thread.yield(); key = new HardwareData(true);
}
while (true)
{ */
/* critical
section
public boolean
get()
{
key.set (true);
return value;
lock.set(false);
}
do {
lock.swap(key);
/* remainder
section
*/
public void
set(boolean
newValue)
{
}
}
this.value = newValue;
while (key.get() == true);
}
Each of these
methods must be
implemented
atomically.
/* critical section */
public boolean getAndSet(boolean newValue) {
boolean oldValue
= this.get();
lock.set(false);
Semaphores
• Simpler synchronization tool than
the previous
• A semaphore consists of:
– an integer variable
– atomic operations acquire()
and release()
• The value of a counting
semaphore can be any integer.
• The value of a binary semaphore
(also called a mutex lock) is only 0
or 1.
• A binary semaphore can control
access to a critical section.
/* definitions of acquire()
and release() */
acquire() {
while value <= 0
; // wait
value--;
}
release() {
value++;
/* }using a binary semaphore to
control access to a critical
section */
Semaphore sem = new Semaphore(1);
sem.acquire();
/* critical section */
sem.release();
/* remainder section */
Multiple Java Threads using Semaphores
/**
* Worker thread to demonstrate use of a semaphore
*
/**
* @author Gagne,* Galvin,
create aSilberschatz
semaphore and five threads
* Operating System
*
Concepts with Java, 8th Ed., Fig. 6.8
*/
* @author Gagne, Galvin, Silberschatz
* Operating System Concepts with Java, 8th Ed., Fig. 6.8
public class Worker
*/
implements Runnable {
private Semaphore
public
sem;
class SemaphoreFactory
{
public Worker(Semaphore
public static
sem) { void main(String args[]) {
this.sem = sem; Semaphore sem = new Semaphore(1);
}
Thread[] bees = new Thread[5];
public void run() { for (int i = 0; i < 5; i++)
while (true) {
bees[i] = new Thread(new Worker(sem));
sem.acquire();
criticalSection();
for (int i = 0; i < 5; i++)
sem.release();
bees[i].start();
remainderSection();
}
}
}
}
}
Semaphore Implementation
• Disadvantage of previous implementation:
busy waiting
• Better idea:
– When a process fails to acquire a
semaphore, it places itself in a waiting
queue associated with the semaphore.
– When a process releases a semaphore,
it restarts a process waiting for the
semaphore.
• This requires a semaphore to be an
integer value and a list of processes.
• Now value might be negative; if so, its
magnitude is the number of waiting
processes.
/* definitions of acquire()
and release() */
acquire() {
value--;
if (value <= 0) {
add this process to list
block();
}
}
release() {
value++;
if (value < 0) {
remove a process P from list
wakeup(P);
}
}
Semaphore Implementation
• Operating provides block() and
wakeup(P) as basic system calls
• The list may be implemented as any type
of queue.
• Important: acquire() and release()
must be implemented atomically—this
creates a critical section problem!
• We have transferred the critical section
problem from the application program to
the Semaphore class, but it’s progress,
since the new critical sections are short.
• Possible solutions:
– Disable interrupts during acquire()
and release()
– Use a hardware solution
/* definitions of acquire()
and release() */
acquire() {
value--;
if (value <= 0) {
add this process to list
block();
}
}
release() {
value++;
if (value < 0) {
remove a process P from list
wakeup(P);
}
}
Possible Problem: Deadlock
• A semaphore with a waiting queue can result in a situation where
two processes are waiting for an event that can only be caused by
the other waiting process.
• This is called a deadlock.
• Example:
P0
P1
S.acquire();
Q.acquire();
Q.acquire();
S.acquire();
⋮
⋮
S.release();
Q.release();
Q.release();
S.release();
• Deadlocks are the subject of Chapter 7.
• A related problem is indefinite blocking or starvation, in which a
process waits indefinitely to acquire the semaphore.
Possible Problem: Priority Inversion
• A high-priority process might have to wait for a low-priority process to
release a shared resource.
• Example:
– Three processes, L, M, and H, with priorities L < M < H
– Process H is waiting for resource R, which is currently locked by
process L
– Process M becomes runnable and preempts process L
– Now process M has caused process H to wait longer
• This problem is called priority inversion.
• Possible solutions:
– Have only two priorities
– Priority-inheritance protocol: all processes accessing resources
needed by higher-priority processes inherit the higher priority until
they release the resources
Bounded Buffer Problem
• Recall: BoundedBuffer helps solve the Producer-Consumer
/* producers call this method */
Problem
/* consumers call this method */
public
insert(E item)
{
• We can
usevoid
semaphores
to protect
the shared memory items in
empty.acquire();
public void remove(E
the BoundedBuffer
object item) {
mutex.acquire();
E item;
/**
// add an
item to the buffer
* boundedbuffer[in]
buffer full.acquire();
with
semaphores
= item;
mutex.acquire();
*
in = (in + 1) % BUFFER_SIZE;
* @author Gagne, Galvin, Silberschatz
// Concepts
remove anwith
itemJava,
to the
* Operating
System
8thbuffer
Ed., Fig. 6.9
mutex.release();
item
=
buffer[out];
*/
full.release();
out = (out + 1) % BUFFER_SIZE;
}
import java.util.*;
mutex.release();
full.release();
public class BoundedBuffer<E>
implements Buffer<E>
{
return item;
private
private
private
private
private
static} final int BUFFER_SIZE = 5;
E[] buffer;
int in, out;
Semaphore mutex;
Semaphore empty;
Readers and Writers Problem
• Suppose several concurrent processes share access to a database.
• Some processes only read the database (call them readers), others
read and write (call them writers).
• Readers-writers problem: We must ensure that writers have
exclusive access to the shared database.
• Two variations:
– First readers-writers problem: no reader should be kept waiting
unless a writer has already obtained permission to use the
database (writers might starve)
– Second readers-writers problem: once a writer is ready, it must
be able to write as soon as possible (readers might starve)
• The will examine a Java solution using semaphores.
Readers and Writers Problem
/**
* Database class, with methods to coordinate reader and writer
* access to the database
*
* @author Gagne, Galvin, Silberschatz
* Operating System Concepts with Java, 8th Ed., Fig. 6.18
*/
public class Database implements ReadWriteLock
{
private int readerCount; // number of active readers
Semaphore mutex; // controls access to readerCount
Semaphore db;
// controls access to the database
public Database() {
readerCount = 0;
mutex = new Semaphore(1);
db = new Semaphore(1);
}
public void acquireReadLock(int readerNum) {
mutex.acquire();
/* first reader indicates that the database is being read */
++readerCount;
Dining Philosophers Problem
• Five philosophers sit around a
circular table, with a single
chopstick between each two
philosophers.
• Philosophers alternate
between thinking and eating.
• When a philosopher wants to
eat, he first must pick up the
two neighboring chopsticks,
one after another.
• When a philosopher finishes eating, he
returns the two chopsticks to the table.
• Problem: How can we prevent deadlock and starvation?
Dining Philosophers Problem
• Simple solution: represent
each chopstick by a
semaphore.
• This does not solve the
possibility of deadlock.
/* shared data (chopsticks) */
Semaphore chopStick[] = new Semaphore[5];
for(int i=0; i < 5; i++)
chopStick[i] = new Semaphore(1);
• Possible solutions:
– Allow at most four
philosophers at the table
– Allow a philosopher to pick up
chopsticks only if both are
available (must pick them up in
a critical section)
– Require odd philosophers to
pick up left chopstick first, and
even philosophers to pick up
right chopstick first.
/* the structure of philosopher i */
while (true) {
// get left chopstick
chopStick[i].acquire();
// get right chopstick
chopStick[(i+1)%5].acquire();
eating();
// return left chopstick
chopStick[i].release();
// return right chopstick
chopStick[(i+1)%5].release();
thinking();
}
Problems with Semaphores
If a single process is not well-behaved, then synchronization may fail.
For example:
semaphore.acquire();
critical section
semaphore.release();
Correct.
semaphore.release();
critical section
semaphore.acquire();
violates mutual-exclusion
requirement (error might
not always occur)
semaphore.acquire();
critical section
semaphore.acquire();
produces deadlock
//semaphore.acquire();
critical section
semaphore.release();
violates mutual-exclusion
requirement
Monitors
• A monitor is an abstract type
that controls access to a shared
resource.
• The shared resource is only
available via methods provided
by the monitor.
• The monitor ensures that only
one process at a time can access
the shared resource.
monitor monitor name
{
//shared variable declarations
...
//methods
initialization code (...) {
...
}
procedure P1 (...) {
...
}
procedure P2 (...) {
...
}
...
}
Java Synchronization
• Java allows methods to be declared synchronized.
• Each Java object has an associated lock, which we have ignored until
now, and which may be owned by a single thread.
• A thread acquires the lock for an object by calling a synchronized
method of that object.
– If no other thread owns the lock, then the calling thread gains
ownership of the lock and runs the synchronized method.
– If some thread owns the lock, then the calling thread waits in the
entry set for the lock.
• When a thread exists a synchronized method, it releases the lock.
Synchronized BoundedBuffer
/**
* Implementing a bounded buffer with Java synchronization
*
* @author Gagne, Galvin, Silberschatz
* Operating System Concepts with Java, 8th Ed., Fig. 6.27
*/
public class BoundedBuffer<E>
{
private static final int BUFFER_SIZE = 5;
private int count; // number of items in the buffer
private int in; // points to the next free position
private int out; // points to the next full position
private E[] buffer;
Problem: Deadlock
// constructor
public BoundedBuffer() {
...
}
// Producers call this method
public synchronized void insert(E item) {
while (count == BUFFER_SIZE)
Thread.yield();
If the Producer tries to
insert an item into a full
buffer, it will sleep in the
insert method and the
Consumer will never be
able to gain the lock to
consume an item.
Java: Wait and Notify
• Each Java object also
has a wait set.
• When a thread enters a
synchronized method,
it gains the lock for the object.
• If the thread determines that it has to wait for something, it can call the
wait() method. When this happens:
– The thread releases the lock for the object.
– The state of the thread is set to blocked.
– The thread is placed in the wait set for the object.
• The notify() method does the following:
– Picks a thread T from the wait set
– Moves T from the wait set to the entry set
– Changes the state of T from blocked to runnable
Synchronized BoundedBuffer
/**
* Implementing a bounded buffer with Java synchronization
*
* @author Gagne, Galvin, Silberschatz
* Operating System Concepts with Java, 8th Ed., Fig. 6.27
*/
public class BoundedBuffer<E>
{
private static final int BUFFER_SIZE = 5;
private int count, in, out;
private E[] buffer;
// constructor
public BoundedBuffer() {
...
}
// Producers call this method
public synchronized void insert(E item) {
while (count == BUFFER_SIZE) {
try { wait(); }
catch (InterruptedException e) {}
}
Multiple Notification
• The notify() method selects an arbitrary thread from the wait
set.
• It is possible that the selected thread is not, in fact, waiting on the
condition for which it is notified.
• The notifyAll() method moves all threads from the wait set
to the entry set.
• notifyAll() is a more expensive operation than notify(),
but it is necessary if you must wake one particular thread out of
many that might be sleeping
Java Synchronization Example
Solving the Readers-Writers problem using Java synchronization…
/**
* Database class, with methods to coordinate reader and writer
* access to the database using Java synchronization
*
* @author Gagne, Galvin, Silberschatz
* Operating System Concepts with Java, 8th Ed., Fig. 6.33-6.35
*/
public class Database implements ReadWriteLock
{
private int readerCount; // number of active readers
private boolean dbWriting; // indicates whether databse is in use
public Database() {
readerCount = 0;
dbWriting = false;
}
public synchronized void acquireReadLock() {
while (dbWriting == true) {
try {
wait();
Java Block Synchronization
• The amount of time between when a lock is acquired and when it
released is called the scope of the lock.
• A synchronized method could be too large of a scope.
• Java allows blocks of code to be declared synchronized.
• Example:
Object lock = new Object();
...
public void someMethod() {
nonCriticalSection();
synchronized(lock) {
criticalSection();
}
remainderSection();
}
Java Synchronization Notes
• A thread can nest synchronized method invocations for
different objects in order to own multiple locks simulaneously.
• wait(), notify(), and notifyAll() may only be called
from synchronized methods or blocks.
• wait() throws an InterrupedException if the interruption
status of its thread is set—that is, if someone has called
interrupt() for that thread
Synchronization in Java 5
• ReentrantLock
– like the synchronized statement, but with more features
– Allows granting the lock to the longest-waiting thread
• Counting semaphores
– controls access to a resource that may be simultaneously
accessed by some fixed number of threads
– provides acquire() and release() methods
• Condition variables
– provide similar functionality to wait() and notify()
– offer more flexibility for threads that must wait for specific
conditions
Download