Cse OS Unit 4
Cse OS Unit 4
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:
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.
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.
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. )
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:
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
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. ) )
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.