Operating Systems ch-2 Part2
Operating Systems ch-2 Part2
Direct Communication
Processes must name each other explicitly:
✦send (P, message)–send a message to process P
Mailbox sharing
✦P1,P2,and P3 share mailbox A.
✦P1,sends;P2andP3receive.
Solutions
✦Allow a link to be associated with at most two processes.
✦Allow only one process at a time to execute a receive operation.
✦Allow the system to select arbitrarily the receiver. Sender Is notified who the receiver was.
Concurrent Processes
The concurrent processes executing in the operating system may be either
independent processes or cooperating processes. A process is independent if it cannot affect
or be affected by the other processes executing in the system .Clearly, any process that does
not share any data (temporary or persistent) with any other process is independent. On the
other hand ,a process is cooperating if it can affect or be affected by the other processes
executing in the system. Clearly, any process that shares data with other processes is a
cooperating process.
We may want to provide an environment that allows process cooperation for several
reasons:
Information sharing: Since several users may be interested in the same piece of
information (for instance, a shared file), we must provide an environment to allow
concurrent access to these types of resources.
Computation speedup: If we want a particular task to run faster, we must break it into
subtasks, each of which will be executing in parallel with the others. Such a speedup can be
achieved only if the computer has multiple processing elements (such as CPUS or I/O
channels).
Modularity: We may want to construct the system in a modular fashion, the system
functions into separate processes or threads.
Convenience: Even an individual user may have many tasks on which to work at one time.
For instance, a user may be editing , printing ,and compiling in parallel.
Message-Passing System
The function of a message system is to allow processes to communicate with one
another without the need to resort to shared data. We have already seen message passing
used as a method of communication in micro kernels. In this scheme, services are provided
as ordinary user processes. That is, the services operate outside of the kernel.
Communication among the user processes is accomplished through the passing of
messages .An IPC facility provides a t least the two operations : send (message)and receive
(message).
Messages sent by a process can be of either fixed or variable size. If only fixed-sized
messages can be sent ,the system-level implementation is straight forward. This
restriction ,how ever, makes the task of programming more difficult. On the other hand,
variable-sized messages require a more complex system-level implementation, but the
programming task becomes simpler.
If processes P and Q want to communicate, they must send messages to and receive
messages from each other; a communication link must exist between them. This link can be
implemented in a variety of ways. We are concerned here not with the link's physical
implementation, but rather with its logical implementation. Here are several methods for
logically implementing a link and the send/receive operations:
Director indirect communication
Symmetric or asymmetric communication
Automatic or explicit buffering
Send by copy or send by reference
Fixed-sized or variable- sized messages
We look at each of these types of message systems next.
Synchronization
Communication between processes takes place by calls to send and receive primitives.
There are different design options for implementing each primitive. Message passing may
be either blocking or non blocking-also known as synchronous and asynchronous.
Blocking send: The sending process is blocked until the message is received by the
receiving process or by the mailbox.
Non blocking send: The sending process ends the message and resumes operation.
Non blocking receive: The receiver retrieves either a valid message or a null.
Different combinations of send and receive are possible. When both the send and
receive are blocking, we have a rendezvous between the sender and the receiver.
Buffering
Whether the communication is direct or indirect, messages exchanged by communicating
processes reside in a temporary queue. Basically ,such a queue can be implemented in three
ways:
Zero capacity: The queue has maximum length 0; thus, the link cannot have any messages
waiting in it. In this case, the sender must block until the recipient receives the message.
Bounded capacity: The queue has finite length n; thus, at most n messages can reside in it.
If the queue is not full when a new message is sent, the latter is placed in the queue (either
the message is copied or a pointer to the message is kept), and the sender can continue
execution without waiting. The link has a finite capacity, how ever. If the link is full ,the
sender must block until space is available in the queue.
Unbounded capacity: The queue has potentially infinite length; thus, any number of
messages can wait in it. The sender never blocks .The zero-capacity case is sometimes
referred to as a message system with no buffering; the other cases a referred to as automatic
buffering.
Client-Server Communication
Sockets
Remote Procedure Calls
Remote Method Invocation(Java)
Process Synchronization
A situation where several processes access and manipulate the same data
concurrently and the outcome of the execution depends on the particular order in which the
access takes place, is called a race condition.
Producer-Consumer Problem
Paradigm for cooperating processes, producer process produces information that is consumed by
a Consumer process.
To allow producer and consumer processes to run concurrently, we must have available a
buffer of items that can be filled by the producer and emptied by the consumer .A producer can
produce one item while the consumer is consuming another item. The producer and consumer
must be synchronized, so that the consumer does not try to consume an item that has not yet
been produced. In this situation, the consumer must wait until an item is produced.
✦unbounded-buffer places no practical limit on the size of the buffer
✦bounded-buffer assumes that there is a fixed buffer size.
Bounded-Buffer–Shared-Memory Solution
The code for the producer and consumer processes follows. The producer process has a local
variable Next produced in which the new item to be produced is stored:
Bounded-Buffer–Producer Process
Item nextProduced;
while(1)
{
while(((in+1)% BUFFER_SIZE)==out)
;/*do nothing */
buffer[in]=nextProduced;
in=(in+1)%BUFFER_SIZE;
}
Bounded-Buffer-consumer Process
do{
Entry section
Critical
section Exit section
Remainder section
}while(1);
A solution to the critical-section problem must satisfy the following three requirements:
Consider two processes P0 and P1. For convenience, when presenting Pi, we use Pi to denote
the other process; that is ,j ==1-i.
flag[i]=true
turn=j
while(flag[j]&&turn==j);
critical section
flag[i]=false
Remainder section
}while(1);
To enter the critical section, process Pi first sets flag [il to be true and then sets turn to
the value j ,there by asserting that if the other process wishes to enter the critical section
it can do so. If both processes try to enter at the same time, turn will be set to both i and j at
roughly the same time. Only one of these assignments will last; the other will occur, but will
be over written immediately. The eventual value of turn decides which of the two processes
is allowed to enter its critical section first.
To prove property 1,we note that each Pi enters its critical section only if either flag[jl==false or
turn==i. Also note that, if both processes can be executing in their critical sections
at the same time, then flag [i] ==flag[j]== true. These two observation simply that P0 and
P1 could not have successfully executed their while statements at about the same time, since the
value of turn can be either 0 or 1,but can not be both. Hence, one of the processes say Pj -must
have successfully executed the while statement, where as Pi had to execute atleast one
additional statement ("turn == j"). However, since, at that time, flag [j] == true, and turn == j,
and this condition will persist as long as Pi is in its critical section, the result follows:
To prove properties 2 and 3 , we note that a process Pi can be prevented from entering
the critical section only if it is stuck in the while loop with the condition flag [j] == true and turn
== j; this loop is the only one. If Pi is not ready to enter the critical section, then flag[j]==false
and Pi can enter its critical section. If Pi has set flag[j] to true and is also executing in its while
statement, then either turn == i or turn == j. If turn == i, then Pi will enter the critical section. If
turn == j, then Pi will enter the critical section. However, once Pi exits its critical section, it
will reset flag [ jl to false, allowing Pi to enter its critical section. If Pi resets flag [ j 1 to true, it
must also set turn to i.
Thus, since Pi does not change the value of the variable turn while executing the while
statement, Pi will enter the critical section (progress) after at most one entry by Pi (bounded
waiting).
Synchronization Hardware:
As with other aspects of software, hardware features can make the programming task easier
and improve system efficiency. In this section, we present some simple hardware instructions
that are available on many systems, and show how they can be used effectively in solving the
critical-section problem.
boolean TestAndSet(boo1ean&target)
boolean rv=target;
target =true;
return rv;
}
The critical-section problem could be solved simply in a uniprocess or environment if we
could for bid interrupts to occur while a shared variable is being modified. In this manner, we
could be sure that the current sequence of instructions would be allowed to execute in order
with out preemption. No other instructions would be run, so no unexpected modifications
could be made to the shared variable.
Many machines therefore provide special hardware instructions that allow us either to test and
modify the content of a word, or to swap the contents of two words, atomically-that is,as one
uninterruptible unit. We can use these special instructions to solve the critical-section problem
in a relatively simple manner. Rather than discussing one specific instruction for one specific
machine, let us abstract the main concepts behind these types of instructions.
The TestAndSet instruction can be defined as shown in code. The important characteristic is
that this instruction is executed atomically. Thus, if two Test And Set instructions are
executed simultaneously (each on a different CPU),they will be executed sequentially in some
arbitrary order.
do{
while(TestAndSet(lock));
critical section
lock=false
Remaindersection
}while(1);
void
Swap(boo1ean&a,boolean&b)
{
boolean temp=a;
a=b;
b=temp}
If the machine supports the Test And Set instruction ,then we can implement mutual
exclusion by declaring a Boolean variable lock, initialized to false.
If the machine supports the Swap instruction, then mutual exclusion can be
provided as follows. A global Boolean variable lock is declared and is initialized to
false. In addition, each process also has a local Boolean variable key.
Semaphores
The solutions to the critical –section problem presented before are not easy to generalize to
more complex problems. To over come this difficulty, we can use a synchronization tool
called a semaphore. A semaphore S is an integer variable that, a part from initialization, is
accessed only through two standard atomic operations : wait and signal. These operations were
originally termed P (for wait; from the Dutch proberen, to test)and V(for signal; from
verhogen,to increment).
wait(S)
{
while(S<=0);
S--;
}
The classical definitions of signal in pseudo code is
Signal(S)
S++;
Modifications to the integer value of the semaphore in the wait and signal operations must
be executed indivisibly. That is, when one process modifies the semaphore value, no other
process can simultaneously modify that same semaphore value. In addition, in the case of the
wait (S), the testing of the integer value of S (S5O),and its possible modification (S--),must also
be executed with out interruption.
Usage
We can use semaphores to deal with the n-process critical –section problem. The n
processes share a semaphore, muter (standing for mutual exclusion), initialized to 1. Each
process Pi is organized as shown in Figure .We can also use semaphores to solve various
synchronization problems.
For example, consider two concurrently running processes : PI with a statement S1 and P2
with a statement S2.Suppose that we require that S2 be executed only after S1 has completed.
We can implement this scheme readily by letting P1 and P2 share a common semaphore synch,
initialized to 0,and by inserting the statements in process PI, and the statements
wait(synch);
s2;
in process P2.Because synch is initialized to 0,P2willexecute S2 only after PI has invoked
signal(synch), which is after S1.
s1;
signal(synch);
do{
wait(mutex);
critical section
signal(mutex) ;
remainder section
}while(1);
Implementation
The main disadvantage of the mutual-exclusion solutions and of the semaphore definition given here ,is
that they all require busy waiting .While a process is in its critical section, any other process that tries to
enter its.
Critical section must loop continuously in the entry code. This continual looping is clearly a
problem in a real multi programming system, where a single CPU is shared among
many processes . Busy waiting wastes CPU cycles that some other process might be
able to use productively . This type of semaphore is also called a spin lock (because the
process "spins" while waiting for the lock). Spin locks are useful in multi processor systems.
The advantage of a spin lock is that no context switch is required when a process must
wait on a lock, and a context switch may take considerable time. Thus ,when locks are
expected to be held for short times , spin locks are useful.
To overcome the need for busy waiting, we can modify the definition of the wait
and signal semaphore operations . When a process executes the wait operation and
finds that the semaphore value is not positive ,it must wait. However, rather than busy
waiting, the process can block itself. The block operation places a process into a waiting
queue associated with the semaphore, and the state of the process is switched to the
waiting state .Then ,control is transferred to the CPU scheduler ,which selects another
process to execute.
A process that is blocked, waiting on a semaphore S, should be restarted when some
other process executes a signal operation. The process is restarted by a wakeup operation,
which changes the process from the waiting state to the ready state. The process is then
placed in the ready queue. (The CPU may or may not be switched from the running
process to the newly ready process, depending on the CPU-scheduling algorithm.)
To implement semaphores under this definition, we define a
semaphore as a "C" struct:typedef
struct{
int value;
struct process*L;
)semaphore;
Each semaphore has an integer value and a list of processes. When a process must wait on a
semaphore, it is added to the list of processes. A signal operation removes one process from
the list of waiting processes and awakens that process.
The wait semaphore operation can now be defined as
Void wait
(semaphoreS)
{
S.value--;
if(S.value<0)
{
Add this
process
toS.L;
block();
}
The block operation suspends the process that invokes it. The wakeup(P1) operation
resumes the execution of a blocked process .These two operations are provided by the
operating system as basic system calls.
Binary Semaphores
The semaphore construct described in the previous sections is commonly known as a counting
semaphore, since its integer value can range over an unrestricted domain. A binary semaphore is
a semaphore with an integer value that can range only between 0 and 1. A binary semaphore
can be simpler to implement than accounting semaphore, depending on the underlying
hardware architecture. We will now show how a counting semaphore can be implemented
using binary semaphores. Let S be accounting semaphore.
binary-semaphore S1,S2;
int C;
InitiallyS1=1,S2=0,and the value of integer C is set to the initial value of the counting
semaphore S. The wait operation on the counting semaphores can be implemented as follows:
wait(S1);
C--;
if (C<0){
signal(S1) ;
wait(S2);
}
signal(S1);
ThesignaloperationonthecountingsemaphoreScanbeimplementedasfollows:
wai t(S1) ;
C++;
if (C<=0)
signal(S2);
else signal(S1);
do{
produce an item in nextp
...
wait (empty) ;
wait(mutex);
...
add next p to buffer
...
signal(mutex);
signal(full);
)while(1);
In the solution to the first readers-writers problem, the reader processes share the following
data b structures:
Semaphore mutex,
wrt;int
readcount;
Note that, if a writer is in the critical section and n readers are waiting, then one reader is
queued on wrt, and n -1 readers are queued on mutex. Also observe that, when a writer
executes signal (wrt), we may resume the execution of either the waiting readers or a single
waiting writer.
The Dining-Philosophers Problem
Consider five philosophers who spend their lives thinking and eating. The philosophers share a
common circular table surrounded by five chairs, each belonging to one philosopher. In the
center of the table is a bowl of rice, and the table is laid with five single chopsticks. When a
philosopher thinks, she does not interact with her colleagues. From time to time, a philosopher
gets hungry and tries to pick up the two chopsticks that are closest to her (the chopsticks that are
between her and her left and right neighbors). A philosopher may pick up only one chopstick at a
time. Obviously, she cannot pick up a chopstick that is already in the hand of a neighbor. When a
hungry philosopher has both her chopsticks at the same time, she eats without releasing her
chopsticks. When she is finished eating, she puts down both of her chopsticks and starts
thinking again.
wait (chopstick[i]);
wait (chopstick[(i+1)%5]) ;
...
eat
...
signal(chopstick[(i+1)%5]);
...
think
...
)while(1);
.
The dining-philosophers problem is considered a classic synchronization problem, neither
because of its practical importance nor because computer scientists dislike philosophers, but
because it is an example of a large class of concurrency –control problems. It is a simple
representation of the need to allocate several resources among several processes in a deadlock-
and starvation free manner.
One simple solution is to represent each chopstick by a semaphore. A philosopher tries to grab
the chopstick by executing a wait operation on that semaphore; she releases her chopsticks by
executing the signal operation on the appropriate semaphores. Thus, the shared data are
Semaphore chopstick [5]; where all the elements of chopstick are initialized to1.
Although this solution guarantees that no two neighbors are eating simultaneously,
it nevertheless must be rejected because it has the possibility of creating a deadlock. Suppose that
all five philosophers become hungry simultaneously, and each grabs her left chopstick. All
the elements of chopstick will now be equal to 0. When each philosopher tries to grab her right
chopstick, she will be delayed forever.
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. Finally, any satisfactory solution to the dining-philosophers problem must guard
against the possibility that one of the philosophers will starve to death.
Monitors
Another high -level synchronization construct is the monitor type. A monitor is
characterized by a set of programmer -defined operators. The representation of a monitor
type consists of declarations of variables whose values define the state of an instance of the type,
as well as the bodies of procedures or functions that implement operations on the type. The
syntax of a monitor is
.The representation of a monitor type can not be used directly by the various processes. Thus, a
procedure defined with in a monitor can access only those variables declared locally within the
monitor and its formal parameters .Similarly ,the local variables of a monitor can be accessed by
only the local procedures.
The monitor construct ensures that only one process at a time can be active within the monitor.
Consequently, the programmer does not need to code this synchronization constraint explicitly.
However, the monitor construct, as defined so far, is not sufficiently powerful for modeling
some
Synchronization schemes. For this purpose, we need to define additional synchronization
mechanisms. These mechanisms are provided by the condition operations construct. A
programmer who needs to write her own tailor-made synchronization scheme can define one or
more variables of type condition:
Condition x,y;
The only operations that can be invoked on a condition variable are wait and signal. The
operation means that the process invoking this operation is suspended until another process
invokes
The x.signal operation resumes exactly one suspended process. If no process is suspended, then
the signal operation has no effect; that is, the state of x is as though the operation were never
executed .Contrast this operation with the signal operation associated with semaphores, which
always affects the state of the semaphore. Now suppose that, when the x. signal () operation is
invoked by a process P, there is a suspended process Q associated with condition x. Clearly, if
the operations suspended process Q is allowed to resume its execution, the signaling process P
must wait. Otherwise, both P and Q will be active simultaneously within the monitor. Note,
however, that both processes can conceptually continue with their execution.
1. P either waits until Q leaves the monitor, or waits for another condition.
2. Q either waits until Pl eaves the monitor, or waits for another condition.
There are reasonable arguments in favor of adopting either option 1 or option 2. Since P was
already executing in the monitor, choice 2 seems more reasonable. However, if we allow
process P to continue, the" logical" condition for which Q was waiting may no longer hold by the
time Q is resumed. Choice 1 was advocated by Ho are, mainly because the preceding argument
in favor of it translates directly to simpler and more elegant proof rules. A compromise between
these two choices was adopted in the language Concurrent C. When process P executes the
signal operation, process Q is immediately resumed. This model is less powerful than Hoare's,
because a process cannot signal more than once during a single procedure call.