Title: Chapter 6.3: Process Synchronization Part 3
1Chapter 6.3 Process SynchronizationPart 3
2Module 6 Process Synchronization
- Lecture 6.1
- Background
- The Critical-Section Problem
- Petersons Solution
- Synchronization Hardware
- Lecture 6.2
- Semaphores
- Lecture 6.3
- Classic Problems of Synchronization
- Monitors
- Synchronization Examples
- Atomic Transactions
3Classical Problems of Synchronization
- We will discuss all three of these very important
topics. - Bounded-Buffer Problem
- Readers and Writers Problem
- Dining-Philosophers Problem
4Bounded-Buffer Problem
- Idea here is to control access to shared
resources via a buffer pool. - Each element in the buffer contains a single
item. - And, the buffer is of fixed size.
- We need to coordinate access to this shared
resource and, if it is not busy, then we need
exclusive control when we access it. - We need a mutex semaphore (initialized to 1 for
mutual exclusion) and two additional semaphores
empty and full, where empty is initially set to
the maximum items available in pool, n, and full
is set to 0. - These semaphores are best used in a
producer-consumer relationship where access to a
number of resources and controlled in a buffer. - Code is straightforward.
- The producer moves an item into a buffer
- The consumer removes an item from the buffer.
- We will show the concept here.
5Bounded Buffer Problem (Cont.) - Producer
- The structure of the producer process
- do
- // produce an item to be
placed into a buffer - wait (empty) // can see
process may have to wait if empty is true. - wait (mutex) // After wait(),
however, we must ensure mutual exclusion // to
access the buffer. - // code to add item to the
buffer - signal (mutex) // releases the
lock - signal (full) // what do you
think this will do? - while (true)
6Bounded Buffer Problem (Cont.) - Consumer
- The structure of the consumer process
- do
- wait (full) // may have to wait
if full is zero (no room in the buffer) - wait (mutex) // provides for
mutual exclusion gain exclusive access. - // remove an item from
buffer - signal (mutex) // releases its
hold on mutual exclusivity - signal (empty) // what do you
think this does? (reduces number of items - // in buffer by one?
- // Other processes may now
access shared buffer. - // consume the removed item
- while (true)
7Readers-Writers Problem
- This is a very common problem and generally
refers to sharing issues centering on a data set
to be shared among concurrent processes - Readers only read the data set they do not
perform any updates - Writers can both read an write.
- Clearly, we dont want a process to be reading
data while another process is in the middle of
updating data!! - Two problems
- 1. The first readers-writers problem No
reader is to be kept waiting unless a writer has
obtained permission to use the object that is
shared. - Equivalently, no reader should wait for other
readers to finish simply because a writer is
waiting. - 2. The second readers-writers problem says
that once a writer is ready to write, this write
performs as soon as possible that is, if a
writer is waiting to access an object, then no
new readers should be given permission to read.
8Readers-Writers Problem more 1
- Problem is that solutions to the Readers-Writers
issue may result in starvation. - We will present a solution to the first
readers-writers problem - Supporting data structure
- semaphore mutex, wrt // two
semaphores - int readcount // simple
integer variable - (mutex and wrt are set to 1 readcount
set to 0) - For the readers
- readcount is a count of the number of
processes currently reading the object. Mutex
is used to control access to the readcount
integer. - For the writers
- wrt serves as a mutual-exclusion semaphore for
the writers. - Do we need this for Readers???
- wrt is also used by the first or last reader
that enters / exits the critical section. Recall
we are not concerned (nor do other readers
matter) by readers who enter /exit while at least
one other reader is in its/their critical
section. - Lets look at the code.
9Readers-Writers Problem A Writer Process
- The structure of a writer process
-
- do
- wait (wrt) // writer may
have to wait on wrt. - // wrt serves as mutual exclusion semaphore
for writers. - // So when a process gets wrt, no other
process may - // be reading.
-
- // writing is performed
(critical section) - signal (wrt) // releases
the semaphore - while (true)
-
10Readers-Writers Problem A Reader Process
-
- do
- wait (mutex) // needed to
control access to readcount. - // Clearly, we wont want
gt one process having access to readcount at the
// same time. - readcount // important if a
writer is in critical section and n readers are - // waiting, one reader is queued on wrt
other readers on mutex. - if (readercount 1) wait
(wrt) - // ? heres the queue on first reader on
wrt semaphore. - recall readcount // was set
to 0. Since this is a first reader, it must
ensure that no other // process is
writing to the shared resource. Hence, it may
have to wait // on the wrt semaphore. - // Of course, once the
Reader gets through the wrt, it may continue - signal (mutex) // Now, the Reader
releases lock on mutex, which releases its hold
on // readcount. - // In first readers-writers
problem, when code gets to this point - // whether or not it has waited on wrt,
reading can now be performed!
-
- wait (mutex)
// now we need to adjust readcount so we
need to guarantee mutual // exclusion
Hence- may need to wait if another process is
accessing // readcount. -
- readcount - - // Once this process
gets through the wait in order to access
readcount, , it // decrements and tests
readcount. -
- if (readcount 0) signal (wrt)
// This means there were no processes waiting to
read or reading.
11Dining-Philosophers Problem
- Five philosophers eat, think. Once in a while,
they get hungry. - To eat, a philosopher needs two chopsticks the
ones on either side of him/her. - Philosopher can only pick up one at a time, and
it must be available. - Once a philosopher has both chopsticks, she eats
when finished, she gives up both chopsticks and
then again resumes thinking. - Shared data two items.
- Bowl of rice (you may liken this to a data set)
- Semaphore chopstick 5 initialized to 1 (an
array of integers) - The solution (next slide) guarantees no two
neighbors are eating at the same time, but
deadlock will be clearly apparent.
12Dining-Philosophers Problem the Structure
- The structure of Philosopher i
- // Go through code on your own
- do
- wait ( chopsticki ) // waiting on
chopstick to the right. - wait ( chopStick (i 1) 5 ) // waiting
on other chopstick - // can see we are waiting on chopsticks on
either side of her. -
- // eating takes place..
- signal ( chopsticki ) // releases one
chopstick - signal (chopstick (i 1) 5 ) //
releases other chopstick. - // here, philosopher attempts to give up both
chopsticks. - // philosopher resumes thinking
- while (true)
13Dining-Philosophers Problem (Cont.)
- Remedies (from your book) that ensure
deadlock will not occur - 1. Allow at most four philosophers to be sitting
simultaneously at table. - 2. Allow a philosopher to pick up her chopsticks
only if both chopsticks are available (to do this
she must pick them up in a critical section) - 3. Use an asymmetric solution that is, an odd
philosopher picks up first her left chopstick
and then her right chopstick, whereas an even
philosopher picks up her right chopstick and then
her left chopstick. - Unfortunately even though these approaches
produce a deadlock free solution, this does not
preclude starvation! - Incidentally, we will return to the dining
philosophers problem a few slides ahead
14Problems with Semaphores
- Correct use of semaphore operations requires
careful attention Remember, these are usually
written by programmers such as you and me - Lots of possibilities for programming errors when
programmers use semaphores to solve critical
section problems. - While some of the possibilities are unlikely to
occur, they can occur and results can be
disastrous. - Getting the sequence wrong, repeating the same
system call, omitting a call - signal (mutex) . wait (mutex) // sequence
wrong - wait (mutex) wait (mutex) // two waits no
signal? - wait (mutex) or signal (mutex) (but not both) //
forgetting one. - A single process error can really wreak havoc
- Semaphores are powerful and flexible for
enforcing mutual exclusion and for coordinating
processes. - It is often difficult to produce a correct
program using semaphores. - The difficulty centers around the wait() and
signal() operations that may be scattered
throughout a program. - Not easy to see the overall effect of these
operations on the semaphores they affect. - Remember semaphores themselves are not atomic,
but include critical sections which are atomic by
using techniques such as test and set
Semaphores are operating system features
available to programmers - Enter the monitor type structure
15Monitors
- A Monitor is a high-level abstraction that
provides a convenient and effective mechanism for
process synchronization. - A monitor provides equivalent functionality to
that of semaphores and is easier to control. - Monitors have been implemented in a number of
programming languages including Java. - Using a monitor, a programmer may put monitor
locks on any object, like a linked list. - Can have a lock on a group of linked lists
(single lock) or have a lock for each linked
list, Lots of flexibility here. - A monitor is first of all an abstract data type
(ADT) with restrictions - Only one process may be active within the monitor
at a time - Note in its implementation, this data type has
both private data and public methods. - This data type provides a set of
programmer-defined operations providing for
mutual exclusion . - Recognize (again) that only one process may be
active in the monitor at any given time. - Also contains shared variables which establish
the state of the instance at that time. - monitor monitor-name
-
- // shared variable declarations // kind of like
instance variables. - procedure P1 () .
-
- procedure Pn ()
Looks kind of like a standard class definition,
doesnt it.
16Monitors
- Since this is an object, only public methods can
access - the instance variables (in the monitor object),
- its formal parameters for a specific method, and
- any local variables declared within the methods
(only accessible by code in this method. - Again, only one process at a time can be active
within the monitor. - This is important.
- ? Thus the programmer does not have to worry
about this kind of synchronization issues
revolving around sharing the monitor.
17Schematic view of a Monitor
Queue of clients for the monitor
There can be a number of operations in a monitor
18More on the Monitor and its Operations
- But unfortunately, this ADT is not powerful
enough for modeling some synchronization schemes.
And, in fact, programmers can and do add some of
their own synchronization mechanisms the
monitor doesnt come with them. - From Stallings book As it turns out, in order
for a monitor to be useful for concurrent
processing, a monitor must include
synchronization tools. - For example suppose a process invokes a monitor
and, while in the monitor, this process must be
blocked until some condition is satisfied that
is, it cannot continue until something becomes
true, false, available, etc.. So. - A facility is needed through which the process is
not only blocked while using the monitor, but
releases the monitor so that some other process
may have access to the monitor and enter it.
Remember, there is only one instance of the
monitor. - Later, when this condition is satisfied and the
monitor is again available, the process can be
resumed and allowed to reenter the monitor at the
point of its suspension. - Monitors support synchronization by using
condition constructs or condition variables. - These are contained within the monitor and
accessible only within the monitor. - These guys are special data types in monitors
that are operated on by two functions (ahead) - First, the variables look like condition x,
y - They are operated upon by wait() and signal(),
as in x.wait or x.signal. - x.wait () this suspends the execution of the
calling process on condition c.
Monitor is now available to other
processes. - x.signal () is a system call to resume
execution of a blocked process after a wait() on
the same condition. If there are several
processes blocked on this condition, OS will
select one of them if there are none, do
nothing. .
19Condition Variables Synchronization
Mechanizations for use in Monitors
- It is important to note that the wait() and
signal() operations are different than those for
the semaphore. - If a process in a monitor signals() and no task
is waiting on the condition variable, the signal
is lost. - We like to think (not exactly true the monitor
has only a single entry point that is guarded so
that only one process may be in the monitor at a
time. - Other processes that attempt to enter the monitor
join a queue of processes blocked waiting for
monitor availability. (see two slides back) - Once a process is in the monitor, it can
temporarily block itself on condition x by
issuing x.wait. It will then be placed in a
queue of processes waiting to reenter the monitor
when the condition changes and resume execution
at the point in its program following the wait().
- If a process that is executing in a monitor
detects a change in condition variable x, it
issues a x.signal operation that alerts the
corresponding condition queue that the condition
had changed.
20 Monitor with Condition Variables
Queue of entering processes
Can see graphically the additional
synchronization mechanisms programmer-defined. T
his will be come clear ahead?
21Discussion on signal() and wait()
- So, again, x.wait() ? process invoking this
operation is suspended until another process
invokes x.signal() - x.signal() resumes exactly one suspended process,
if in fact there is a process suspended
otherwise, there is no net effect, as we have
said. - Even with these additional synch mechanisms,
nothing is free. - If signal() is invoked by P and another process,
Q, is suspended (associated with the condition
x), then Q can resume, and P must wait. - Recall We cannot have two processes with
simultaneous access. - But there are two possibilities and both are
reasonable - Signal and Wait. P either waits until Q leaves
the monitor or waits for another logical
condition to be okay. - Signal and Continue. Here, Q either waits until
P leaves the monitor or waits for another
condition to be okay. - Here, P is already executing, so let it continue,
but if so when Q resumes, the logical condition
for which Q was waiting might not be true. - In Concurrent Pascal (no longer taught), when P
executes the signal operation, it immediately
leaves the monitor and Q is resumed immediately.
22Good Example of Monitor w/Conditions
- Lets consider the bounded buffer
producer-consumer problem. And lets look at the
code on the next page - The monitor module, boundedbuffer, controls the
buffer used to store and retrieve characters. - The monitor includes two condition variables
(declared with the construct cond notfull is
true when there is room to add at least one
character to the buffer, and notempty is true
when there is at least one character in the
buffer. - lttake a brief look at the declarations in the
monitor next slide gt - We can see that there are two methods in this
monitor append() and take() - A producer can add characters to the buffer only
by means of the procedure append inside the
monitor the producer itself does not have
direct access to buffer. - The procedure first checks the condition notfull
to determine if there is space available in the
buffer. - If not, the process executing the monitor is
blocked on that condition. - Some other process (producer or consumer) may now
enter the monitor. - Later, when the buffer is no longer full, the
blocked process may be removed from the queue,
reactivated , and resume processing. - After placing a character in the buffer, the
process signals the notempty condition. - A similar description can be made of the consumer
function. - Lets look at the code..l.
23 // program producerconsumer monitor
boundedbuffer char buffer N // N is the
buffer size int nextin, nextout, count //
these variables are obvious I hope. cond
notfull, notempty // condition variables for
synchronization void append (char x) //
executed by the producer if (count
N) cwait (notfull) // buffer is full avoid
overflow wait on notfull! buffer nextin
x // otherwise, move x into buffer nextin
(nextin 1) N // adjust location for next
append mod buffer size count // one more
item in buffer csignal (notempty) // resume
any waiting consumer void take (char x)
if (count 0) cwait (notempty)// buffer
empty avoid underflow wait until notempty. x
buffer nextout // otherwise, assign
next char in buffer to x nextout (nextout
1) N // adjust location of next item for
consumption count -- // one fewer item in
buffer csignal (notfull) // signal if any
process waiting until buffer is not full. //
monitor body nextin 0 nextout 0 count
0 // buffer is initially empty.
24and, void producer() char x while
(true) produce (x) append (x)
void consumer () char x while
(true) take(x) consume (x) void
main() parbegin (producer, consumer)
25Explanation of Preceding Code
- This example points out the division of
responsibility with monitors as compared to
semaphores. - In the case of monitors, the monitor construct
itself enforces mutual exclusion - it is not possible for both producer and consumer
to simultaneously access buffer. - However, the programmer must place the
appropriate wait and signal primitives inside
the monitor to prevent processes from depositing
items in a full buffer or removing them from an
empty one. (see two slides back used cwait and
csignal) - In the case of semaphores, both mutual exclusion
and synchronization are the responsibility of the
programmer. - And we could go on in this area.
- These last few slides came from Stallings to
supplement our book.
26Revisit Dining Philosophers with Monitors
- The classic problem has deadlock built in. Lets
take a look using Monitors! - Here, we impose the constraint that a philosopher
may pick up her chopsticks only if both are
available. (one of the solutions cited
earlier - The algorithm needs to capture the states a
philosopher may be in The states are thinking,
hungry, and eating. - Note enum thinking, hungry, eating state5
- This means we have an array of enum data types
and each element of state has a value of one of
the three enumerated data types. - We also add conditions to assist in additional
synchronization - condition self5
- which is used for philosopher I to delay
him/herself when hungry, but when s/he is - unable to obtain both chopsticks.
- Self is the synchronization primitives needed
for synchronization. - Recall the monitor itself provides for mutual
exclusion but synchronization is the
responsibility of the programmer and this code
must be included. - Given this backdrop, lets look at more code
27- We have a monitor, dp (for dining philosophers)
and the definition is provided in the next slide. - Initialization has every philosopher Thinking.
- At the top of the monitor, the enum data type is
declared. - The condition array, self, is declared as the
constraint on synchronization.
28Solution to Dining Philosophers
- monitor DP
-
- enum THINKING HUNGRY, EATING) state 5
- condition self 5
- void pickup (int i)
- statei HUNGRY
- test(i)
- if (statei ! EATING) self i.wait
-
- void putdown (int i)
- statei THINKING
- // test left and right
neighbors - test((i 4) 5)
- test((i 1) 5)
-
- void test (int i)
- if ( (state(i 4) 5 ! EATING)
- (statei HUNGRY)
Each philosopher starts by executing the pickup
method, which sets him/her to Hungry and invokes
test(). Note This may result in the suspension
of process. Note the code for the
wait() Consider for philosopher 0 Set state0
to Hungry invoke test(). In test() If
statement (state4 not Eating, state0 is
Hungry and state1 is not Eating (both
neighbors), so set state0 to EATING set
self0.signal(). Philosopher may now eat.
(kind of like consume) Then, philosopher
executes putdown(). Here, this state is reset
to Thinking (eating is done) and then tests the
two neighbors. These will be False, since both
(originally) are Thinking. Then Philosopher 0
invokes the condition self0.signal() citing
s/he is done. Note this signal is a programmer
responsibility for synchronization. Of course,
this is not perfect, and a philosopher may, in
fact, still starve to death! Heres the
initialization.Then note the instance
variables at the top and the condition
variables.
29Enough
- Book contains more materials on implementing a
monitor using semaphores and resuming processes
within a monitor. - I recommend that you read these.
- But weve accomplished our objective a
reasonably thorough introduction to semaphores
and monitors. - You should know the characteristics of each the
advantages and the disadvantages - Advanced operating systems courses and advanced
OS programming would continue with these last two
topics dealing with monitors.
30Synchronization Examples
- We will look at Linux
- We will look at synchronization in the kernel...
31Synchronization in Linux
- We recall that prior to version 2.6, Linux was a
non-preemptive kernel. - Newer versions of the Linux kernel are
preemptive. - This means a task can be preempted when running
in kernel mode. - Now, we know that the Linux kernel uses spinlocks
and semaphores for locking in the kernel. - Recall
- Spinlocks a type of semaphore that causes
continual looping executing a busy waiting
execution. - It may be costly because the CPU could be busy
doing other tasks. - Might be okay, however, in that there is no
context switching involved and when the locks are
expected to be held for only a very short time,
spinlocks may be the way to go. - Often employed on multiprocessor systems because
one thread can wait (spin) on one processor while
another process executes its critical section on
another processor. - Semaphores merely an integer variable itself
(usually an integer) that can be accessed
controlled only via two atomic
operations wait() and signal().
32Synchronization in Linux more
- As we are aware, in symmetric multiprocessors
(SMP), spinlocks will work well if critical
section execution is quick. - For single processor machines, the mechanism used
is to simply enable / disable kernel pre-emption. - In summary in single processors, we disable /
enable preemption, while in multiple processor
machines, we use spinlocks (acquire and release
the spin lock). - Now, how does Linux implement the preemption
process? - Linux employs two system calls
- preempt_disable() and preempt_enable().
33Synchronization in Linux more
- Pre-empting the kernel is not always a simple
task at times it may not be safe. - ? There may be a process executing in kernel
mode currently holding lock(s). - So, do we really want to preempt such
processes??? - To address this, each task in the system has what
is called a thread-info structure that contains
an integer counter, preempt_count. - This counter indicates the number of locks held
by the task. - Any time a kernel mode task is holding one or
more locks, (lock gt 0), then preempting cannot be
done.. - So only when this count becomes zero (that is,
the executing task releases all locks), then a
kernel process can be interrupted via the system
calls. - As we recall, spinlocks and kernel
preemption/non-preemption are acceptable
approaches when a lock is held for a short time. - If a lock must be held for longer periods, a
semaphore is used and typically a process blocks.
346.9 Atomic Transactions
- Goal is to ensure critical sections are executed
atomically by using mutual exclusion mechanisms.
- ? Motivation execute critical section totally
or not at all. - Long time focus in data base systems where one is
not allowed to read, say, financial data, when
the file / database is undergoing updating by
another process. - Much research has taken place in the database
arena. - Thinking apply this research to operating
systems too. - These are the ideas behind atomic transactions.
- The transaction must execution to completion.
356.9.1 The System Model
- Transaction a series of instructions that must
run from beginning to end (atomic execution)
considered a single logical function. - Here, we are reading and writing terminated by
commit or abort. - Commit implies that transaction has completed a
successful execution. - Abort implies that the transaction terminated due
to logical error or system failure. - Now, an aborted transaction may have updated data
and this data might not be in the correct state
when the transaction terminated abnormally. - Thus, we must undertake some kind of roll back or
restore to get the data back to its previous
stable / reliable state.
36The System Model more Devices
- So we must talk about device properties used to
store data that might be involved in some kind of
rollback. - Volatile Storage usually does not survive a
system crash (central memory and cache) - Nonvolatile Storage data usually survives
crashes (disks, magnetic tapes, ) - Stable Storage - Information never lost. Here
we - replicate information in non-volatile storage
caches (generally disk) with independent failure
models and to - update the information in a controlled manner.
(later chapters)
376.9.1 The System Model more the Log
- We also have a Log.
- Before a write() is ultimately executed, all
log info must be written to stable storage. - Clearly, this incurs a very serious performance
penalty!! - Additional Writes plus additional storage are
needed for the log, etc. - But where vitally important, such as in secure
environments, financial environments, and more,
this performance penalty is worth the price. - In general terms, by using the log, the system is
able to recover from a failure and recover from a
partial update. - Two algorithms are needed
- An undo() restores values of data prior to
Transaction - A redo() sets values of data to new values.
- Both old values and new values must have been
loaded into the log for these two algorithms to
be successful. - Naturally, failure can occur during recovery. So
this process must be idempotent (yield same
results if executed repeatedly).
386.9.2 Checkpoints in Logs
- Here again, consider what we have we have a log
and the log is used to determine which
transactions need to be undone and/or redone. - It would first appear that we might conceivably
have to go through the entire log to determine a
set of transactions that need reconsideration. - Major Disadvantages
- Search process takes time.
- Recovery takes lots of time
- We can save time by undertaking checkpoints.
- Essentially, in addition to the write-ahead
logging, the system performs periodic
checkpoints. - Process is
- Output all log records currently residing in
volatile storage (PM) onto stable memory - Output all modified data residing in volatile
storage to the stable storage - Output a log record ltcheckpointgt onto stable
storage.
396.9.2 Checkpoints
- The process of establishing checkpoints in the
log helps to shorten the recovery time, and it
makes sense - If transaction T1 is committed prior to the
checkpoint, T1 commit record is in log. - Here, any modifications made by T1 must have
already been written to stable storage either
prior to the checkpoint or as part of the
checkpoint itself. - In recovery, then, there is no need for a redo on
the transaction. - So, a recovery routine now only examines the log
to determine the most recent transaction that
started into execution before the most recent
checkpoint took place. - Does so by searching log backward to find first
ltcheckpointgt record and then finding the
ltT-startgt that follows it. - So the redo and undo only need to be accommodated
on this transaction and any others that started
after ltT- Startgt.
406.9.2 Log-Based Recovery
- To enable atomicity
- Record on stable storage information describing
all modifications made by transaction to data it
has accessed. - This method is referred to as write-ahead
logging. (this is not new) - System maintains a data structure called a log.
- Log itself consists of records describing
transaction particulars - Transaction name unique name of trans that
performed the write operation - Data item name unique name of the data item
written - Old value value prior to the write
- New value value that data item should have
after the write. - Process
- Prior to transaction execution, original record
written to log ltT-startsgt - After every write a new record is written.
- Upon completion, a ltT- commitsgt is written to
the log.
41Summarizing
- This chapter was all about working with
sequential processes that must share data. - Of course, mutual exclusion and synchronization
must be ensured. - Critical Sections can be accessed by only one
process at a time. - Different approaches were presented to deal with
this phenomenon. - Main disadvantage of user-coded solutions is they
usually require busy waiting. - Semaphores overcome this and can be used with
various schemes especially if there is hardware
support for atomic instructions. - Classic Problems There is a large class of
concurrency-related problems addressed by the
classic bounded-buffer problem, the
readers-writers problem, and the
dining-philosopher problem. - Monitors provide a synchronization mechanism by
using an abstract data type (ADT). - Condition variables provide a method by which a
monitor procedure can provide additional
constraints and block a process execution until
the process is signaled to continue. - We looked at Linux and discussed logs with
checkpoints for recovery.
42End of Chapter 6Part 3