0% found this document useful (0 votes)
20 views

Cse OS Unit 4

This document discusses interprocess communication and process synchronization. It describes two main types of interprocess communication: shared memory and message passing. Shared memory allows fast information sharing between processes on the same computer but is more complex to set up. Message passing is slower but simpler to implement and works across multiple computers. The document also discusses process synchronization issues like race conditions that can occur when multiple processes access shared resources simultaneously. It provides an example of a producer-consumer problem and how a race condition can arise when both increment and decrement a shared counter variable at the same time. Process synchronization techniques are needed to prevent race conditions and allow only one process to access shared resources at a time.

Uploaded by

manushrivas26
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views

Cse OS Unit 4

This document discusses interprocess communication and process synchronization. It describes two main types of interprocess communication: shared memory and message passing. Shared memory allows fast information sharing between processes on the same computer but is more complex to set up. Message passing is slower but simpler to implement and works across multiple computers. The document also discusses process synchronization issues like race conditions that can occur when multiple processes access shared resources simultaneously. It provides an example of a producer-consumer problem and how a race condition can arise when both increment and decrement a shared counter variable at the same time. Process synchronization techniques are needed to prevent race conditions and allow only one process to access shared resources at a time.

Uploaded by

manushrivas26
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

Interprocess Communication

 Independent Processes operating concurrently on a system are those that can neither affect other processes
or be affected by other processes.
 Cooperating Processes are those that can affect or be affected by other processes. There are several
reasons why cooperating processes are allowed:
o Information Sharing - There may
be several processes which need
access to the same file for
example. ( e.g. pipelines. )
o Computation speedup - Often a
solution to a problem can be
solved faster if the problem can
be broken down into sub-tasks to
be solved simultaneously (
particularly when multiple
processors are involved. )
o Modularity - The most efficient
architecture may be to break a
system down into cooperating
modules. ( E.g. databases with a
client-server architecture. )
o Convenience - Even a single user
may be multi-tasking, such as editing, compiling, printing, and running the same code in different
windows.
 Cooperating processes require some type of inter-process communication, which is most commonly one of
two types: Shared Memory systems or Message Passing systems. Figure 3.13 illustrates the difference
between the two systems:
 Shared Memory is faster once it is set up, because no system calls are required and access occurs at normal
memory speeds. However it is more complicated to set up, and doesn't work as well across multiple
computers. Shared memory is generally preferable when large amounts of information must be shared
quickly on the same computer.
 Message Passing requires system calls for every message transfer, and is therefore slower, but it is simpler
to set up and works well across multiple computers. Message passing is generally preferable when the
amount and/or frequency of data transfers is small, or when multiple computers are involved.
1. Shared-Memory Systems
 In general the memory to be shared in a shared-memory system is initially within the address space of a
particular process, which needs to make system calls in order to make that memory publicly available to
one or more other processes.
 Other processes which wish to use the shared memory must then make their own system calls to attach the
shared memory area onto their address space.
 Generally a few messages must be passed back and forth between the cooperating processes first in order to
set up and coordinate the shared memory access.
2. Message-Passing Systems
 Message passing systems must support at a minimum system calls for "send message" and "receive
message".
 A communication link must be established between the cooperating processes before messages can be sent.
 There are three key issues to be resolved in message passing systems as further explored in the next three
subsections:
o Direct or indirect communication ( naming )
o Synchronous or asynchronous communication
o Automatic or explicit buffering.
Pushpraj Singh/VITS, Satna Operating System 1
We will look at each of these issues in following section:
1. Naming
 With direct communication the sender must know the name of the receiver to which it wishes to send a
message.
o There is a one-to-one link between every sender-receiver pair.
o For symmetric communication, the receiver must also know the specific name of the sender from
which it wishes to receive messages. For asymmetric communications, this is not necessary.
 Indirect communication uses shared mailboxes, or ports.
o Multiple processes can share the same mailbox or boxes.
o Only one process can read any given message in a mailbox. Initially the process that creates the
mailbox is the owner, and is the only one allowed to read mail in the mailbox, although this
privilege may be transferred.
 ( Of course the process that reads the message can immediately turn around and place an
identical message back in the box for someone else to read, but that may put it at the back
end of a queue of messages. )
o The OS must provide system calls to create and delete mailboxes, and to send and receive messages
to/from mailboxes.
2. Synchronization
 Either the sending or receiving of messages ( or neither or both ) may be either blocking or non-blocking.
3. Buffering
 Messages are passed via queues, which may have one of three capacity configurations:
1. Zero capacity – Messages cannot be stored in the queue, so senders must block until receivers
accept the messages.
2. Bounded capacity- There is a certain pre-determined finite capacity in the queue. Senders must
block if the queue is full, until space becomes available in the queue, but may be either blocking or
non-blocking otherwise.
3. Unbounded capacity - The queue has a theoretical infinite capacity, so senders are never forced to
block.

Process Synchronization
We have seen earlier cooperating processes (those that can effect or be effected by other simultaneously
running processes ), and as an example, we used the producer-consumer cooperating processes:

Producer code from chapter 3: Consumer code from chapter 3:


item nextProduced; item nextConsumed;
while( true ) { while( true ) {
/* Produce an item and store it in nextProduced */ /* Wait for an item to become available */
nextProduced = makeNewItem( . . . ); while( in == out )
/* Wait for space to become available */ ; /* Do nothing */
while( ( ( in + 1 ) % BUFFER_SIZE ) == out ) /* Get the next available item */
; /* Do nothing */ nextConsumed = buffer[ out ];
/* And then store the item and repeat the loop. */ out = ( out + 1 ) % BUFFER_SIZE;
buffer[ in ] = nextProduced; /* Consume the item in nextConsumed
in = ( in + 1 ) % BUFFER_SIZE; ( Do something with it ) */
} }

 The only problem with the above code is that the maximum number of items which can be placed into the
buffer is BUFFER_SIZE - 1. One slot is unavailable because there always has to be a gap between the
producer and the consumer.
Pushpraj Singh/VITS, Satna Operating System 2
 We could try to overcome this deficiency by introducing a counter variable, as shown in the following code
