Sei sulla pagina 1di 96

Chapter 6: Process Synchronization

Module 6: Process Synchronization

 Background
 The Critical-Section Problem
 Peterson’s Solution
 Synchronization Hardware
 Semaphores
 Classic Problems of Synchronization
 Monitors
 Synchronization Examples
 Atomic Transactions

Operating System Concepts – 7th Edition, Feb 8, 2005 6.2 Silberschatz, Galvin and Gagne ©2005
Background

 Concurrent access to shared data may result in


data inconsistency (Ex: multithreaded program)
 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.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.3 Silberschatz, Galvin and Gagne ©2005
Producer

while (true) {

/* produce an item and put in nextProduced */


while (count == BUFFER_SIZE)
; // do nothing
buffer [in] = nextProduced;
in = (in + 1) % BUFFER_SIZE;
count++;
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.4 Silberschatz, Galvin and Gagne ©2005
Consumer

while (true) {
while (count == 0)
; // do nothing
nextConsumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;

/* consume the item in nextConsumed */


}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.5 Silberschatz, Galvin and Gagne ©2005
Will it work?

 Individually, both producer and consumer


seem correct. All n buffers can be used
(instead of just n-1 ).
 Note that both producer and consumer
modify variable “count”.
 In reality, it may or may not work properly.
 Potential problem: race condition

Operating System Concepts – 7th Edition, Feb 8, 2005 6.6 Silberschatz, Galvin and Gagne ©2005
Race Condition

 In machine language count++ could be implemented as

register1 = count
register1 = register1 + 1
count = register1
 And count-- could be implemented as

register2 = count
register2 = register2 - 1
count = register2

Operating System Concepts – 7th Edition, Feb 8, 2005 6.7 Silberschatz, Galvin and Gagne ©2005
Race Condition (cont.)
 Consider this execution interleaving with “count = 5” initially:
T0: producer  register1 = count {register1 = 5}
T1: producer  register1 = register1 + 1 {register1 = 6}
T2: consumer  register2 = count {register2 = 5}
T3: consumer  register2 = register2 - 1 {register2 = 4}
T4: producer  count = register1 {count = 6 }
T5: consumer  count = register2 {count = 4}
 The variable “count” could end up with a value of 4, 5, or 6,
depending upon how the execution of the instruction streams is
interleaved (order in which the access to the shared data takes
place). This is called a race condition.
 The producer and consumer processes must be synchronized so
that they do not try to execute “count++” and “count--” at the
same time.
 Things to remember
 Threads interleave executions arbitrarily and at different rates
 Scheduling is not under program control

Operating System Concepts – 7th Edition, Feb 8, 2005 6.8 Silberschatz, Galvin and Gagne ©2005
Another Classic Example

 Suppose we have to implement a function to handle withdrawals


from a bank account:
withdraw (account, amount) {
balance = get_balance(account);
balance = balance – amount;
put_balance(account, balance);
return balance;
}
 Now suppose that you and your brother/sister share a bank
account with a balance of $1000.
 Then you each go to separate ATM machines and simultaneously
withdraw $100 from the account. !!!

Operating System Concepts – 7th Edition, Feb 8, 2005 6.9 Silberschatz, Galvin and Gagne ©2005
Example Continued

 We’ll represent the situation by creating a separate thread for each


person to do the withdrawals
 These threads run on the same bank machine:

 What’s the problem with this implementation?


 Think about potential schedules of these two threads

Operating System Concepts – 7th Edition, Feb 8, 2005 6.10 Silberschatz, Galvin and Gagne ©2005
Interleaved Schedules

 The problem is that the execution of the two threads can be


interleaved:

 What is the balance of the account now?


 Is the bank happy with our implementation?

Operating System Concepts – 7th Edition, Feb 8, 2005 6.11 Silberschatz, Galvin and Gagne ©2005
Shared Resources

 The problem is that two concurrent threads (or processes)


accessed a shared resource (account) without any synchronization
such that the output depends on the order of execution
 Known as a race condition (memorize this buzzword)
 We need mechanisms to control access to these shared resources
in the face of concurrency
 So we can reason about how the program will operate
 Our example was updating a shared bank account
 Also necessary for synchronizing access to any shared data
structure
 Buffers, queues, lists, hash tables, etc.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.12 Silberschatz, Galvin and Gagne ©2005
When Are Resources Shared?

 Local variables are not shared (private)


 Refer to data on the stack
 Each thread has its own stack
 Never pass/share/store a pointer to a local variable on another
thread’s stack
 Global variables and static objects are shared
 Stored in the static data segment, accessible by any thread
 Dynamic objects and other heap objects are shared
 Allocated from heap with malloc/free or new/delete

Operating System Concepts – 7th Edition, Feb 8, 2005 6.13 Silberschatz, Galvin and Gagne ©2005
Mutual Exclusion

 We want to use mutual exclusion to synchronize access to shared


resources
 This allows us to have larger atomic blocks
 Code that uses mutual exclusion to synchronize its execution is
called a critical section
 Only one thread at a time can execute in the critical section
 All other threads are forced to wait on entry
 When a thread leaves a critical section, another can enter
 What requirements would you place on a critical section?

Operating System Concepts – 7th Edition, Feb 8, 2005 6.14 Silberschatz, Galvin and Gagne ©2005
The Critical-Section Problem

 Consider a system where multiple processes are accessing


and possibly modifying some shared data.
 Each process has a code segment, called a critical section,
in which the shared data is accessed or modified.
 Problem: to ensure that when one process is executing in its
critical section, no other process is allowed to execute in its
critical section (for the same shared data) at the same time.
 Goal: design a protocol that the processes can use to
cooperate such that each process must request
permission to enter its critical section  synchronization

Operating System Concepts – 7th Edition, Feb 8, 2005 6.15 Silberschatz, Galvin and Gagne ©2005
Critical-Section Problem (cont.)

 General structure of process Pi  Key Issue (Goal):


do { How to implement
entry section the “entry” section
critical section and “exit” section
exit section to ensure that only
remainder section one process is
} while (TRUE) executing its
critical section at a
time.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.16 Silberschatz, Galvin and Gagne ©2005
Critical Section Requirements

 A solution to the critical-section problem must satisfy three requirements:


1. Mutual Exclusion - If process Pi is executing in its critical section, then no
other processes can be executing in their critical sections
 No two processes executing their critical sections simultaneously.
2. Progress - No process outside of its critical section and not trying to enter
its critical section can block others from entering their critical sections. (As
an example strict alternating turns would not satisfy this condition.)
 In other words, a process operating outside of its critical section
cannot prevent other processes from entering theirs; processes
attempting to enter their critical sections simultaneously must decide
which process enters eventually
 The definition has two sides:
 If some thread T is not in the critical section, then T cannot
prevent some other thread S from entering the critical section
 A thread in the critical section will eventually leave the critical
section

Operating System Concepts – 7th Edition, Feb 8, 2005 6.17 Silberschatz, Galvin and Gagne ©2005
Solution to Critical-Section Problem (cont.)
3. Bounded Waiting (no starvation) - No process should have to wait
forever. No starvation. i.e. a process attempting to enter its critical
section will be able to do so eventually
 Performance: The overhead of entering and exiting the critical section
is small with respect to the work being done within it
 NOTE: Race conditions can happen even in kernel code (a data
structure that maintains the list of open files needs to be synch so that
two processes updating the no of open files need to do so correctly)
 Two general approaches to handle critical sections in OS kernels:
 Nonpreemptive kernels: does not allow preemption. A process will
run until it exits, blocks, or voluntarily releases CPU. This approach
is free from race conditions (Windows XP, 2000)
 Preemptive kernels: allows a process to be preempted while
running in kernel mode. Why being used if it leads to race
conditions?
 More favorable since its more responsive, more suitable for
real-time programming (Solaris)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.18 Silberschatz, Galvin and Gagne ©2005
Mechanisms For Building Critical Sections

 Atomic read/write
 Can it be done?  depends on the HW
 Locks
 Primitive, minimal semantics, used to build others
 Semaphores
 Basic, easy to get the hang of, but hard to program with
 Monitors
 High-level, requires language support, operations implicit
 Messages
 Simple model of communication and synchronization based on
atomic transfer of data across a channel
 Direct application to distributed systems
 Messages for synchronization are straightforward (once we see
how the others work)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.19 Silberschatz, Galvin and Gagne ©2005
Mutual Exclusion with Atomic Read/Writes:
First Try

 This is called alternation


 It satisfies mutex:
 if blue is in the critical section, then turn == 1 and if yellow is in the critical
section then turn == 2 (why?)
 (turn == 1) ≡ (turn != 2) (turn is either 1 or 2 )
 It violates progress: the blue thread could go into an infinite loop outside of
the critical section and before setting the turn value, which will prevent the
yellow one from entering.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.20 Silberschatz, Galvin and Gagne ©2005
Peterson’s Solution (Atomic
( R/W)

 Software-based solution for two processes only..


 Assume that the LOAD and STORE instructions are atomic;
that is, cannot be interrupted (Not so on all architectures)
 The two processes (Pi, Pj) share two variables:
 int turn;
 Boolean flag[2]
 The variable turn indicates whose turn it is to enter the
critical section (if turn =i, Pi is allowed to execute in its
critical section)
 The flag array is used to indicate if a process is ready to
enter the critical section. flag[i] = true implies that process Pi
is ready to enter its critical section

Operating System Concepts – 7th Edition, Feb 8, 2005 6.21 Silberschatz, Galvin and Gagne ©2005
Processes’ Structure
Process i Process j

while (true) { while (true) {


flag[i] = TRUE; flag[j] = TRUE;
turn = j; /* if another process turn = i; /* if another process
wishes to enter its critical section, it can do wishes to enter its critical section, it can do
so . If both processes try to enter at the same so If both processes try to enter at the same
time, turn will be set to both I and j at roughly time, turn will be set to both I and j at roughly
the same time. Only one of these operations the same time. Only one of these operations
will last. The other will occur but will be will last. The other will occur but will be
overwritten. */ overwritten. */
while ( flag[j] && turn == j) while ( flag[i] && turn == i)
no operation; no operation;
CRITICAL SECTION CRITICAL SECTION
flag[i] = FALSE; flag[j] = FALSE;
REMAINDER REMAINDER
SECTION SECTION
} }

To show the correctness of the solution, we need to show it satisfies :


Mutual exclusion, progress, and bounded waiting.
Operating System Concepts – 7th Edition, Feb 8, 2005 6.22 Silberschatz, Galvin and Gagne ©2005
Peterson’s Solution (cont.)

 The solution is correct.


 Mutual exclusion is preserved.
 turn can be 0 or 1, but not both.
 The progress requirement is satisfied.
 Each process defers to the other via turn.
 The bounded-waiting requirement is met.
 Process Pi will enter its critical section after at most one
entry by process Pj.
 Does not work for more than two processes.
 Has a “busy waiting” loop that consumes CPU time, but does no
useful work.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.23 Silberschatz, Galvin and Gagne ©2005
Locks

 A lock is an object in memory providing two operations


 acquire(): before entering the critical section
 release(): after leaving a critical section
 Threads calls acquire() and release() in pairs
 Between acquire()/release(), the thread holds the lock
 acquire() does not return until any previous holder releases
 What can happen if the calls are not paired?
 Locks can spin (a spinlock) or block (a mutex)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.24 Silberschatz, Galvin and Gagne ©2005
Using Locks

 What happens when blue tries to acquire the lock?


 Why is the “return” outside the critical section? Is it reading or writing?
 What happens when a third thread calls acquire?

Operating System Concepts – 7th Edition, Feb 8, 2005 6.25 Silberschatz, Galvin and Gagne ©2005
Implementing Locks (1)

 How do we implement locks? Here is one attempt:

 This is called a spinlock because a thread spins waiting for the lock
to be released
 Does this work?

Operating System Concepts – 7th Edition, Feb 8, 2005 6.26 Silberschatz, Galvin and Gagne ©2005
Implementing Locks (2)

 No. Two independent threads may both notice that a lock has been
released and thereby acquire it.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.27 Silberschatz, Galvin and Gagne ©2005
Implementing Locks (3)

 The problem is that the implementation of locks has critical


sections, too
 How do we stop the recursion?
 The implementation of acquire/release must be atomic
 An atomic operation is one which executes as though it could
not be interrupted
 Code that executes “all or nothing”
 How do we make them atomic?
 Need help from hardware
 Atomic instructions (e.g., test-and-set, swap)
 Disable/enable interrupts (prevents context switches)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.28 Silberschatz, Galvin and Gagne ©2005
Synchronization Hardware
 Many OS functions are made simpler and more efficient by support in
hardware.
 Many systems provide hardware support for critical section code
(hardware instructions)
 Uniprocessors – could disable interrupts while a shared variable is
being modified to solve the critical section problem
 Used in nonpreemptive kernels
 Currently running code would execute without preemption
 Generally too inefficient on multiprocessor systems as disabling
interrupts can be time consuming. (Why?)
 Operating systems using this not broadly scalable
 Modern machines provide special atomic hardware instructions
 Atomic = non-interruptable
 Either test memory word and modify its value
 Or swap contents of two memory words

Operating System Concepts – 7th Edition, Feb 8, 2005 6.29 Silberschatz, Galvin and Gagne ©2005
TestAndndSet Instruction

 The TestAndSet() instruction tests the value of a word in


memory and sets a new value, as an indivisible (atomic)
unit. It can be defined as:
boolean TestAndSet (boolean *target)
{
boolean rv = *target;
*target = TRUE;
return rv:
}
 If *target == TRUE, it remains unchanged and the returned
value is TRUE.
 If *target == FALSE, it is changed to TRUE and the
returned value is FALSE.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.30 Silberschatz, Galvin and Gagne ©2005
Solution using TestAndSet
 Mutual exclusion for critical
sections can be achieved via
Process Pi structure:
TestAndSet() by using a boolean while (true) {
lock variable, initialized to
FALSE. acquire();
// critical section
release();
// remainder section
}

 Does it satisfy bounded waiting?


 NO ! (See textbook pp 199
for bounded waiting
explanation.)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.31 Silberschatz, Galvin and Gagne ©2005
Swap Instruction

 The Swap() instruction swaps the contents of two words in


memory, as an indivisible (atomic) unit. It can be defined as:
void Swap (boolean *a, boolean *b)
{
boolean temp = *a;
*a = *b;
*b = temp:
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.32 Silberschatz, Galvin and Gagne ©2005
Solution using Swap
 Mutual exclusion for critical sections can be achieved via Swap() by
using a global boolean lock variable, initialized to FALSE, and local
boolean key variables for each process:
 Process Pi structure:
while (true) {
key = TRUE;
while ( key == TRUE)
Swap (&lock, &key );

// critical section

lock = FALSE;

// remainder section

Operating System Concepts – 7th Edition, Feb 8, 2005 6.33 Silberschatz, Galvin and Gagne ©2005
Problems with Spinlocks

 The problem with spinlocks is that they are wasteful


 If a thread is spinning on a lock, then the thread holding the lock
cannot make progress due to high CPU contention
 How did the lock holder give up the CPU in the first place?
 Lock holder calls yield or sleep (nonpreemptive)
 Involuntary context switch (preemptive)
 Only want to use spinlocks as primitives to build higher-level
synchronization constructs

Operating System Concepts – 7th Edition, Feb 8, 2005 6.34 Silberschatz, Galvin and Gagne ©2005
Disabling Interrupts

 Another implementation of acquire/release is to disable interrupts:

 Note that there is no state associated with the lock


 Can two threads disable interrupts simultaneously?
 NO, since disabling take effect by the first one that do so

Operating System Concepts – 7th Edition, Feb 8, 2005 6.35 Silberschatz, Galvin and Gagne ©2005
On Disabling Interrupts

 Disabling interrupts blocks notification of external events that could


trigger a context switch (e.g., timer)
 This is what Nachos uses as its primitive
 In a “real” system, this is only available to the kernel
 Why?
 What could user-level programs use instead?
 Disabling interrupts is insufficient on a multiprocessor
 Back to atomic instructions
 Like spinlocks, only want to disable interrupts to implement higher-
level synchronization primitives
 Don’t want interrupts disabled between the whole duration of
acquire and release

Operating System Concepts – 7th Edition, Feb 8, 2005 6.36 Silberschatz, Galvin and Gagne ©2005
Summarize Where We Are

 Goal: Use mutual exclusion to protect critical sections of code that access
shared resources
 Method: Use locks (spinlocks or disable interrupts)
 Problem: Critical sections can be long

Spinlocks Disabling Interrupts


 Threads waiting to  Should not disable
acquire lock spin in interrupts for long
test-and-set loop periods of time
 Wastes CPU cycles  Can miss or delay
 Longer the CS, the important events
longer the spin (e.g., timer, I/O)
 Greater the chance for
lock holder to be
interrupted, and thus
delayed

Operating System Concepts – 7th Edition, Feb 8, 2005 6.37 Silberschatz, Galvin and Gagne ©2005
Higher-Level Synchronization

 Spinlocks and disabling interrupts are useful only for very short and simple
critical sections
 Wasteful otherwise
 These primitives are “primitive” – don’t do anything besides mutual
exclusion
 Need higher-level synchronization primitives that:
 Block waiters
 Leave interrupts enabled within the critical section
 All synchronization requires atomicity
 Look at two common high-level mechanisms
 Semaphores: binary (mutex) and counting
 Monitors: mutexes and condition variables
 Use them to solve common synchronization problems
 So we’ll use our “atomic” locks as primitives to implement them

Operating System Concepts – 7th Edition, Feb 8, 2005 6.38 Silberschatz, Galvin and Gagne ©2005
Implementing Locks (4)

 Block waiters, interrupts enabled in critical sections

Operating System Concepts – 7th Edition, Feb 8, 2005 6.39 Silberschatz, Galvin and Gagne ©2005
Semaphore
 Semaphores are an abstract data type that provides mutual
exclusion ((synchronization) to critical sections
 Block waiters, interrupts enabled within CS
 Described by Dijkstra in THE system in 1968
 Semaphores can also be used as atomic counters
 More later
 Semaphores are integers that support two operations:
 wait(semaphore): decrement, block until semaphore is open
 Also P(), after the Dutch word for test, or down()
 signal(semaphore): increment, allow another thread to enter
 Also V() after the Dutch word for increment, or up()
 That's it! No other operations - not even just reading its value -
exist.
 Semaphore safety property: a semaphore's value is always greater
than or equal to 0.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.40 Silberschatz, Galvin and Gagne ©2005
Semaphores (cont.)
 Thus, a semaphore S is an integer variable that can only be accessed
through two standard atomic operations, wait() and signal():
 wait (S) {
while S <= 0
; // no-op
S--;
}
 signal (S) {
S++;
}

 The modifications to the value of the semaphore (S++ and S--) must be
executed indivisibly. In addition, in wait(), the testing of the integer value of
S (S<=0) and its possible modification (S- -) must be executed without
interruption. (Can be accomplished with TestAndSet() or Swap()
instructions).
 The wait() and signal() operations were originally called P() and V(), from
the Dutch words proberen, meaning “to test”, and verhogen, meaning “to
increment”.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.41 Silberschatz, Galvin and Gagne ©2005
Blocking in Semaphores

 Associated with each semaphore is a queue of waiting processes


 When wait() is called by a thread:
 If semaphore is open, thread continues
 If semaphore is closed, thread blocks on queue
 Then signal() opens the semaphore:
 If a thread is waiting on the queue, the thread is unblocked
 If no threads are waiting on the queue, the signal is
remembered for the next thread
 In other words, signal() has “history”
 This “history” is a counter

Operating System Concepts – 7th Edition, Feb 8, 2005 6.42 Silberschatz, Galvin and Gagne ©2005
Semaphore Usage
 Two types of semaphores:
 Binary semaphore – integer value can range only between 0
and 1; can be simpler to implement
 Also known as mutex locks (semaphore)
 Represents single access to a resource
 Guarantees mutual exclusion to a critical section
 Counting (general) semaphore – integer value can range over an unrestricted
domain
 Represents a resource with many units available, or a resource that allows
certain kinds of unsynchronized concurrent access (e.g., reading)
 Multiple threads can pass the semaphore
 Number of threads determined by the semaphore “count”
– mutex has count = 1, counting has count = N
 Counting semaphores can be implemented using binary semaphores.
 A binary semaphore can be used for critical section mutual exclusion between n
processes
 Semaphore S; // initialized to 1
 wait (S);
Critical Section
signal (S);
remainder section

Operating System Concepts – 7th Edition, Feb 8, 2005 6.43 Silberschatz, Galvin and Gagne ©2005
Semaphore Usage (cont.)

 A binary semaphore can be used to ensure a particular order of


execution of code within two processes (synchronization):
 Semaphore synch, initialized to 0.
 Process P1 Process P2
S1; wait(synch);
signal(synch); S2;
 Process P2 will execute S2 only after the wait(synch) is satisfied,
which is only after P1 has invoked signal(synch), which is after
S1.
 A counting semaphore can be used to control access to a given
resource with n instances, e.g. buffer slots in the
producer/consumer problem. The semaphore is initialized to the
number of resources available, and each process issue a wait to
use a resourse.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.44 Silberschatz, Galvin and Gagne ©2005
Semaphore Implementation

 Main disadvantage of semaphores is the busy waiting. If a


process is in its critical section, others will loop in the wait()
 Could now have busy waiting in critical section
implementation
 This type of semaphores is called spinlock because a
process spins while waiting for the lock. However, no
context switch happen during this time.
 In general, for user-level processes, we do not want to waste
CPU cycles with busy waiting. The wait() and signal()
operations can be modified so that a process is blocked until
the lock becomes available, instead of spinning.
 The process is put in a waiting queue associated with
semaphore, then control is transferred to CPU scheduler to
select another process to execute.
 A process that is blocked, waiting for semaphore S, should
be restarted when some other process executes a signal ()
operation.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.45 Silberschatz, Galvin and Gagne ©2005
Semaphore Implementation (cont.)

 Define a semaphore as a structure:


typedef struct {
int value;
struct process *list;
} semaphore;
 The semaphore now has an integer value, and queue of
processes (PCBs) waiting on the semaphore.
 Also define two new operations (system calls):
 block() – place the process invoking the operation on the
waiting queue.
 wakeup(P) – remove process P from the waiting queue and
place it in the ready queue.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.46 Silberschatz, Galvin and Gagne ©2005
Semaphore Implementation (no Busy waiting)
 Implementation of wait:
wait(semaphore S) {
S.value--;
if (S.value < 0) {
add this process to S.list;
block();
}
}
 Implementation of signal:
signal(semaphore S) {
S.value++;
if (S.value <= 0) {
remove a process P from S.list;
wakeup(P);
}
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.47 Silberschatz, Galvin and Gagne ©2005
Using Semaphores

 Use is similar to our locks, but semantics are different

Operating System Concepts – 7th Edition, Feb 8, 2005 6.48 Silberschatz, Galvin and Gagne ©2005
Semaphore Implementation (cont.)

 The semaphore value can now be negative. If so, its absolute value
is the number of processes waiting on the semaphore.
 Bounded waiting can be ensured if the waiting queue is FIFO.
 Must guarantee that no two processes can execute wait () and
signal () on the same semaphore at the same time
 The wait() and signal() operations must be atomic. (A critical
section problem within the OS—disable interrupts, spin locks.)
 Busy waiting not completely eliminated.
 Moved from application programs to the kernel.
 Spin locks in the kernel can serialize the critical sections of the
wait() and signal() operations, but those sections are short.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.49 Silberschatz, Galvin and Gagne ©2005
Deadlock and Starvation

 Deadlock – two or more processes are waiting indefinitely for an


event that can be caused by only one of the waiting processes
 In out situation the event is executing signal()
 Let S and Q be two semaphores initialized to 1
P0 P1
wait (S); wait (Q);
wait (Q); wait (S);
. .
. .
. .
signal (S); signal (Q);
signal (Q); signal (S);
 A set of processes is in a deadlock situation when every process in the set
is waiting for an event that can be caused only by another process in the
set.
 Starvation – indefinite blocking. A process may never be removed
from the semaphore queue in which it is suspended (if using LIFO)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.50 Silberschatz, Galvin and Gagne ©2005
Classical Problems of Synchronization

 Bounded-Buffer Problem
 Readers and Writers Problem
 Dining-Philosophers Problem

Operating System Concepts – 7th Edition, Feb 8, 2005 6.51 Silberschatz, Galvin and Gagne ©2005
Bounded-Buffer Problem

 N buffers, each can hold one item


 Semaphore mutex initialized to the value 1
 Semaphore full initialized to the value 0
 Semaphore empty initialized to the value N.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.52 Silberschatz, Galvin and Gagne ©2005
Bounded Buffer Problem (Cont.)

 The structure of the producer process

while (true) {

// produce an item

wait (empty);
wait (mutex);

// add the item to the buffer

signal (mutex);
signal (full);
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.53 Silberschatz, Galvin and Gagne ©2005
Bounded Buffer Problem (Cont.)

 The structure of the consumer process

while (true) {
wait (full);
wait (mutex);

// remove an item from buffer

signal (mutex);
signal (empty);

// consume the removed item

Operating System Concepts – 7th Edition, Feb 8, 2005 6.54 Silberschatz, Galvin and Gagne ©2005
Readers-Writers Problem

 A resource (e.g. a database) is shared among a number of


concurrent processes
 Readers – only read the database; they do not perform any
updates
 Writers – can both read and write.

 Problem – control access so that a writer gets exclusive access,


but multiple readers can access concurrently.
 Two versions:
 First reader-write: No reader will be kept waiting unless a
writer already has exclusive access to the database. (Writers
might starve.)
 Second reader-writer: If a writer is waiting, no new readers
may start reading. (Readers might starve.)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.55 Silberschatz, Galvin and Gagne ©2005
Readers-Writers Problem (Cont.)

 Shared data for solution to version 1:


 The database.
 Integer readcount initialized to 0.
 Number of readers currently reading.
 Semaphore mutex initialized to 1.
 For mutual exclusion when updating readcount.
 Semaphore wrt initialized to 1.
 For mutual exclusion of writers.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.56 Silberschatz, Galvin and Gagne ©2005
Readers-Writers Problem (Cont.)

 The structure of a writer process

while (true) {
wait (wrt) ;

// writing is performed

signal (wrt) ;
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.57 Silberschatz, Galvin and Gagne ©2005
Readers-Writers Problem (Cont.)
 The structure of a reader process

while (true) {
wait (mutex) ;
readcount ++ ;
if (readcount == 1) wait (wrt) ;
signal (mutex)

// reading is performed

wait (mutex) ;
readcount - - ;
if (readcount == 0) signal (wrt) ;
signal (mutex) ;
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.58 Silberschatz, Galvin and Gagne ©2005
Dining-Philosophers Problem

 Philosophers alternate between


periods of thinking and eating.
 Five single chopsticks.
 When hungry, a philosopher must
pick up two chopsticks, on left and
right, in order to eat.
 When finished eating, chopsticks are
put down.
 Represent the shared chopsticks by
semaphores:
 semaphore chopstick[5];
 Each semaphore initialized to 1.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.59 Silberschatz, Galvin and Gagne ©2005
Dining-Philosophers Problem (Cont.)

 The structure of Philosopher i for an attempted solution:

While (true) {
wait ( chopstick[i] );
wait ( chopStick[ (i + 1) % 5] );

// eat

signal ( chopstick[i] );
signal (chopstick[ (i + 1) % 5] );

// think

Operating System Concepts – 7th Edition, Feb 8, 2005 6.60 Silberschatz, Galvin and Gagne ©2005
Dining-Philosophers Problem (Cont.)

 Attempted solution guarantees that no two neighbors are eating


simultaneously, but can suffer from deadlock and starvation.
(How?)
 Deadlock-free solutions:
 Allow at most four to sit at the table at a time.
 Allow philosopher to pick up chopsticks only when both are
available. (Check availability and pick up in a critical section.)
 Use an asymmetric solution: left-first / right-first.
 Starvation?

Operating System Concepts – 7th Edition, Feb 8, 2005 6.61 Silberschatz, Galvin and Gagne ©2005
Problems with Semaphores

 Incorrect usage of semaphores can cause problems that may


be difficult to detect because of timing.
 Some incorrect usages of semaphore operations:
 signal(mutex); (multiple processes in critical section)
 ...critical section...
 wait(mutex);

 wait(mutex); (deadlock)
 ...critical section...
 wait(mutex);

 Omitting of wait(mutex) or signal(mutex), or both. Either


mutual exclusion is violated, or deadlock could occur.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.62 Silberschatz, Galvin and Gagne ©2005
Monitors

 A high-level abstraction that provides a convenient and


effective mechanism for process synchronization (provides for
safe sharing of an abstract data type among concurrent
processes).
 The monitor type can be characterized as a set of programmer-
defined operations that execute with mutual exclusion within
the monitor.
 The monitor type contains declarations of variables that define
the state of an instance of that type, and the bodies of
procedures or functions that operate on those variables.
 Only one process may be active within the monitor at a time
 The programmer doesn’t need to code this synchronization
constraint explicitly.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.63 Silberschatz, Galvin and Gagne ©2005
Structure of a Monitor

monitor monitor-name
{
// shared variable declarations
procedure P1 (…) { …. }
.
.
.
procedure Pn (…) {……}

Initialization code ( ….) { … }



}
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.64 Silberschatz, Galvin and Gagne ©2005
Schematic view of a Monitor

Operating System Concepts – 7th Edition, Feb 8, 2005 6.65 Silberschatz, Galvin and Gagne ©2005
Condition Variables

 To allow a process to wait within the monitor, a condition


variable must be declared, as:
condition x, y;

 Only two operations on a condition variable x:


 x.wait () – a process that invokes the operation is
suspended until another process invokes
x.signal () .
 x.signal () – resumes exactly one suspended process. If no
process is suspended, then the signal operation
has no effect, that is the state of x is the same as
if the operation had never been executed.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.66 Silberschatz, Galvin and Gagne ©2005
Monitor with Condition Variables

Operating System Concepts – 7th Edition, Feb 8, 2005 6.67 Silberschatz, Galvin and Gagne ©2005
Solution to Dining Philosophers

monitor DP
{
enum { THINKING; HUNGRY, EATING) state [5] ;
condition self [5];

void pickup (int i) {


state[i] = HUNGRY;
test(i);
if (state[i] != EATING) self [i].wait;
}

void putdown (int i) {


state[i] = THINKING;
// test left and right neighbors
test((i + 4) % 5);
test((i + 1) % 5);
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.68 Silberschatz, Galvin and Gagne ©2005
Solution to Dining Philosophers (cont)

void test (int i) {


if ( (state[(i + 4) % 5] != EATING) &&
(state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING) ) {
state[i] = EATING ;
self[i].signal () ;
}
}

initialization_code() {
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.69 Silberschatz, Galvin and Gagne ©2005
Solution to Dining Philosophers (cont)

 Each philosopher I invokes the operations pickup()


and putdown() in the following sequence:

dp.pickup (i)

EAT

dp.putdown (i)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.70 Silberschatz, Galvin and Gagne ©2005
Monitor Implementation Using Semaphores

 Variables
semaphore mutex; // (initially = 1)
semaphore next; // (initially = 0)
int next-count = 0;

 Each procedure F will be replaced by

wait(mutex);

body of F;


if (next-count > 0)
signal(next)
else
signal(mutex);

 Mutual exclusion within a monitor is ensured.

Operating System Concepts – 7th Edition, Feb 8, 2005 6.71 Silberschatz, Galvin and Gagne ©2005
Monitor Implementation
 For each condition variable x, we have:

semaphore x-sem; // (initially = 0)


int x-count = 0;

 The operation x.wait can be implemented as:

x-count++;
if (next-count > 0)
signal(next);
else
signal(mutex);
wait(x-sem);
x-count--;

Operating System Concepts – 7th Edition, Feb 8, 2005 6.72 Silberschatz, Galvin and Gagne ©2005
Monitor Implementation

 The operation x.signal can be implemented as:

if (x-count > 0) {
next-count++;
signal(x-sem);
wait(next);
next-count--;
}

Operating System Concepts – 7th Edition, Feb 8, 2005 6.73 Silberschatz, Galvin and Gagne ©2005
Synchronization Examples

 Solaris
 Windows XP
 Linux
 Pthreads

Operating System Concepts – 7th Edition, Feb 8, 2005 6.74 Silberschatz, Galvin and Gagne ©2005
Solaris Synchronization

 Implements a variety of locks to support multitasking,


multithreading (including real-time threads), and multiprocessing
 Uses adaptive mutexes for efficiency when protecting data from
short code segments
 Uses condition variables and readers-writers locks when longer
sections of code need access to data
 Uses turnstiles to order the list of threads waiting to acquire either
an adaptive mutex or reader-writer lock

Operating System Concepts – 7th Edition, Feb 8, 2005 6.75 Silberschatz, Galvin and Gagne ©2005
Windows XP Synchronization

 Uses interrupt masks to protect access to global resources on


uniprocessor systems
 Uses spinlocks on multiprocessor systems
 Also provides dispatcher objects which may act as either mutexes
and semaphores
 Dispatcher objects may also provide events
 An event acts much like a condition variable

Operating System Concepts – 7th Edition, Feb 8, 2005 6.76 Silberschatz, Galvin and Gagne ©2005
Linux Synchronization

 Linux:
 disables interrupts to implement short critical sections

 Linux provides:
 semaphores
 spin locks

Operating System Concepts – 7th Edition, Feb 8, 2005 6.77 Silberschatz, Galvin and Gagne ©2005
Pthreads Synchronization

 Pthreads API is OS-independent


 It provides:
 mutex locks
 condition variables

 Non-portable extensions include:


 read-write locks
 spin locks

Operating System Concepts – 7th Edition, Feb 8, 2005 6.78 Silberschatz, Galvin and Gagne ©2005
Atomic Transactions

 System Model
 Log-based Recovery
 Checkpoints
 Concurrent Atomic Transactions

Operating System Concepts – 7th Edition, Feb 8, 2005 6.79 Silberschatz, Galvin and Gagne ©2005
System Model

 Assures that operations happen as a single logical unit of work, in


its entirety, or not at all
 Related to field of database systems
 Challenge is assuring atomicity despite computer system failures
 Transaction - collection of instructions or operations that performs
single logical function
 Here we are concerned with changes to stable storage – disk
 Transaction is series of read and write operations
 Terminated by commit (transaction successful) or abort
(transaction failed) operation
 Aborted transaction must be rolled back to undo any changes it
performed

Operating System Concepts – 7th Edition, Feb 8, 2005 6.80 Silberschatz, Galvin and Gagne ©2005
Types of Storage Media

 Volatile storage – information stored here does not survive system


crashes
 Example: main memory, cache
 Nonvolatile storage – Information usually survives crashes
 Example: disk and tape
 Stable storage – Information never lost
 Not actually possible, so approximated via replication or RAID to
devices with independent failure modes

Goal is to assure transaction atomicity where failures cause loss of


information on volatile storage

Operating System Concepts – 7th Edition, Feb 8, 2005 6.81 Silberschatz, Galvin and Gagne ©2005
Log-Based Recovery

 Record to stable storage information about all modifications by a


transaction
 Most common is write-ahead logging
 Log on stable storage, each log record describes single
transaction write operation, including
 Transaction name
 Data item name
 Old value
 New value
 <Ti starts> written to log when transaction Ti starts
 <Ti commits> written when Ti commits
 Log entry must reach stable storage before operation on
data occurs

Operating System Concepts – 7th Edition, Feb 8, 2005 6.82 Silberschatz, Galvin and Gagne ©2005
Log-Based Recovery Algorithm

 Using the log, system can handle any volatile memory errors
 Undo(Ti) restores value of all data updated by Ti
 Redo(Ti) sets values of all data in transaction Ti to new values
 Undo(Ti) and redo(Ti) must be idempotent
 Multiple executions must have the same result as one
execution
 If system fails, restore state of all updated data via log
 If log contains <Ti starts> without <Ti commits>, undo(Ti)
 If log contains <Ti starts> and <Ti commits>, redo(Ti)

Operating System Concepts – 7th Edition, Feb 8, 2005 6.83 Silberschatz, Galvin and Gagne ©2005
Checkpoints

 Log could become long, and recovery could take long


 Checkpoints shorten log and recovery time.
 Checkpoint scheme:
1. Output all log records currently in volatile storage to stable
storage
2. Output all modified data from volatile to stable storage
3. Output a log record <checkpoint> to the log on stable storage
 Now recovery only includes Ti, such that Ti started executing
before the most recent checkpoint, and all transactions after Ti All
other transactions already on stable storage

Operating System Concepts – 7th Edition, Feb 8, 2005 6.84 Silberschatz, Galvin and Gagne ©2005
Concurrent Transactions

 Must be equivalent to serial execution – serializability


 Could perform all transactions in critical section
 Inefficient, too restrictive
 Concurrency-control algorithms provide serializability

Operating System Concepts – 7th Edition, Feb 8, 2005 6.85 Silberschatz, Galvin and Gagne ©2005
Serializability

 Consider two data items A and B


 Consider Transactions T0 and T1
 Execute T0, T1 atomically
 Execution sequence called schedule
 Atomically executed transaction order called serial schedule
 For N transactions, there are N! valid serial schedules

Operating System Concepts – 7th Edition, Feb 8, 2005 6.86 Silberschatz, Galvin and Gagne ©2005
Schedule 1: T0 then T1

Operating System Concepts – 7th Edition, Feb 8, 2005 6.87 Silberschatz, Galvin and Gagne ©2005
Nonserial Schedule

 Nonserial schedule allows overlapped execute


 Resulting execution not necessarily incorrect
 Consider schedule S, operations Oi, Oj
 Conflict if access same data item, with at least one write
 If Oi, Oj consecutive and operations of different transactions & Oi
and Oj don’t conflict
 Then S’ with swapped order Oj Oi equivalent to S
 If S can become S’ via swapping nonconflicting operations
 S is conflict serializable

Operating System Concepts – 7th Edition, Feb 8, 2005 6.88 Silberschatz, Galvin and Gagne ©2005
Schedule 2: Concurrent Serializable Schedule

Operating System Concepts – 7th Edition, Feb 8, 2005 6.89 Silberschatz, Galvin and Gagne ©2005
Locking Protocol

 Ensure serializability by associating lock with each data item


 Follow locking protocol for access control
 Locks
 Shared – Ti has shared-mode lock (S) on item Q, Ti can read Q
but not write Q
 Exclusive – Ti has exclusive-mode lock (X) on Q, Ti can read
and write Q
 Require every transaction on item Q acquire appropriate lock
 If lock already held, new request may have to wait
 Similar to readers-writers algorithm

Operating System Concepts – 7th Edition, Feb 8, 2005 6.90 Silberschatz, Galvin and Gagne ©2005
Two-phase Locking Protocol

 Generally ensures conflict serializability


 Each transaction issues lock and unlock requests in two phases
 Growing – obtaining locks
 Shrinking – releasing locks
 Does not prevent deadlock

Operating System Concepts – 7th Edition, Feb 8, 2005 6.91 Silberschatz, Galvin and Gagne ©2005
Timestamp-based Protocols

 Select order among transactions in advance – timestamp-ordering


 Transaction Ti associated with timestamp TS(Ti) before Ti starts
 TS(Ti) < TS(Tj) if Ti entered system before Tj
 TS can be generated from system clock or as logical counter
incremented at each entry of transaction
 Timestamps determine serializability order
 If TS(Ti) < TS(Tj), system must ensure produced schedule
equivalent to serial schedule where Ti appears before Tj

Operating System Concepts – 7th Edition, Feb 8, 2005 6.92 Silberschatz, Galvin and Gagne ©2005
Timestamp-based Protocol Implementation

 Data item Q gets two timestamps


 W-timestamp(Q) – largest timestamp of any transaction that
executed write(Q) successfully
 R-timestamp(Q) – largest timestamp of successful read(Q)
 Updated whenever read(Q) or write(Q) executed
 Timestamp-ordering protocol assures any conflicting read and write
executed in timestamp order
 Suppose Ti executes read(Q)
 If TS(Ti) < W-timestamp(Q), Ti needs to read value of Q that
was already overwritten
 read operation rejected and Ti rolled back
 If TS(Ti) ≥ W-timestamp(Q)
 read executed, R-timestamp(Q) set to max(R-
timestamp(Q), TS(Ti))

Operating System Concepts – 7th Edition, Feb 8, 2005 6.93 Silberschatz, Galvin and Gagne ©2005
Timestamp-ordering Protocol

 Suppose Ti executes write(Q)


 If TS(Ti) < R-timestamp(Q), value Q produced by Ti was
needed previously and Ti assumed it would never be produced
 Write operation rejected, Ti rolled back
 If TS(Ti) < W-tiimestamp(Q), Ti attempting to write obsolete
value of Q
 Write operation rejected and Ti rolled back
 Otherwise, write executed
 Any rolled back transaction Ti is assigned new timestamp and
restarted
 Algorithm ensures conflict serializability and freedom from deadlock

Operating System Concepts – 7th Edition, Feb 8, 2005 6.94 Silberschatz, Galvin and Gagne ©2005
Schedule Possible Under Timestamp Protocol

Operating System Concepts – 7th Edition, Feb 8, 2005 6.95 Silberschatz, Galvin and Gagne ©2005
End of Chapter 6

Potrebbero piacerti anche