segments:
 Unfortunately we have now introduced a
new problem, because both the producer
and the consumer are adjusting the value
of the variable counter, which can lead to a
condition known as a race condition. In
this condition a piece of code may or may
not work correctly, depending on which of
two simultaneous processes executes first,
and more importantly if one of the
processes gets interrupted such that the
other process runs between important steps
of the first process. ( Bank balance
example discussed in class. )
 The particular problem above comes from
the producer executing "counter++" at the
same time the consumer is executing
"counter--". If one process gets part way
through making the update and then the
other process butts in, the value of counter
can get left in an incorrect state.
 But, you might say, "Each of those are single instructions - How can they get interrupted halfway through?"
The answer is that although they are single instructions in C++, they are actually three steps each at the
hardware level: (1) Fetch counter from
memory into a register, (2) increment or
decrement the register, and (3) Store the
new value of counter back to memory. If
the instructions from the two processes get
interleaved, there could be serious
problems, such as illustrated by the
following:
 Exercise: What would be the resulting
value of counter if the order of statements
T4 and T5 were reversed? ( What should
the value of counter be after one producer
and one consumer, assuming the original
value was 5? )
 Note that race conditions are notoriously
difficult to identify and debug, because by
their very nature they only occur on rare
occasions, and only when the timing is just exactly right. ( or wrong! :-) ) Race conditions are also very
difficult to reproduce. :-(
 Obviously the solution is to only allow one process at a time to manipulate the value "counter". This is a
very common occurrence among cooperating processes, so lets look at some ways in which this is done, as
well as some classic problems in this area.

Pushpraj Singh/VITS, Satna Operating System 3


The Critical-Section Problem
 The producer-consumer problem described above is a specific example of a more general situation known
as the critical section problem. The general idea is that in a number of cooperating processes, each has a
critical section of code, with the following conditions and terminologies:
o Only one process in the group can be allowed to execute in their critical section at any one time. If
one process is already executing their critical
section and another process wishes to do so,
then the second process must be made to wait
until the first process has completed their
critical section work.
o The code preceding the critical section, and
which controls access to the critical section, is
termed the entry section. It acts like a carefully
controlled locking door.
o The code following the critical section is
termed the exit section. It generally releases the
lock on someone else's door, or at least lets the
world know that they are no longer in their
critical section.
o The rest of the code not included in either the critical section or the entry or exit sections is termed
the remainder section.
 A solution to the critical section problem must satisfy the following three conditions:
1. Mutual Exclusion - Only one process at a time can be executing in their critical section.
2. Progress - If no process is currently executing in their critical section, and one or more processes
want to execute their critical section, then only the processes not in their remainder sections can
participate in the decision, and the decision cannot be postponed indefinitely. ( I.e. processes cannot
be blocked forever waiting to get into their critical sections. )
3. Bounded Waiting - There exists a limit as to how many other processes can get into their critical
sections after a process requests entry into their critical section and before that request is granted. (
I.e. a process requesting entry into their critical section will get a turn eventually, and there is a limit
as to how many other processes get to go first. )
 We assume that all processes proceed at a non-zero speed, but no assumptions can be made regarding the
relative speed of one process versus another.
 Kernel processes can also be subject to race conditions, which can be especially problematic when updating
commonly shared kernel data structures such as open file tables or virtual memory management.
Accordingly kernels can take on one of two forms:
o Non-preemptive kernels do not allow processes to be interrupted while in kernel mode. This
eliminates the possibility of kernel-mode race conditions, but requires kernel mode operations to
complete very quickly, and can be problematic for real-time systems, because timing cannot be
guaranteed.
o Preemptive kernels allow for real-time operations, but must be carefully written to avoid race
conditions. This can be especially tricky on SMP systems, in which multiple kernel processes may
be running simultaneously on different processors.
 Non-preemptive kernels include Windows XP, 2000, traditional UNIX, and Linux prior to 2.6; Preemptive
kernels include Linux 2.6 and later, and some commercial UNIXes such as Solaris and IRIX.

Pushpraj Singh/VITS, Satna Operating System 4


Peterson's Solution
 Peterson's Solution is a classic software-based solution to the critical section problem. It is unfortunately not
guaranteed to work on modern hardware, due to vagaries of load and store operations, but it illustrates a
number of important concepts.
 Peterson's solution is based on two processes, P0 and P1, which alternate between their critical sections and
remainder sections. For convenience of discussion, "this" process is Pi, and the "other" process is Pj.
( I.e. j = 1 - i )
 Peterson's solution requires two shared data items:
o int turn - Indicates whose turn it is to enter into the critical section. If turn = = i, then process i is allowed into
their critical section.
o boolean flag[ 2 ] - Indicates when a process wants to enter into their critical section. When process i wants to
enter their critical section, it sets flag[ i ] to true.
 In the following diagram, the entry and exit
sections are enclosed in boxes.
o In the entry section, process i first raises a flag
indicating a desire to enter the critical section.
o Then turn is set to j to allow the other process to
enter their critical section if process j so desires.
o The while loop is a busy loop ( notice the
semicolon at the end ), which makes process i
wait as long as process j has the turn and wants
to enter the critical section.
o Process i lowers the flag[ i ] in the exit section,
allowing process j to continue if it has been
waiting.
 To prove that the solution is correct, we must
examine the three conditions listed above:
1. Mutual exclusion - If one process is executing
their critical section when the other wishes to do
so, the second process will become blocked by the flag of the first process. If both processes attempt to enter
at the same time, the last process to execute "turn = j" will be blocked.
2. Progress - Each process can only be blocked at the while if the other process wants to use the critical section (
flag[ j ] = = true ), AND it is the other process's turn to use the critical section ( turn = = j ). If both of those
conditions are true, then the other process ( j ) will be allowed to enter the critical section, and upon exiting the
critical section, will set flag[ j ] to false, releasing process i. The shared variable turn assures that only one
process at a time can be blocked, and the flag variable allows one process to release the other when exiting
their critical section.
3. Bounded Waiting - As each process enters their entry section, they set the turn variable to be the other
processes turn. Since no process ever sets it back to their own turn, this ensures that each process will have to
let the other process go first at most one time before it becomes their turn again.
 Note that the instruction "turn = j" is atomic, that is it is a single machine instruction which cannot be
interrupted.

Synchronization Hardware
 To generalize the solution(s) expressed above, each process when entering their critical section must set
some sort of lock, to prevent other processes from entering their critical sections simultaneously, and must
release the lock when exiting their critical section, to allow other processes to proceed. Obviously it must
be possible to attain the lock only when no other process has already set a lock. Specific implementations
of this general procedure can get quite complicated, and may include hardware solutions as outlined in this
section.

Pushpraj Singh/VITS, Satna Operating System 5


 One simple solution to the critical section problem
is to simply prevent a process from being
interrupted while in their critical section, which is
the approach taken by nonpreemptive kernels.
Unfortunately this does not work well in
multiprocessor environments, due to the
difficulties in disabling and the re-enabling
interrupts on all processors. There is also a
question as to how this approach affects timing if
the clock interrupt is disabled.
 Another approach is for hardware to provide
certain atomic operations. These operations are
guaranteed to operate as a single instruction, without interruption. One such operation is the "Test and Set",
which simultaneously sets a boolean lock variable and returns its previous value, as shown in Figures 6.4
and 6.5:
 Another variation on the test-and-set is an atomic
swap of two booleans, as shown in Figures 6.6
and 6.7:
 The above examples satisfy the mutual exclusion
requirement, but unfortunately do not guarantee
bounded waiting. If there are multiple processes
trying to get into their critical sections, there is no
guarantee of what order they will enter, and any
one process could have the bad luck to wait
forever until they got their turn in the critical
section. ( Since there is no guarantee as to the

relative rates of the processes, a very fast


process could theoretically release the lock,
whip through their remainder section, and re-
lock the lock before a slower process got a
chance. As more and more processes are
involved vying for the same resource, the odds
of a slow process getting locked out completely
increase. )
 Figure 6.8 illustrates a solution using test-and-
set that does satisfy this requirement, using two
shared data structures, boolean lock and
boolean waiting[ N ], where N is the number of
processes in contention for critical sections:

Pushpraj Singh/VITS, Satna Operating System 6


 The key feature of the above algorithm is that a process blocks on the AND of the critical section being
locked and that this process is in the waiting state. When exiting a critical section, the exiting process does
not just unlock the critical section and let the
other processes have a free-for-all trying to get in.
Rather it first looks in an orderly progression (
starting with the next process on the list ) for a
process that has been waiting, and if it finds one,
then it releases that particular process from its
waiting state, without unlocking the critical
section, thereby allowing a specific process into
the critical section while continuing to block all
the others. Only if there are no other processes
currently waiting is the general lock removed,
allowing the next process to come along access to
the critical section.
 Unfortunately, hardware level locks are especially
difficult to implement in multi-processor
architectures. Discussion of such issues is left to
books on advanced computer architecture.
Semaphores
 The hardware solutions described above can be difficult for application
programmers to implement. An alternative is to use semaphores, which are
integer variables for which only two (atomic) operations are defined, the wait and
signal operations, as shown in the following figure.
 Note that not only must the variable-changing steps ( S-- and S++ ) be indivisible,
it is also necessary that for the wait operation when the test proves false that there
be no interruptions before S gets decremented. It IS okay, however, for the busy
loop to be interrupted when the test is true, which prevents the system from
hanging forever.
Usage
 In practice, semaphores can take on one of two forms:
o Binary semaphores can take on one of two values, 0 or 1. They can be
used to solve the critical section problem as described above, and are
sometimes known as mutexes, because they provide mutual exclusion.
The use of mutexes for this purpose is shown in Figure 6.9 below.
o Counting semaphores can take on any integer value, and are usually
used to count the number remaining of some limited resource. The
counter is initialized to the number of such resources available in the
system, and whenever the counting semaphore is greater than zero, then
a process can enter a critical section and use one of the resources. When
the counter gets to zero ( or negative in some implementations ), then
the process blocks until another process frees up a resource and increments the counting semaphore with a
signal call. ( The binary semaphore can be seen as just a special case where the number of resources initially
available is just one. )
Implementation
 The big problem with semaphores as described above is the busy loop in the wait call, which consumes
CPU cycles without doing any useful work. This type of lock is known as a spinlock, because the lock just
sits there and spins while it waits. While this is generally a bad thing, it does have the advantage of not
invoking context switches, and so it is sometimes used in multi-processing systems when the wait time is
expected to be short - One thread spins on one processor while another completes their critical section on
another processor.
Pushpraj Singh/VITS, Satna Operating System 7
 An alternative approach is to block a process when it is forced to wait for an available semaphore, and
swap it out of the CPU. In this implementation each semaphore needs to maintain a list of processes that
are blocked waiting for it, so that one of the processes can
be woken up and swapped back in when the semaphore
becomes available. (Whether it gets swapped back into the
CPU immediately or whether it needs to hang out in the
ready queue for a while is a scheduling problem. )
 The new definition of a semaphore and the corresponding
wait and signal operations are shown as follows:
 Note that in this implementation the value of the
semaphore can actually become negative, in which case its
magnitude is the number of processes waiting for that
semaphore. This is a result of decrementing the counter
before checking its value.
 Key to the success of semaphores is that the wait and
signal operations be atomic, that is no other process can
execute a wait or signal on the same semaphore at the
same time. ( Other processes could be allowed to do other
things, including working with other semaphores, they just
can't have access to this semaphore. ) On single processors
this can be implemented by disabling interrupts during the
execution of wait and signal; Multiprocessor systems have
to use more complex methods, including the use of spinlocking.
Deadlocks and Starvation
 One important problem that can arise when using semaphores to block processes waiting for a limited
resource is the problem of deadlocks, which occur when multiple processes are blocked, each waiting for a
resource that can only be freed by one of the other ( blocked ) processes, as
illustrated in the following example
 Another problem to consider is that of starvation, in which one or more
processes gets blocked forever, and never get a chance to take their turn in
the critical section. For example, in the semaphores above, we did not
specify the algorithms for adding processes to the waiting queue in the
semaphore in the wait( ) call, or selecting one to be removed from the
queue in the signal( ) call. If the method chosen is a FIFO queue, then
every process will eventually get their turn, but if a LIFO queue is implemented instead, then the first
process to start waiting could starve.
Priority Inversion
 A challenging scheduling problem arises when a high-priority process gets blocked waiting for a resource
that is currently held by a low-priority process.
 If the low-priority process gets pre-empted by one or more medium-priority processes, then the high-
priority process is essentially made to wait for the medium priority processes to finish before the low-
priority process can release the needed resource, causing a priority inversion. If there are enough medium-
priority processes, then the high-priority process may be forced to wait for a very long time.
 One solution is a priority-inheritance protocol, in which a low-priority process holding a resource for
which a high-priority process is waiting will temporarily inherit the high priority from the waiting process.
This prevents the medium-priority processes from pre-empting the low-priority process until it releases the
resource, blocking the priority inversion problem.
 The book has an interesting discussion of how a priority inversion almost doomed the Mars Pathfinder
mission, and how the problem was solved when the priority inversion was stopped. Full details are
available online.

Pushpraj Singh/VITS, Satna Operating System 8


Classic Problems of Synchronization
The following classic problems are used to test virtually every new proposed synchronization algorithm.
1. The Bounded-Buffer Problem
2. The Readers-Writers Problem
3. The Dining-Philosophers Problem

The Bounded-Buffer Problem


 This is a generalization of the producer-consumer
problem wherein access is controlled to a shared group
of buffers of a limited size.
 In this solution, the two counting semaphores "full" and
"empty" keep track of the current number of full and
empty buffers respectively ( and initialized to 0 and N
respectively. ) The binary semaphore mutex controls
access to the critical section. The producer and
consumer processes are nearly identical - One can think
of the producer as producing full buffers, and the
consumer producing empty buffers.

The Readers-Writers Problem


 In the readers-writers problem there are some processes
( termed readers ) who only read the shared data, and
never change it, and there are other processes ( termed
writers ) who may change the data in addition to or
instead of reading it. There is no limit to how many
readers can access the data simultaneously, but when a writer accesses the data, it needs exclusive access.
 There are several variations to the readers-writers problem, most centered around relative priorities of readers
versus writers.
o The first readers-writers problem gives priority to readers. In this problem, if a reader wants access to the data,
and there is not already a writer accessing it, then access is
granted to the reader. A solution to this problem can lead to
starvation of the writers, as there could always be more
readers coming along to access the data. ( A steady stream of
readers will jump ahead of waiting writers as long as there is
currently already another reader accessing the data, because
the writer is forced to wait until the data is idle, which may
never happen if there are enough readers. )
o The second readers-writers problem gives priority to the
writers. In this problem, when a writer wants access to the
data it jumps to the head of the queue - All waiting readers
are blocked, and the writer gets access to the data as soon as
it becomes available. In this solution the readers may be
starved by a steady stream of writers.
 The following code is an example of the first readers-writers
problem, and involves an important counter and two binary
semaphores:
o readcount is used by the reader processes, to count the
number of readers currently accessing the data.
o mutex is a semaphore used only by the readers for
controlled access to readcount.
o wrt is a semaphore used to block and release the writers.
Pushpraj Singh/VITS, Satna Operating System 9
The first reader to access the data will set this lock and the last reader to exit will release it; The remaining
readers do not touch wrt.
o Note that the first reader to come along will block on wrt if there is currently a writer accessing the data, and
that all following readers will only block on mutex for their turn to increment readcount.
 Some hardware implementations provide specific reader-writer locks, which are accessed using an argument
specifying whether access is requested for reading or writing. The use of reader-writer locks is beneficial for
situation in which: (1) processes can be easily identified as either readers or writers, and (2) there are
significantly more readers than writers, making the additional overhead of the reader-writer lock pay off in terms
of increased concurrency of the readers.

The Dining-Philosophers Problem


 The dining philosophers problem is a classic synchronization problem involving the allocation of limited
resources amongst a group of processes in a deadlock-free and
starvation-free manner:
o Consider five philosophers sitting around a table, in which there
are five chopsticks evenly distributed and an endless bowl of
rice in the center, as shown in the diagram below. ( There is
exactly one chopstick between each pair of dining philosophers.
)
o These philosophers spend their lives alternating between two
activities: eating and thinking.
o When it is time for a philosopher to eat, it must first acquire two
chopsticks - one from their left and one from their right.
o When a philosopher thinks, it puts down both chopsticks in their
original locations.
 One possible solution, as shown in the following code section, is
to use a set of five semaphores ( chopsticks[ 5 ] ), and to have each hungry philosopher first wait on their left
chopstick ( chopsticks[ i ] ), and then wait on their right chopstick ( chopsticks[ ( i + 1 ) % 5 ] )
 But suppose that all five philosophers get hungry at the same time, and each starts by picking up their left
chopstick. They then look for their right chopstick, but because it is unavailable, they wait for it, forever, and
eventually all the philosophers starve due to the resulting
deadlock.
 Some potential solutions to the problem include:
o Only allow four philosophers to dine at the same time.
( Limited simultaneous processes. )
o Allow philosophers to pick up chopsticks only when
both are available, in a critical section. ( All or nothing
allocation of critical resources. )
o Use an asymmetric solution, in which odd philosophers
pick up their left chopstick first and even philosophers
pick up their right chopstick first. ( Will this solution
always work? What if there are an even number of
philosophers? )
 Note carefully that a deadlock-free solution to the dining
philosophers problem does not necessarily guarantee a
starvation-free one. ( While some or even most of the
philosophers may be able to get on with their normal
lives of eating and thinking, there may be one unlucky
soul who never seems to be able to get both chopsticks at
the same time.

Pushpraj Singh/VITS, Satna Operating System 10


Monitors
 Semaphores can be very useful for solving concurrency problems, but only if programmers use them properly. If
even one process fails to abide by the proper use of semaphores,
either accidentally or deliberately, then the whole system breaks
down. ( And since concurrency problems are by definition rare
events, the problem code may easily go unnoticed and/or be
heinous to debug. )
 For this reason a higher-level language construct has been
developed, called monitors.
Usage
 A monitor is essentially a class, in which all data is private, and
with the special restriction that only one method within any given
monitor object may be active at the same time. An additional
restriction is that monitor methods may only access the shared
data within the monitor and any data passed to them as
parameters. I.e. they cannot access any data external to the
monitor.
 Figure 6.17 shows a schematic of a monitor, with an entry queue
of processes waiting their turn to execute monitor operations
(methods)
 In order to fully realize the potential of monitors, we need to
introduce one additional new data type, known as a condition.
o A variable of type condition has only two legal operations, wait and signal. I.e. if X was defined as type
condition, then legal operations would be X.wait( ) and X.signal( )
o The wait operation blocks a process until some other process calls
signal.
o The signal process does nothing if there are no processes waiting
on that condition. Otherwise it wakes up exactly one waiting
process. ( Contrast this with counting semaphores, which always
affect the semaphore on a signal call. )
 Figure 6.18 below illustrates a monitor that includes condition
variables within its data space. Note that the condition variables,
along with the list of processes currently waiting for the conditions,
are in the data space of the monitor - The processes on these lists are
not "in" the monitor, in the sense that they are not executing any
code in the monitor.
 But now there is a potential problem - If process P within the
monitor issues a signal that would wake up process Q also within the
monitor, then there would be two processes running
simultaneously within the monitor, violating the exclusion
requirement. Accordingly there are two possible solutions
to this dilemma:
Signal and wait - When process P issues the signal to wake
up process Q, P then waits, either for Q to leave the monitor
or on some other condition.
Signal and continue - When P issues the signal, Q waits,
either for P to exit the monitor or for some other condition.
There are arguments for and against either choice.
Concurrent Pascal offers a third alternative - The signal call
causes the signaling process to immediately exit the monitor,
so that the waiting process can then wake up and proceed.
Pushpraj Singh/VITS, Satna Operating System 11
Dining-Philosophers Solution Using Monitors
 This solution to the dining philosophers uses
monitors, and the restriction that a philosopher
may only pick up chopsticks when both are
available. There are also two key data
structures in use in this solution:
1.enum { thinking, hungry, eating } state[ 5
]; A philosopher may only set their state to
eating when neither of their adjacent
neighbors is eating. ( state[ ( i + 1 ) % 5 ] !=
eating && state[ ( i + 4 ) % 5 ] != eating ).
2.condition self[ 5 ]; This condition is used to
delay a hungry philosopher who is unable to
acquire chopsticks.
 In the following solution philosophers share a
monitor, dp, and eat using the following
sequence of operations:
1.dp.pickup( ) - Acquires chopsticks, which
may block the process.
2.eat
3.dp.putdown( ) - Releases the chopsticks.

Implementing a Monitor Using Semaphores


 One possible implementation of a monitor
uses a semaphore "mutex" to control
mutual exclusionary access to the
monitor, and a counting semaphore "next"
on which processes can suspend
themselves waiting their turn to get ( back ) into the monitor.
The integer next_count keeps track of how many processes are
waiting in the next queue. Externally accessible monitor
processes are then implemented as:
 Condition variables can be implemented using semaphores as
well. For a condition x, a semaphore "x_sem" and an integer
"x_count" are introduced, both initialized to zero. The wait and
signal methods are then implemented as follows. ( This approach
to the condition implements the signal-and-wait option described
above for ensuring that only one process at a time is active
inside the monitor. )

Resuming Processes Within a Monitor


 When there are multiple processes waiting on the same
condition within a monitor, how does one decide which one to
wake up in response to a signal on that condition? One obvious
approach is FCFS, and this may be suitable in many cases.
 Another alternative is to assign ( integer ) priorities, and to wake
up the process with the smallest ( best ) priority.

Pushpraj Singh/VITS, Satna Operating System 12


 Figure 6.20 illustrates the use of such a condition within a monitor used for resource allocation. Processes
wishing to access this resource must specify the
time they expect to use it using the acquire( time )
method, and must call the release( ) method when
they are done with the resource.

 Unfortunately the use of monitors to restrict access


to resources still only works if programmers make
the requisite acquire and release calls properly. One
option would be to place the resource allocation
code into the monitor, thereby eliminating the
option for programmers to bypass or ignore the
monitor, but then that would substitute the
monitor's scheduling algorithms for whatever other
scheduling algorithms may have been chosen for
that particular resource. Chapter 14 on Protection
presents more advanced methods for enforcing
"nice" cooperation among processes contending for
shared resources.
 Concurrent Pascal, Mesa, C#, and Java all
implement monitors as described here. Erlang provides concurrency support using a similar mechanism.

Deadlocks
System Model
 For the purposes of deadlock discussion, a system can be modeled as a collection of limited resources,
which can be partitioned into different categories, to be allocated to a number of processes, each having
different needs.
 Resource categories may include memory, printers, CPUs, open files, tape drives, CD-ROMS, etc.
 By definition, all the resources within a category are equivalent, and a request of this category can be
equally satisfied by any one of the resources in that category. If this is not the case ( i.e. if there is some
difference between the resources within a category ), then that category needs to be further divided into
separate categories. For example, "printers" may need to be separated into "laser printers" and "color inkjet
printers".
 Some categories may have a single resource.
 In normal operation a process must request a resource before using it, and release it when it is done, in the
following sequence:
1. Request - If the request cannot be immediately granted, then the process must wait until the
resource(s) it needs become available. For example the system calls open( ), malloc( ), new( ), and
request( ).
2. Use - The process uses the resource, e.g. prints to the printer or reads from the file.
3. Release - The process relinquishes the resource. so that it becomes available for other processes. For
example, close( ), free( ), delete( ), and release( ).
 For all kernel-managed resources, the kernel keeps track of what resources are free and which are allocated,
to which process they are allocated, and a queue of processes waiting for this resource to become available.
Application-managed resources can be controlled using mutexes or wait( ) and signal( ) calls, ( i.e. binary
or counting semaphores. )
 A set of processes is deadlocked when every process in the set is waiting for a resource that is currently
allocated to another process in the set ( and which can only be released when that other waiting process
makes progress. )

Pushpraj Singh/VITS, Satna Operating System 13


Deadlock Characterization
Necessary Conditions
There are four conditions that are necessary to achieve deadlock:
1.Mutual Exclusion - At least one resource must be held in a non-sharable mode. If any other process requests this
resource, then that process must wait for the resource to be released.
2.Hold and Wait - A process must be simultaneously holding at least one resource and waiting for at least one
resource that is currently being held by some other process.
3.No preemption - Once a process is holding a resource (i.e. once its request has been granted), then that resource
cannot be taken away from that process until the process voluntarily releases it.
4.Circular Wait - A set of processes { P0, P1, P2, . . ., PN } must exist such that every P[ i ] is waiting for P[ ( i +
1 ) % ( N + 1 ) ]. (Note that this condition implies the hold-and-wait condition, but it is easier to deal with the
conditions if the four are considered separately. )

Resource-Allocation Graph
 In some cases deadlocks can be understood more clearly through the use of Resource-Allocation Graphs,
having the following properties:
o A set of resource categories, {R1, R2, R3, . . ., RN}, which appear as square nodes on the graph.
Dots inside the resource nodes indicate specific instances of the resource. (E.g. two dots might
represent two laser printers. )
o A set of processes, { P1, P2, P3, . . ., PN }
o Request Edges - A set of directed arcs from Pi to Rj, indicating that process Pi has requested Rj,
and is currently waiting for that resource to become available.
o Assignment Edges - A set of directed arcs from Rj to Pi indicating that resource Rj has been
allocated to process Pi, and that Pi is currently holding resource Rj.
o Note that a request edge can be converted into an assignment edge by reversing the direction of
the arc when the request is granted. (However note also that request edges point to the category box,
whereas assignment edges emanate from a particular instance dot within the box. )
o For example:
 If a resource-allocation graph contains no cycles, then the system is not deadlocked. ( When looking for
cycles, remember that these are directed graphs. ) See the example in Figure 7.2 above.
 If a resource-allocation graph does contain cycles AND each resource category contains only a single
instance, then a deadlock exists.
 If a resource category contains more than one instance, then the presence of a cycle in the resource-
allocation graph indicates the possibility of a deadlock, but does not guarantee one. Consider, for example,
Figures 7.3 and 7.4:

Pushpraj Singh/VITS, Satna Operating System 14


Methods for Handling Deadlocks
Generally speaking there are three ways of handling deadlocks:
1.Deadlock prevention or avoidance - Do not allow the system to get into a deadlocked state.
2.Deadlock detection and recovery - Abort a process or preempt some resources when deadlocks are detected.
3.Ignore the problem all together - If deadlocks only occur once a year or so, it may be better to simply let
them happen and reboot as necessary than to incur the constant overhead and system performance penalties
associated with deadlock prevention or detection. This is the approach that both Windows and UNIX take.

In order to avoid deadlocks, the system must have additional information about all processes. In particular, the
system must know what resources a process will or may request in the future. (Ranging from a simple worst-case
maximum to a complete resource request and release plan for each process, depending on the particular algorithm.)
Deadlock detection is fairly straightforward, but deadlock recovery requires either aborting processes or
preempting resources, neither of which is an attractive alternative.
If deadlocks are neither prevented nor detected, then when a deadlock occurs the system will gradually slow
down, as more and more processes become stuck waiting for resources currently held by the deadlock and by other
waiting processes. Unfortunately this slowdown can be indistinguishable from a general system slowdown when a
real-time process has heavy computing needs.

Deadlock Prevention
Deadlocks can be prevented by preventing at least one of the four required conditions:
1. Mutual Exclusion
 Shared resources such as read-only files do not lead to deadlocks.
 Unfortunately some resources, such as printers and tape drives, require exclusive access by a single process.
2. Hold and Wait
 To prevent this condition processes must be prevented from holding one or more resources while simultaneously
waiting for one or more others. There are several possibilities for this:
o Require that all processes request all resources at one time. This can be wasteful of system resources if a
process needs one resource early in its execution and doesn't need some other resource until much later.
o Require that processes holding resources must release them before requesting new resources, and then re-
acquire the released resources along with the new ones in a single new request. This can be a problem if a
process has partially completed an operation using a resource and then fails to get it re-allocated after
releasing it.
o Either of the methods described above can lead to starvation if a process requires one or more popular
resources.
3. No Preemption
 Preemption of process resource allocations can prevent this condition of deadlocks, when it is possible.
o One approach is that if a process is forced to wait when requesting a new resource, then all other resources
previously held by this process are implicitly released, ( preempted ), forcing this process to re-acquire the old
resources along with the new resources in a single request, similar to the previous discussion.
o Another approach is that when a resource is requested and not available, then the system looks to see what
other processes currently have those resources and are themselves blocked waiting for some other resource. If
such a process is found, then some of their resources may get preempted and added to the list of resources for
which the process is waiting.
o Either of these approaches may be applicable for resources whose states are easily saved and restored, such as
registers and memory, but are generally not applicable to other devices such as printers and tape drives.
4. Circular Wait
 One way to avoid circular wait is to number all resources, and to require that processes request resources
only in strictly increasing ( or decreasing ) order.
 In other words, in order to request resource Rj, a process must first release all Ri such that i >= j.
 One big challenge in this scheme is determining the relative ordering of the different resources

Pushpraj Singh/VITS, Satna Operating System 15


Deadlock Avoidance
 The general idea behind deadlock avoidance is to prevent deadlocks from ever happening, by preventing at least
one of the aforementioned conditions.
 This requires more information about each process, AND tends to lead to low device utilization. (i.e. it is a
conservative approach.)
 In some algorithms the scheduler only needs to know the maximum number of each resource that a process might
potentially use. In more complex algorithms the scheduler can also take advantage of the schedule of exactly
what resources may be needed in what order.
 When a scheduler sees that starting a process or granting resource requests may lead to future deadlocks, then
that process is just not started or the request is not granted.
 A resource allocation state is defined by the number of available and allocated resources and the maximum
requirements of all processes in the system.
Safe State Maximum Current
Needs Allocation
 A state is safe if the system can allocate all resources requested by all processes ( up to
their stated maximums ) without entering a deadlock state. P0 10 5
 More formally, a state is safe if there exists a safe sequence of processes { P0, P1, P2,
..., PN } such that all of the resource requests for Pi can be granted using the resources P1 4 2
currently allocated to Pi and all processes Pj where j < i. ( I.e. if all the processes prior P2 9 2
to Pi finish and free up their resources, then Pi will be able to finish also, using the
resources that they have freed up. )
 If a safe sequence does not exist, then the system is in an unsafe state, which
MAY lead to deadlock. ( All safe states are deadlock free, but not all unsafe
states lead to deadlocks. )
 For example, consider a system with 12 tape drives, allocated as follows. Is
this a safe state? What is the safe sequence?
 What happens to the above table if process P2 requests and is granted one
more tape drive?
 Key to the safe state approach is that when a request is made for resources,
the request is granted only if the resulting allocation state is a safe one.

Algorithm1. Resource-Allocation Graph Algorithm


 If resource categories have only single instances of their resources,
then deadlock states can be detected by cycles in the resource-
allocation graphs.
 In this case, unsafe states can be recognized and avoided by
augmenting the resource-allocation graph with claim edges, noted
by dashed lines, which point from a process to a resource that it may
request in the future.
 In order for this technique to work, all claim edges must be added to
the graph for any particular process before that process is allowed to
request any resources. ( Alternatively, processes may only make
requests for resources for which they have already established claim
edges, and claim edges cannot be added to any process that is
currently holding resources. )
 When a process makes a request, the claim edge Pi->Rj is converted
to a request edge. Similarly when a resource is released, the
assignment reverts back to a claim edge.
 This approach works by denying requests that would produce cycles
in the resource-allocation graph, taking claim edges into effect.
 Consider for example what happens when process P2 requests resource R2:
 The resulting resource-allocation graph would have a cycle in it, and so the request cannot be granted.
Pushpraj Singh/VITS, Satna Operating System 16
Algorithm 2: Banker's Algorithm
 For resource categories that contain more than one instance the resource-allocation graph method does not work,
and more complex ( and less efficient ) methods must be chosen.
 The Banker's Algorithm gets its name because it is a method that bankers could use to assure that when they lend
out resources they will still be able to satisfy all their clients. ( A banker won't loan out a little money to start
building a house unless they are assured that they will later be able to loan out the rest of the money to finish the
house. )
 When a process starts up, it must state in advance the maximum allocation of resources it may request, up to the
amount available on the system.
 When a request is made, the scheduler determines whether granting the request would leave the system in a safe
state. If not, then the process must wait until the request can be granted safely.
 The banker's algorithm relies on several key data structures: ( where n is the number of processes and m is the
number of resource categories. )
o Available[ m ] indicates how many resources are currently available of each type.
o Max[ n ][ m ] indicates the maximum demand of each process of each resource.
o Allocation[ n ][ m ] indicates the number of each resource category allocated to each process.
o Need[ n ][ m ] indicated the remaining resources needed of each type for each process. ( Note that Need[ i ][ j ]
= Max[ i ][ j ] - Allocation[ i ][ j ] for all i, j. )
 For simplification of discussions, we make the following notations / observations:
o One row of the Need vector, Need[ i ], can be treated as a vector corresponding to the needs of process i, and
similarly for Allocation and Max.
o A vector X is considered to be <= a vector Y if X[ i ] <= Y[ i ] for all i.

1. Safety Algorithm
 In order to apply the Banker's algorithm, we first need an algorithm for determining whether or not a particular
state is safe.
 This algorithm determines if the current state of a system is safe, according to the following steps:
1.Let Work and Finish be vectors of length m and n respectively.
 Work is a working copy of the available resources, which will be modified during the analysis.
 Finish is a vector of booleans indicating whether a particular process can finish. ( or has finished so far in
the analysis. )
 Initialize Work to Available, and Finish to false for all elements.
2.Find an i such that both (A) Finish[ i ] == false, and (B) Need[ i ] < Work. This process has not finished, but
could with the given available working set. If no such i exists, go to step 4.
3.Set Work = Work + Allocation[ i ], and set Finish[ i ] to true. This corresponds to process i finishing up and
releasing its resources back into the work pool. Then loop back to step 2.
4.If finish[ i ] == true for all i, then the state is a safe state, because a safe sequence has been found.
 JTB's Modification:
1.In step 1. instead of making Finish an array of booleans initialized to false, make it an array of ints initialized to
0. Also initialize an int s = 0 as a step counter.
2.In step 2, look for Finish[ i ] == 0.
3.In step 3, set Finish[ i ] to ++s. S is counting the number of finished processes.
4.For step 4, the test can be either Finish[ i ] > 0 for all i, or s >= n. The benefit of this method is that if a safe
state exists, then Finish[ ] indicates one safe sequence ( of possibly many. ) )

2. Resource-Request Algorithm (The Bankers Algorithm)


 Now that we have a tool for determining if a particular state is safe or not, we are now ready to look at the
Banker's algorithm itself.
 This algorithm determines if a new request is safe, and grants it only if it is safe to do so.

Pushpraj Singh/VITS, Satna Operating System 17


 When a request is made (that does not exceed currently available resources ), pretend it has been granted,
and then see if the resulting state is a safe one. If so, grant the request, and if not, deny the request, as
follows:
1. Let Request[ n ][ m ] indicate the number of resources of each type currently requested by
processes. If Request[ i ] > Need[ i ] for any process i, raise an error condition.
2. If Request[ i ] > Available for any process i, then that process must wait for resources to become
available. Otherwise the process can continue to step 3.
3. Check to see if the request can be granted safely, by pretending it has been granted and then seeing
if the resulting state is safe. If so, grant the request, and if not, then the process must wait until its
request can be granted safely. The procedure for granting a request ( or pretending to for testing
purposes ) is:
 Available = Available - Request
 Allocation = Allocation + Request
 Need = Need - Request
An Illustrative Example
 Consider the following situation:
 And now consider what happens if process P1 requests 1 instance of A and 2 instances of C. ( Request[ 1 ]
= ( 1, 0, 2 ) )
 What about requests of ( 3, 3,0 ) by P4? or ( 0, 2, 0 ) by P0? Can these be safely granted? Why or why not?

Deadlock Detection
 If deadlocks are not avoided, then another approach is to detect when they have occurred and recover
somehow.
 In addition to the performance hit of constantly checking for deadlocks, a policy / algorithm must be in
place for recovering from deadlocks, and there is potential for lost work when processes must be aborted or
have their resources preempted.
1. Single Instance of Each Resource Type
 If each resource category has a single
instance, then we can use a variation of the
resource-allocation graph known as a wait-for
graph.
 A wait-for graph can be constructed from a
resource-allocation graph by eliminating the
resources and collapsing the associated edges,
as shown in the figure below.
 An arc from Pi to Pj in a wait-for graph
indicates that process Pi is waiting for a
resource that process Pj is currently holding.
 As before, cycles in the wait-for graph
indicate deadlocks.
 This algorithm must maintain the wait-for
graph, and periodically search it for cycles.

Pushpraj Singh/VITS, Satna Operating System 18


2. Several Instances of a Resource Type
 The detection algorithm outlined here is essentially the same as the Banker's algorithm, with two subtle
differences:
o In step 1, the Banker's Algorithm sets Finish[ i ] to false for all i.
The algorithm presented here sets Finish[ i ] to false only if
Allocation[ i ] is not zero. If the currently allocated resources for
this process are zero, the algorithm sets Finish[ i ] to true. This is
essentially assuming that IF all of the other processes can finish,
then this process can finish also. Furthermore, this algorithm is
specifically looking for which processes are involved in a deadlock
situation, and a process that does not have any resources allocated
cannot be involved in a deadlock, and so can be removed from any further consideration.
o Steps 2 and 3 are unchanged
o In step 4, the basic Banker's Algorithm says that if Finish[ i ] == true for all i, that there is no deadlock. This
algorithm is more specific, by stating that if Finish[ i ] == false for any process Pi, then that process is
specifically involved in the deadlock which has been detected.
 ( Note: An alternative method was presented above, in which Finish
held integers instead of booleans. This vector would be initialized to
all zeros, and then filled with increasing integers as processes are
detected which can finish. If any processes are left at zero when the
algorithm completes, then there is a deadlock, and if not, then the
integers in finish describe a safe sequence. To modify this algorithm
to match this section of the text, processes with allocation = zero
could be filled in with N, N - 1, N - 2, etc. in step 1, and any
processes left with Finish = 0 in step 4 are the deadlocked processes. )
 Consider, for example, the following state, and determine if it is currently deadlocked:
 Now suppose that process P2 makes a request for an additional instance of type C, yielding the state shown
below. Is the system now deadlocked?
7.6.3 Detection-Algorithm Usage
 When should the deadlock detection be done? Frequently, or infrequently?
 The answer may depend on how frequently deadlocks are expected to occur, as well as the possible
consequences of not catching them immediately. ( If deadlocks are not removed immediately when they occur,
then more and more processes can "back up" behind the deadlock, making the eventual task of unblocking the
system more difficult and possibly damaging to more processes. )
 There are two obvious approaches, each with trade-offs:
1.Do deadlock detection after every resource allocation which cannot be immediately granted. This has the
advantage of detecting the deadlock right away, while the minimum number of processes are involved in the
deadlock. ( One might consider that the process whose request triggered the deadlock condition is the "cause"
of the deadlock, but realistically all of the processes in the cycle are equally responsible for the resulting
deadlock. ) The down side of this approach is the extensive overhead and performance hit caused by checking
for deadlocks so frequently.
2.Do deadlock detection only when there is some clue that a deadlock may have occurred, such as when CPU
utilization reduces to 40% or some other magic number. The advantage is that deadlock detection is done
much less frequently, but the down side is that it becomes impossible to detect the processes involved in the
original deadlock, and so deadlock recovery can be more complicated and damaging to more processes.
3.( As I write this, a third alternative comes to mind: Keep a historical log of resource allocations, since that last
known time of no deadlocks. Do deadlock checks periodically ( once an hour or when CPU usage is low?),
and then use the historical log to trace through and determine when the deadlock occurred and what processes
caused the initial deadlock. Unfortunately I'm not certain that breaking the original deadlock would then free
up the resulting log jam. )
Pushpraj Singh/VITS, Satna Operating System 19
Recovery from Deadlock
 There are three basic approaches to recovery from deadlock:
1. Inform the system operator, and allow him/her to take manual intervention.
2. Terminate one or more processes involved in the deadlock
3. Preempt resources.
Process Termination
 Two basic approaches, both of which recover resources allocated to terminated processes:
o Terminate all processes involved in the deadlock. This definitely solves the deadlock, but at the
expense of terminating more processes than would be absolutely necessary.
o Terminate processes one by one until the deadlock is broken. This is more conservative, but
requires doing deadlock detection after each step.
 In the latter case there are many factors that can go into deciding which processes to terminate next:
1. Process priorities.
2. How long the process has been running, and how close it is to finishing.
3. How many and what type of resources is the process holding. (Are they easy to preempt and
restore?)
4. How many more resources does the process need to complete.
5. How many processes will need to be terminated
6. Whether the process is interactive or batch.
7. (Whether or not the process has made non-restorable changes to any resource.)
Resource Preemption
 When preempting resources to relieve deadlock, there are three important issues to be addressed:
1. Selecting a victim - Deciding which resources to preempt from which processes involves many of
the same decision criteria outlined above.
2. Rollback - Ideally one would like to roll back a preempted process to a safe state prior to the point
at which that resource was originally allocated to the process. Unfortunately it can be difficult or
impossible to determine what such a safe state is, and so the only safe rollback is to roll back all the
way back to the beginning. ( I.e. abort the process and make it start over. )
3. Starvation - How do you guarantee that a process won't starve because its resources are constantly
being preempted? One option would be to use a priority system, and increase the priority of a
process every time its resources get preempted. Eventually it should get a high enough priority that
it won't get preempted any more.

Pushpraj Singh/VITS, Satna Operating System 20

You might also like